diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..b70b2019e --- /dev/null +++ b/.clang-format @@ -0,0 +1,96 @@ +--- +BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/AbstractCommand.h b/AbstractCommand.h index 0377e3874..66d28a98e 100644 --- a/AbstractCommand.h +++ b/AbstractCommand.h @@ -19,15 +19,15 @@ #ifndef ABSTRACTCOMMAND_H_ #define ABSTRACTCOMMAND_H_ -#include "ref_countable.h" #include "intrusive_ptr.h" +#include "ref_countable.h" -template +template class AbstractCommand : public ref_countable { -public: - typedef intrusive_ptr Ptr; + public: + typedef intrusive_ptr Ptr; - virtual Res operator()(ArgTypes... args) = 0; + virtual Res operator()(ArgTypes... args) = 0; }; #endif // ifndef ABSTRACTCOMMAND_H_ diff --git a/AbstractFilter.h b/AbstractFilter.h index 20a1344b6..23fc9e759 100644 --- a/AbstractFilter.h +++ b/AbstractFilter.h @@ -19,10 +19,10 @@ #ifndef ABSTRACTFILTER_H_ #define ABSTRACTFILTER_H_ -#include "ref_countable.h" -#include "PageView.h" -#include "PageOrderOption.h" #include +#include "PageOrderOption.h" +#include "PageView.h" +#include "ref_countable.h" class FilterUiInterface; class PageInfo; @@ -37,36 +37,30 @@ class QDomElement; * Filters represent processing stages, like "Deskew", "Margins" and "Output". */ class AbstractFilter : public ref_countable { -public: - ~AbstractFilter() override = default; + public: + ~AbstractFilter() override = default; - virtual QString getName() const = 0; + virtual QString getName() const = 0; - virtual PageView getView() const = 0; + virtual PageView getView() const = 0; - virtual void selected() { - } + virtual void selected() {} - virtual int selectedPageOrder() const { - return -1; - } + virtual int selectedPageOrder() const { return -1; } - virtual void selectPageOrder(int option) { - } + virtual void selectPageOrder(int option) {} - virtual std::vector pageOrderOptions() const { - return std::vector(); - } + virtual std::vector pageOrderOptions() const { return std::vector(); } - virtual void performRelinking(const AbstractRelinker& relinker) = 0; + virtual void performRelinking(const AbstractRelinker& relinker) = 0; - virtual void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) = 0; + virtual void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) = 0; - virtual QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const = 0; + virtual QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const = 0; - virtual void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) = 0; + virtual void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) = 0; - virtual void loadDefaultSettings(const PageInfo& page_info) = 0; + virtual void loadDefaultSettings(const PageInfo& page_info) = 0; }; diff --git a/AbstractRelinker.h b/AbstractRelinker.h index 63f3ac982..7eb1c0f52 100644 --- a/AbstractRelinker.h +++ b/AbstractRelinker.h @@ -25,14 +25,14 @@ class RelinkablePath; class QString; class AbstractRelinker : public ref_countable { -public: - ~AbstractRelinker() override = default; - - /** - * Returns the path to be used instead of the given path. - * The same path will be returned if no substitution is to be made. - */ - virtual QString substitutionPathFor(const RelinkablePath& orig_path) const = 0; + public: + ~AbstractRelinker() override = default; + + /** + * Returns the path to be used instead of the given path. + * The same path will be returned if no substitution is to be made. + */ + virtual QString substitutionPathFor(const RelinkablePath& orig_path) const = 0; }; diff --git a/Application.cpp b/Application.cpp index fd397be91..38209be76 100644 --- a/Application.cpp +++ b/Application.cpp @@ -16,83 +16,106 @@ along with this program. If not, see . */ -#include "RelinkingDialog.h" -#include "LoadFilesStatusDialog.h" +#include "Application.h" +#include +#include +#include +#include "DebugImages.h" +#include "ErrorWidget.h" #include "FixDpiDialog.h" #include "ImageMetadataLoader.h" +#include "LoadFilesStatusDialog.h" +#include "MainWindow.h" +#include "NewOpenProjectPanel.h" +#include "OutOfMemoryHandler.h" #include "ProcessingIndicationWidget.h" #include "ProjectOpeningContext.h" -#include "DebugImages.h" -#include "ErrorWidget.h" -#include "Utils.h" +#include "RelinkingDialog.h" #include "StageSequence.h" -#include "NewOpenProjectPanel.h" -#include "MainWindow.h" -#include "Application.h" -#include "OutOfMemoryHandler.h" -#include -#include +#include "Utils.h" Application::Application(int& argc, char** argv) : QApplication(argc, argv), m_currentLocale("en") { - initTranslations(); + initTranslations(); + initPortableVersion(); } bool Application::notify(QObject* receiver, QEvent* e) { - try { - return QApplication::notify(receiver, e); - } catch (const std::bad_alloc&) { - OutOfMemoryHandler::instance().handleOutOfMemorySituation(); + try { + return QApplication::notify(receiver, e); + } catch (const std::bad_alloc&) { + OutOfMemoryHandler::instance().handleOutOfMemorySituation(); - return false; - } + return false; + } } void Application::installLanguage(const QString& locale) { - if (m_currentLocale == locale) { - return; - } + if (m_currentLocale == locale) { + return; + } - if (m_translationsMap.find(locale) != m_translationsMap.end()) { - bool loaded = m_translator.load(m_translationsMap[locale]); + if (m_translationsMap.find(locale) != m_translationsMap.end()) { + bool loaded = m_translator.load(m_translationsMap[locale]); - this->removeTranslator(&m_translator); - this->installTranslator(&m_translator); + this->removeTranslator(&m_translator); + this->installTranslator(&m_translator); - m_currentLocale = (loaded) ? locale : "en"; - } else { - this->removeTranslator(&m_translator); + m_currentLocale = (loaded) ? locale : "en"; + } else { + this->removeTranslator(&m_translator); - m_currentLocale = "en"; - } + m_currentLocale = "en"; + } } const QString& Application::getCurrentLocale() const { - return m_currentLocale; + return m_currentLocale; } std::list Application::getLanguagesList() const { - std::list list{"en"}; - std::transform(m_translationsMap.begin(), m_translationsMap.end(), std::back_inserter(list), - [](const std::pair& val) { return val.first; }); + std::list list{"en"}; + std::transform(m_translationsMap.begin(), m_translationsMap.end(), std::back_inserter(list), + [](const std::pair& val) { return val.first; }); - return list; + return list; } void Application::initTranslations() { - const QStringList translation_dirs(QString::fromUtf8(TRANSLATION_DIRS).split(QChar(':'), QString::SkipEmptyParts)); - - const QStringList language_file_filter("scantailor_*.qm"); - for (const QString& path : translation_dirs) { - QDir dir(QDir::cleanPath(applicationDirPath() + '/' + path)); - if (dir.exists()) { - QStringList translationFileNames = QDir(dir.path()).entryList(language_file_filter); - for (const QString& fileName : translationFileNames) { - QString locale(fileName); - locale.truncate(locale.lastIndexOf('.')); - locale.remove(0, locale.indexOf('_') + 1); - - m_translationsMap[locale] = dir.absoluteFilePath(fileName); - } - } + const QStringList translation_dirs(QString::fromUtf8(TRANSLATION_DIRS).split(QChar(':'), QString::SkipEmptyParts)); + + const QStringList language_file_filter("scantailor_*.qm"); + for (const QString& path : translation_dirs) { + QDir dir = (QDir::isAbsolutePath(path)) ? QDir(path) : QDir::cleanPath(applicationDirPath() + '/' + path); + if (dir.exists()) { + QStringList translationFileNames = QDir(dir.path()).entryList(language_file_filter); + for (const QString& fileName : translationFileNames) { + QString locale(fileName); + locale.truncate(locale.lastIndexOf('.')); + locale.remove(0, locale.indexOf('_') + 1); + + m_translationsMap[locale] = dir.absoluteFilePath(fileName); + } } + } +} + +void Application::initPortableVersion() { + const QString portableConfigDirName = QString::fromUtf8(PORTABLE_CONFIG_DIR); + if (portableConfigDirName.isEmpty()) { + return; + } + + const QDir portableConfigPath(applicationDirPath() + '/' + portableConfigDirName); + if ((portableConfigPath.exists() && QTemporaryDir(portableConfigPath.absolutePath()).isValid()) + || (!portableConfigPath.exists() && portableConfigPath.mkpath("."))) { + m_portableConfigPath = portableConfigPath.absolutePath(); + } +} + +bool Application::isPortableVersion() const { + return !m_portableConfigPath.isNull(); +} + +const QString& Application::getPortableConfigPath() const { + return m_portableConfigPath; } diff --git a/Application.h b/Application.h index b3d543bda..cca03ff4a 100644 --- a/Application.h +++ b/Application.h @@ -22,31 +22,37 @@ #include #include #include -#include "ui_MainWindow.h" -#include "FilterUiInterface.h" #include "BackgroundTask.h" +#include "FilterUiInterface.h" #include "OutputFileNameGenerator.h" class Application : public QApplication { - Q_OBJECT -public: - Application(int& argc, char** argv); + Q_OBJECT + public: + Application(int& argc, char** argv); + + bool notify(QObject* receiver, QEvent* e) override; + + const QString& getCurrentLocale() const; + + void installLanguage(const QString& locale); - bool notify(QObject* receiver, QEvent* e) override; + std::list getLanguagesList() const; - const QString& getCurrentLocale() const; + bool isPortableVersion() const; - void installLanguage(const QString& locale); + const QString& getPortableConfigPath() const; - std::list getLanguagesList() const; + private: + void initTranslations(); -private: - void initTranslations(); + void initPortableVersion(); - QTranslator m_translator; - QString m_currentLocale; - std::map m_translationsMap; + QTranslator m_translator; + QString m_currentLocale; + std::map m_translationsMap; + QString m_portableConfigPath; }; diff --git a/AtomicFileOverwriter.cpp b/AtomicFileOverwriter.cpp index c3bf42a85..9eedcee63 100644 --- a/AtomicFileOverwriter.cpp +++ b/AtomicFileOverwriter.cpp @@ -17,56 +17,56 @@ */ #include "AtomicFileOverwriter.h" -#include "Utils.h" #include #include +#include "Utils.h" AtomicFileOverwriter::AtomicFileOverwriter() = default; AtomicFileOverwriter::~AtomicFileOverwriter() { - abort(); + abort(); } QIODevice* AtomicFileOverwriter::startWriting(const QString& file_path) { - abort(); + abort(); - m_ptrTempFile = std::make_unique(file_path); - m_ptrTempFile->setAutoRemove(false); - if (!m_ptrTempFile->open()) { - m_ptrTempFile.reset(); - } + m_tempFile = std::make_unique(file_path); + m_tempFile->setAutoRemove(false); + if (!m_tempFile->open()) { + m_tempFile.reset(); + } - return m_ptrTempFile.get(); + return m_tempFile.get(); } bool AtomicFileOverwriter::commit() { - if (!m_ptrTempFile) { - return false; - } + if (!m_tempFile) { + return false; + } - const QString temp_file_path(m_ptrTempFile->fileName()); - const QString target_path(m_ptrTempFile->fileTemplate()); + const QString temp_file_path(m_tempFile->fileName()); + const QString target_path(m_tempFile->fileTemplate()); - // Yes, we have to destroy this object here, because: - // 1. Under Windows, open files can't be renamed or deleted. - // 2. QTemporaryFile::close() doesn't really close it. - m_ptrTempFile.reset(); + // Yes, we have to destroy this object here, because: + // 1. Under Windows, open files can't be renamed or deleted. + // 2. QTemporaryFile::close() doesn't really close it. + m_tempFile.reset(); - if (!Utils::overwritingRename(temp_file_path, target_path)) { - QFile::remove(temp_file_path); + if (!Utils::overwritingRename(temp_file_path, target_path)) { + QFile::remove(temp_file_path); - return false; - } + return false; + } - return true; + return true; } void AtomicFileOverwriter::abort() { - if (!m_ptrTempFile) { - return; - } + if (!m_tempFile) { + return; + } - const QString temp_file_path(m_ptrTempFile->fileName()); - m_ptrTempFile.reset(); // See comments in commit() - QFile::remove(temp_file_path); + const QString temp_file_path(m_tempFile->fileName()); + m_tempFile.reset(); // See comments in commit() + QFile::remove(temp_file_path); } diff --git a/AtomicFileOverwriter.h b/AtomicFileOverwriter.h index b41aff895..c8d3ad38b 100644 --- a/AtomicFileOverwriter.h +++ b/AtomicFileOverwriter.h @@ -19,8 +19,8 @@ #ifndef ATOMICFILEOVERWRITER_H_ #define ATOMICFILEOVERWRITER_H_ -#include "NonCopyable.h" #include +#include "NonCopyable.h" class QString; class QIODevice; @@ -34,43 +34,43 @@ class QTemporaryFile; * in the same directory as the target file. */ class AtomicFileOverwriter { - DECLARE_NON_COPYABLE(AtomicFileOverwriter) + DECLARE_NON_COPYABLE(AtomicFileOverwriter) -public: - AtomicFileOverwriter(); + public: + AtomicFileOverwriter(); - /** - * \brief Destroys the object and calls abort() if necessary. - */ - ~AtomicFileOverwriter(); + /** + * \brief Destroys the object and calls abort() if necessary. + */ + ~AtomicFileOverwriter(); - /** - * \brief Start writing to a temporary file. - * - * \returns A temporary file as QIODevice, or null of temporary file - * could not be opened. In latter case, calling abort() - * is not necessary. - * - * If a file is already being written, it calles abort() and then - * proceeds as usual. - */ - QIODevice* startWriting(const QString& file_path); + /** + * \brief Start writing to a temporary file. + * + * \returns A temporary file as QIODevice, or null of temporary file + * could not be opened. In latter case, calling abort() + * is not necessary. + * + * If a file is already being written, it calles abort() and then + * proceeds as usual. + */ + QIODevice* startWriting(const QString& file_path); - /** - * \brief Replaces the target file with the temporary one. - * - * If replacing failed, false is returned and the temporary file - * is removed. - */ - bool commit(); + /** + * \brief Replaces the target file with the temporary one. + * + * If replacing failed, false is returned and the temporary file + * is removed. + */ + bool commit(); - /** - * \brief Removes the temporary file without touching the target one. - */ - void abort(); + /** + * \brief Removes the temporary file without touching the target one. + */ + void abort(); -private: - std::unique_ptr m_ptrTempFile; + private: + std::unique_ptr m_tempFile; }; diff --git a/AutoManualMode.cpp b/AutoManualMode.cpp new file mode 100644 index 000000000..8c8dcec20 --- /dev/null +++ b/AutoManualMode.cpp @@ -0,0 +1,28 @@ +#include "AutoManualMode.h" + +QString autoManualModeToString(AutoManualMode mode) { + QString str; + switch (mode) { + case MODE_AUTO: + str = "auto"; + break; + case MODE_MANUAL: + str = "manual"; + break; + case MODE_DISABLED: + str = "disabled"; + break; + } + + return str; +} + +AutoManualMode stringToAutoManualMode(const QString& str) { + if (str == "disabled") { + return MODE_DISABLED; + } else if (str == "manual") { + return MODE_MANUAL; + } else { + return MODE_AUTO; + } +} \ No newline at end of file diff --git a/AutoManualMode.h b/AutoManualMode.h index 2c7e436a3..250af1b48 100644 --- a/AutoManualMode.h +++ b/AutoManualMode.h @@ -1,24 +1,12 @@ -/* - Scan Tailor - Interactive post-processing tool for scanned pages. - Copyright (C) 2007-2008 Joseph Artsimovich +#ifndef SCANTAILOR_ADVANCED_AUTOMANUALMODE_H +#define SCANTAILOR_ADVANCED_AUTOMANUALMODE_H - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. +#include - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +enum AutoManualMode { MODE_AUTO, MODE_MANUAL, MODE_DISABLED }; - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ +QString autoManualModeToString(AutoManualMode mode); -#ifndef AUTOMANUALMODE_H_ -#define AUTOMANUALMODE_H_ +AutoManualMode stringToAutoManualMode(const QString& str); -enum AutoManualMode { MODE_AUTO, MODE_MANUAL }; - -#endif +#endif // SCANTAILOR_ADVANCED_AUTOMANUALMODE_H diff --git a/BackgroundExecutor.cpp b/BackgroundExecutor.cpp index 9592b8b3f..09964d713 100644 --- a/BackgroundExecutor.cpp +++ b/BackgroundExecutor.cpp @@ -17,112 +17,110 @@ */ #include "BackgroundExecutor.h" -#include "OutOfMemoryHandler.h" #include #include #include +#include "OutOfMemoryHandler.h" class BackgroundExecutor::Dispatcher : public QObject { -public: - explicit Dispatcher(Impl& owner); + public: + explicit Dispatcher(Impl& owner); -protected: - void customEvent(QEvent* event) override; + protected: + void customEvent(QEvent* event) override; -private: - Impl& m_rOwner; + private: + Impl& m_owner; }; class BackgroundExecutor::Impl : public QThread { -public: - explicit Impl(BackgroundExecutor& owner); + public: + explicit Impl(BackgroundExecutor& owner); - ~Impl() override; + ~Impl() override; - void enqueueTask(const TaskPtr& task); + void enqueueTask(const TaskPtr& task); -protected: - void run() override; + protected: + void run() override; - void customEvent(QEvent* event) override; + void customEvent(QEvent* event) override; -private: - BackgroundExecutor& m_rOwner; - Dispatcher m_dispatcher; - bool m_threadStarted; + private: + BackgroundExecutor& m_owner; + Dispatcher m_dispatcher; + bool m_threadStarted; }; /*============================ BackgroundExecutor ==========================*/ -BackgroundExecutor::BackgroundExecutor() : m_ptrImpl(new Impl(*this)) { -} +BackgroundExecutor::BackgroundExecutor() : m_impl(new Impl(*this)) {} BackgroundExecutor::~BackgroundExecutor() = default; void BackgroundExecutor::shutdown() { - m_ptrImpl.reset(); + m_impl.reset(); } void BackgroundExecutor::enqueueTask(const TaskPtr& task) { - if (m_ptrImpl) { - m_ptrImpl->enqueueTask(task); - } + if (m_impl) { + m_impl->enqueueTask(task); + } } /*===================== BackgroundExecutor::Dispatcher =====================*/ -BackgroundExecutor::Dispatcher::Dispatcher(Impl& owner) : m_rOwner(owner) { -} +BackgroundExecutor::Dispatcher::Dispatcher(Impl& owner) : m_owner(owner) {} void BackgroundExecutor::Dispatcher::customEvent(QEvent* event) { - try { - auto* evt = dynamic_cast(event); - assert(evt); - - const TaskPtr& task = evt->payload(); - assert(task); - - const TaskResultPtr result((*task)()); - if (result) { - QCoreApplication::postEvent(&m_rOwner, new ResultEvent(result)); - } - } catch (const std::bad_alloc&) { - OutOfMemoryHandler::instance().handleOutOfMemorySituation(); + try { + auto* evt = dynamic_cast(event); + assert(evt); + + const TaskPtr& task = evt->payload(); + assert(task); + + const TaskResultPtr result((*task)()); + if (result) { + QCoreApplication::postEvent(&m_owner, new ResultEvent(result)); } + } catch (const std::bad_alloc&) { + OutOfMemoryHandler::instance().handleOutOfMemorySituation(); + } } /*======================= BackgroundExecutor::Impl =========================*/ BackgroundExecutor::Impl::Impl(BackgroundExecutor& owner) - : m_rOwner(owner), m_dispatcher(*this), m_threadStarted(false) { - m_dispatcher.moveToThread(this); + : m_owner(owner), m_dispatcher(*this), m_threadStarted(false) { + m_dispatcher.moveToThread(this); } BackgroundExecutor::Impl::~Impl() { - exit(); - wait(); + exit(); + wait(); } void BackgroundExecutor::Impl::enqueueTask(const TaskPtr& task) { - QCoreApplication::postEvent(&m_dispatcher, new TaskEvent(task)); - if (!m_threadStarted) { - start(); - m_threadStarted = true; - } + QCoreApplication::postEvent(&m_dispatcher, new TaskEvent(task)); + if (!m_threadStarted) { + start(); + m_threadStarted = true; + } } void BackgroundExecutor::Impl::run() { - exec(); + exec(); } void BackgroundExecutor::Impl::customEvent(QEvent* event) { - auto* evt = dynamic_cast(event); - assert(evt); + auto* evt = dynamic_cast(event); + assert(evt); - const TaskResultPtr& result = evt->payload(); - assert(result); + const TaskResultPtr& result = evt->payload(); + assert(result); - (*result)(); + (*result)(); } diff --git a/BackgroundExecutor.h b/BackgroundExecutor.h index 0e0dca3d2..7aa4468b3 100644 --- a/BackgroundExecutor.h +++ b/BackgroundExecutor.h @@ -19,53 +19,53 @@ #ifndef BACKGROUNDEXECUTOR_H_ #define BACKGROUNDEXECUTOR_H_ -#include "NonCopyable.h" -#include "intrusive_ptr.h" +#include #include "AbstractCommand.h" +#include "NonCopyable.h" #include "PayloadEvent.h" -#include +#include "intrusive_ptr.h" class BackgroundExecutor { - DECLARE_NON_COPYABLE(BackgroundExecutor) - -public: - typedef intrusive_ptr> TaskResultPtr; - typedef intrusive_ptr> TaskPtr; - - BackgroundExecutor(); - - /** - * \brief Waits for background tasks to finish, then destroys the object. - */ - ~BackgroundExecutor(); - - /** - * \brief Waits for pending jobs to finish and stop the background thread. - * - * The destructor also performs these tasks, so this method is only - * useful to prematuraly stop task processing. After shutdown, any - * attempts to enqueue a task will be silently ignored. - */ - void shutdown(); - - /** - * \brief Enqueue a task for execution in a background thread. - * - * A task is a functor to be executed in a background thread. - * That functor may optionally return another one, that is - * to be executed in the thread where this BackgroundExecutor - * object was constructed. - */ - void enqueueTask(const TaskPtr& task); - -private: - class Impl; - class Dispatcher; - - typedef PayloadEvent TaskEvent; - typedef PayloadEvent ResultEvent; - - std::unique_ptr m_ptrImpl; + DECLARE_NON_COPYABLE(BackgroundExecutor) + + public: + typedef intrusive_ptr> TaskResultPtr; + typedef intrusive_ptr> TaskPtr; + + BackgroundExecutor(); + + /** + * \brief Waits for background tasks to finish, then destroys the object. + */ + ~BackgroundExecutor(); + + /** + * \brief Waits for pending jobs to finish and stop the background thread. + * + * The destructor also performs these tasks, so this method is only + * useful to prematuraly stop task processing. After shutdown, any + * attempts to enqueue a task will be silently ignored. + */ + void shutdown(); + + /** + * \brief Enqueue a task for execution in a background thread. + * + * A task is a functor to be executed in a background thread. + * That functor may optionally return another one, that is + * to be executed in the thread where this BackgroundExecutor + * object was constructed. + */ + void enqueueTask(const TaskPtr& task); + + private: + class Impl; + class Dispatcher; + + typedef PayloadEvent TaskEvent; + typedef PayloadEvent ResultEvent; + + std::unique_ptr m_impl; }; diff --git a/BackgroundTask.cpp b/BackgroundTask.cpp index d34c3688b..d36c1711f 100644 --- a/BackgroundTask.cpp +++ b/BackgroundTask.cpp @@ -18,12 +18,12 @@ #include "BackgroundTask.h" -const char* BackgroundTask::CancelledException::what() const throw() { - return "BackgroundTask cancelled"; +const char* BackgroundTask::CancelledException::what() const noexcept { + return "BackgroundTask cancelled"; } void BackgroundTask::throwIfCancelled() const { - if (isCancelled()) { - throw CancelledException(); - } + if (isCancelled()) { + throw CancelledException(); + } } diff --git a/BackgroundTask.h b/BackgroundTask.h index 41ebf071e..f1328a45b 100644 --- a/BackgroundTask.h +++ b/BackgroundTask.h @@ -19,46 +19,39 @@ #ifndef BACKGROUNDTASK_H_ #define BACKGROUNDTASK_H_ +#include +#include #include "AbstractCommand.h" -#include "intrusive_ptr.h" #include "FilterResult.h" #include "TaskStatus.h" -#include -#include +#include "intrusive_ptr.h" class BackgroundTask : public AbstractCommand, public TaskStatus { -public: - enum Type { INTERACTIVE, BATCH }; + public: + enum Type { INTERACTIVE, BATCH }; - class CancelledException : public std::exception { - public: - const char* what() const throw() override; - }; + class CancelledException : public std::exception { + public: + const char* what() const noexcept override; + }; - explicit BackgroundTask(Type type) : m_type(type) { - } + explicit BackgroundTask(Type type) : m_type(type) {} - Type type() const { - return m_type; - } + Type type() const { return m_type; } - void cancel() override { - m_cancelFlag.store(1); - } + void cancel() override { m_cancelFlag.store(1); } - bool isCancelled() const override { - return m_cancelFlag.load() != 0; - } + bool isCancelled() const override { return m_cancelFlag.load() != 0; } - /** - * \brief If cancelled, throws CancelledException. - */ - void throwIfCancelled() const override; + /** + * \brief If cancelled, throws CancelledException. + */ + void throwIfCancelled() const override; -private: - QAtomicInt m_cancelFlag; - const Type m_type; + private: + QAtomicInt m_cancelFlag; + const Type m_type; }; diff --git a/BasicImageView.cpp b/BasicImageView.cpp index ff175aeca..fbb5d068f 100644 --- a/BasicImageView.cpp +++ b/BasicImageView.cpp @@ -16,17 +16,17 @@ along with this program. If not, see . */ -#include "ImageTransformation.h" -#include "ImagePresentation.h" -#include "Dpm.h" #include "BasicImageView.h" +#include "Dpm.h" +#include "ImagePresentation.h" +#include "ImageTransformation.h" BasicImageView::BasicImageView(const QImage& image, const ImagePixmapUnion& downscaled_image, const Margins& margins) - : ImageViewBase(image, downscaled_image, ImagePresentation(QTransform(), QRectF(image.rect())), margins), - m_dragHandler(*this), - m_zoomHandler(*this) { - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + : ImageViewBase(image, downscaled_image, ImagePresentation(QTransform(), QRectF(image.rect())), margins), + m_dragHandler(*this), + m_zoomHandler(*this) { + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); } BasicImageView::~BasicImageView() = default; diff --git a/BasicImageView.h b/BasicImageView.h index f2570da41..4982176e6 100644 --- a/BasicImageView.h +++ b/BasicImageView.h @@ -19,25 +19,25 @@ #ifndef BASICIMAGEVIEW_H_ #define BASICIMAGEVIEW_H_ -#include "ImageViewBase.h" +#include #include "DragHandler.h" -#include "ZoomHandler.h" #include "ImagePixmapUnion.h" +#include "ImageViewBase.h" #include "Margins.h" -#include +#include "ZoomHandler.h" class BasicImageView : public ImageViewBase { - Q_OBJECT -public: - explicit BasicImageView(const QImage& image, - const ImagePixmapUnion& downscaled_image = ImagePixmapUnion(), - const Margins& margins = Margins()); + Q_OBJECT + public: + explicit BasicImageView(const QImage& image, + const ImagePixmapUnion& downscaled_image = ImagePixmapUnion(), + const Margins& margins = Margins()); - ~BasicImageView() override; + ~BasicImageView() override; -private: - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + private: + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; }; diff --git a/BlackOnWhiteEstimator.cpp b/BlackOnWhiteEstimator.cpp new file mode 100644 index 000000000..13da1fcff --- /dev/null +++ b/BlackOnWhiteEstimator.cpp @@ -0,0 +1,103 @@ + +#include "BlackOnWhiteEstimator.h" +#include +#include +#include +#include +#include +#include +#include "DebugImages.h" +#include "Despeckle.h" +#include "TaskStatus.h" + +using namespace imageproc; + +bool BlackOnWhiteEstimator::isBlackOnWhiteRefining(const imageproc::GrayImage& grayImage, + const ImageTransformation& xform, + const TaskStatus& status, + DebugImages* dbg) { + BinaryImage bw150; + { + ImageTransformation xform150dpi(xform); + xform150dpi.preScaleToDpi(Dpi(150, 150)); + + if (xform150dpi.resultingRect().toRect().isEmpty()) { + return true; + } + + QImage gray150(transformToGray(grayImage, xform150dpi.transform(), xform150dpi.resultingRect().toRect(), + OutsidePixels::assumeColor(Qt::white))); + bw150 = binarizeOtsu(gray150); + + Despeckle::despeckleInPlace(bw150, Dpi(150, 150), Despeckle::NORMAL, status); + bw150.invert(); + Despeckle::despeckleInPlace(bw150, Dpi(150, 150), Despeckle::NORMAL, status); + bw150.invert(); + if (dbg) { + dbg->add(bw150, "bw150"); + } + } + + status.throwIfCancelled(); + + BinaryImage contentMask; + { + BinaryImage whiteTopHat = whiteTopHatTransform(bw150, QSize(13, 13)); + BinaryImage blackTopHat = blackTopHatTransform(bw150, QSize(13, 13)); + + contentMask = whiteTopHat; + rasterOp>(contentMask, blackTopHat); + + contentMask = closeBrick(contentMask, QSize(200, 200)); + contentMask = dilateBrick(contentMask, QSize(30, 30)); + if (dbg) { + dbg->add(contentMask, "content_mask"); + } + } + + status.throwIfCancelled(); + + rasterOp>(bw150, contentMask); + + return (2 * bw150.countBlackPixels() <= contentMask.countBlackPixels()); +} + +bool BlackOnWhiteEstimator::isBlackOnWhite(const imageproc::GrayImage& grayImage, + const ImageTransformation& xform, + const TaskStatus& status, + DebugImages* dbg) { + if (isBlackOnWhite(grayImage, xform.resultingPreCropArea())) { + return true; + } else { + // The black borders of the page can make the method above giving the wrong result. + return isBlackOnWhiteRefining(grayImage, xform, status, dbg); + } +} + +bool BlackOnWhiteEstimator::isBlackOnWhite(const GrayImage& img, const BinaryImage& mask) { + if (img.isNull()) { + throw std::invalid_argument("BlackOnWhiteEstimator: image is null."); + } + if (img.size() != mask.size()) { + throw std::invalid_argument("BlackOnWhiteEstimator: img and mask have different sizes"); + } + + BinaryImage bwImage(img, BinaryThreshold::otsuThreshold(GrayscaleHistogram(img, mask))); + rasterOp>(bwImage, mask); + + return (2 * bwImage.countBlackPixels() <= mask.countBlackPixels()); +} + +bool BlackOnWhiteEstimator::isBlackOnWhite(const GrayImage& img, const QPolygonF& cropArea) { + if (img.isNull()) { + throw std::invalid_argument("BlackOnWhiteEstimator: image is null."); + } + if (cropArea.intersected(QRectF(img.rect())).isEmpty()) { + throw std::invalid_argument("BlackOnWhiteEstimator: the cropping area is wrong."); + } + + BinaryImage mask(img.size(), BLACK); + PolygonRasterizer::fillExcept(mask, WHITE, cropArea, Qt::WindingFill); + + return isBlackOnWhite(img, mask); +} diff --git a/BlackOnWhiteEstimator.h b/BlackOnWhiteEstimator.h new file mode 100644 index 000000000..7e2345f4b --- /dev/null +++ b/BlackOnWhiteEstimator.h @@ -0,0 +1,35 @@ + +#ifndef SCANTAILOR_BLACKONWHITEESTIMATOR_H +#define SCANTAILOR_BLACKONWHITEESTIMATOR_H + +#include +#include +#include "ImageTransformation.h" + +class TaskStatus; +class DebugImages; + +namespace imageproc { +class GrayImage; +class BinaryImage; +} // namespace imageproc + +class BlackOnWhiteEstimator { + public: + static bool isBlackOnWhite(const imageproc::GrayImage& grayImage, + const ImageTransformation& xform, + const TaskStatus& status, + DebugImages* dbg = nullptr); + + static bool isBlackOnWhiteRefining(const imageproc::GrayImage& grayImage, + const ImageTransformation& xform, + const TaskStatus& status, + DebugImages* dbg = nullptr); + + static bool isBlackOnWhite(const imageproc::GrayImage& img, const imageproc::BinaryImage& mask); + + static bool isBlackOnWhite(const imageproc::GrayImage& img, const QPolygonF& cropArea); +}; + + +#endif // SCANTAILOR_BLACKONWHITEESTIMATOR_H diff --git a/BubbleAnimation.cpp b/BubbleAnimation.cpp index 2c3f7c510..b72c830bf 100644 --- a/BubbleAnimation.cpp +++ b/BubbleAnimation.cpp @@ -17,70 +17,70 @@ */ #include "BubbleAnimation.h" -#include "imageproc/Constants.h" -#include "imageproc/ColorInterpolation.h" #include #include #include #include +#include "imageproc/ColorInterpolation.h" +#include "imageproc/Constants.h" using namespace imageproc; BubbleAnimation::BubbleAnimation(const int num_bubbles) : m_numBubbles(num_bubbles), m_curFrame(0) { - assert(m_numBubbles > 0); + assert(m_numBubbles > 0); } bool BubbleAnimation::nextFrame(const QColor& head_color, const QColor& tail_color, QPaintDevice* pd, QRectF rect) { - if (rect.isNull()) { - rect = QRectF(0.0, 0.0, pd->width(), pd->height()); - } + if (rect.isNull()) { + rect = QRectF(0.0, 0.0, pd->width(), pd->height()); + } - QPainter painter(pd); + QPainter painter(pd); - return nextFrame(head_color, tail_color, &painter, rect); + return nextFrame(head_color, tail_color, &painter, rect); } bool BubbleAnimation::nextFrame(const QColor& head_color, const QColor& tail_color, QPainter* painter, const QRectF rect) { - const QPointF center(rect.center()); - const double radius = std::min(center.x() - rect.x(), center.y() - rect.y()); - - const double PI = imageproc::constants::PI; - const double arc_fraction_as_radius = 0.25; - // We have the following system of equations: - // bubble_radius = arc_between_bubbles * arc_fraction_as_radius; - // arc_between_bubbles = 2.0 * PI * reduced_radius / m_numBubbles; - // reduced_radius = radius - bubble_radius. - // Solving this system of equations, we get: - const double reduced_radius = radius / (1.0 + 2.0 * PI * arc_fraction_as_radius / m_numBubbles); - const double bubble_radius = radius - reduced_radius; - - const double tail_length = 0.5 * m_numBubbles; - - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - - for (int i = 0; i < m_numBubbles; ++i) { - const double angle = -0.5 * PI + 2.0 * PI * (m_curFrame - i) / m_numBubbles; - const double s = std::sin(angle); - const double c = std::cos(angle); - const QPointF vec(c * reduced_radius, s * reduced_radius); - QRectF r(0.0, 0.0, 2.0 * bubble_radius, 2.0 * bubble_radius); - r.moveCenter(center + vec); - const double color_dist = std::min(1.0, i / tail_length); - painter->setBrush(colorInterpolation(head_color, tail_color, color_dist)); - painter->drawEllipse(r); - } - - if (m_curFrame + 1 < m_numBubbles) { - ++m_curFrame; - - return true; - } else { - m_curFrame = 0; - - return false; - } + const QPointF center(rect.center()); + const double radius = std::min(center.x() - rect.x(), center.y() - rect.y()); + + const double PI = imageproc::constants::PI; + const double arc_fraction_as_radius = 0.25; + // We have the following system of equations: + // bubble_radius = arc_between_bubbles * arc_fraction_as_radius; + // arc_between_bubbles = 2.0 * PI * reduced_radius / m_numBubbles; + // reduced_radius = radius - bubble_radius. + // Solving this system of equations, we get: + const double reduced_radius = radius / (1.0 + 2.0 * PI * arc_fraction_as_radius / m_numBubbles); + const double bubble_radius = radius - reduced_radius; + + const double tail_length = 0.5 * m_numBubbles; + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + + for (int i = 0; i < m_numBubbles; ++i) { + const double angle = -0.5 * PI + 2.0 * PI * (m_curFrame - i) / m_numBubbles; + const double s = std::sin(angle); + const double c = std::cos(angle); + const QPointF vec(c * reduced_radius, s * reduced_radius); + QRectF r(0.0, 0.0, 2.0 * bubble_radius, 2.0 * bubble_radius); + r.moveCenter(center + vec); + const double color_dist = std::min(1.0, i / tail_length); + painter->setBrush(colorInterpolation(head_color, tail_color, color_dist)); + painter->drawEllipse(r); + } + + if (m_curFrame + 1 < m_numBubbles) { + ++m_curFrame; + + return true; + } else { + m_curFrame = 0; + + return false; + } } // BubbleAnimation::nextFrame diff --git a/BubbleAnimation.h b/BubbleAnimation.h index 2e424ba7a..3af147d59 100644 --- a/BubbleAnimation.h +++ b/BubbleAnimation.h @@ -30,40 +30,40 @@ class QPainter; * varying colors. */ class BubbleAnimation { -public: - explicit BubbleAnimation(int num_bubbles); + public: + explicit BubbleAnimation(int num_bubbles); - /** - * \brief Renders the next frame of the animation. - * - * \param head_color The color of the head of the string of bubbles. - * \param tail_color The color of the tail of the string of bubbles. - * \param pd The device to paint to. - * \param rect The rectangle in device coordinates to render to. - * A null rectangle indicates the whole device area - * is to be used. - * \return Whether more frames follow. After returning false, - * the next call will render the first frame again. - */ - bool nextFrame(const QColor& head_color, const QColor& tail_color, QPaintDevice* pd, QRectF rect = QRectF()); + /** + * \brief Renders the next frame of the animation. + * + * \param head_color The color of the head of the string of bubbles. + * \param tail_color The color of the tail of the string of bubbles. + * \param pd The device to paint to. + * \param rect The rectangle in device coordinates to render to. + * A null rectangle indicates the whole device area + * is to be used. + * \return Whether more frames follow. After returning false, + * the next call will render the first frame again. + */ + bool nextFrame(const QColor& head_color, const QColor& tail_color, QPaintDevice* pd, QRectF rect = QRectF()); - /** - * \brief Renders the next frame of the animation. - * - * \param head_color The color of the head of the string of bubbles. - * \param tail_color The color of the tail of the string of bubbles. - * \param painter The painter to use for drawing. - * Saving and restoring its state is the responsibility - * of the caller. - * \param rect The rectangle in painter coordinates to render to. - * \return Whether more frames follow. After returning false, - * the next call will render the first frame again. - */ - bool nextFrame(const QColor& head_color, const QColor& tail_color, QPainter* painter, QRectF rect); + /** + * \brief Renders the next frame of the animation. + * + * \param head_color The color of the head of the string of bubbles. + * \param tail_color The color of the tail of the string of bubbles. + * \param painter The painter to use for drawing. + * Saving and restoring its state is the responsibility + * of the caller. + * \param rect The rectangle in painter coordinates to render to. + * \return Whether more frames follow. After returning false, + * the next call will render the first frame again. + */ + bool nextFrame(const QColor& head_color, const QColor& tail_color, QPainter* painter, QRectF rect); -private: - int m_numBubbles; - int m_curFrame; + private: + int m_numBubbles; + int m_curFrame; }; diff --git a/CMakeLists.txt b/CMakeLists.txt index e9a10faf2..0c3d4ce09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,795 +1,808 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.9.0) -PROJECT("ScanTailor") +project("ScanTailor Advanced") # setting compiler flags -SET(CMAKE_CXX_STANDARD 17) - -IF (MSVC AND WIN_XP) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D_USING_V110_SDK71_") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_USING_V110_SDK71_") - SET(CMAKE_EXE_LINKER_FLAGS "/SUBSYSTEM:CONSOLE,5.01 /SUBSYSTEM:WINDOWS,5.01 ${CMAKE_EXE_LINKER_FLAGS}") -ENDIF () - -IF (MSVC) - # Disable checked iterators for extra performance. - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D_SECURE_SCL=0") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_SECURE_SCL=0") - - IF (DEBUG_CLI) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDEBUG_CLI") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DDEBUG_CLI") - ENDIF (DEBUG_CLI) -ENDIF () - -IF (UNIX) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") -ENDIF () - -SET( - CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" - CACHE STRING "Common C flags for all build configurations." FORCE +set(CMAKE_CXX_STANDARD 17) + +if (MSVC) + set(WIN_XP FALSE CACHE BOOLEAN "Whether to build for Windows XP.") + + if (WIN_XP) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D_USING_V110_SDK71_") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_USING_V110_SDK71_") + set(CMAKE_EXE_LINKER_FLAGS "/SUBSYSTEM:CONSOLE,5.01 /SUBSYSTEM:WINDOWS,5.01 ${CMAKE_EXE_LINKER_FLAGS}") + endif() + + # Disable checked iterators for extra performance. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D_SECURE_SCL=0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_SECURE_SCL=0") + + if (DEBUG_CLI) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDEBUG_CLI") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DDEBUG_CLI") + endif() +endif() + +if (UNIX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +endif() + +set( + CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" + CACHE STRING "Common C flags for all build configurations." FORCE ) -SET( - CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" - CACHE STRING "Common C++ flags for all build configurations." FORCE +set( + CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" + CACHE STRING "Common C++ flags for all build configurations." FORCE ) -SET( - CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" - CACHE STRING "Common link flags for all build configurations." FORCE +set( + CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" + CACHE STRING "Common link flags for all build configurations." FORCE ) -ENABLE_TESTING() +enable_testing() -# An undocumented side-effect of CONFIGURE_FILE() is that it makes +# An undocumented side-effect of configure_file() is that it makes # the whole project depend on the file we are parsing / copying. -CONFIGURE_FILE( - "${PROJECT_SOURCE_DIR}/version.h" - "${PROJECT_BINARY_DIR}/.version.h" COPYONLY +configure_file( + "${PROJECT_SOURCE_DIR}/version.h" + "${PROJECT_BINARY_DIR}/.version.h" COPYONLY ) # Prevent this leftover from old builds to be used in favour # of the one in ${PROJECT_SOURCE_DIR} -IF (NOT "${PROJECT_BINARY_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") - FILE(REMOVE "${PROJECT_BINARY_DIR}/version.h") -ENDIF () +if (NOT "${PROJECT_BINARY_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") + file(REMOVE "${PROJECT_BINARY_DIR}/version.h") +endif() # Extract VERSION and VERSION_QUAD from version.h -FILE(READ "${PROJECT_SOURCE_DIR}/version.h" version_h_contents) -STRING( - REGEX REPLACE - ".*#define[ \\t]+VERSION[ \\t]+\"([^\"]*)\".*" - "\\1" VERSION "${version_h_contents}" +file(READ "${PROJECT_SOURCE_DIR}/version.h" version_h_contents) +string( + REGEX REPLACE + ".*#define[ \\t]+VERSION[ \\t]+\"([^\"]*)\".*" + "\\1" VERSION "${version_h_contents}" ) -IF ("${VERSION}" STREQUAL "${version_h_contents}") - MESSAGE(FATAL_ERROR "Failed to extract VERSION from version.h") -ENDIF () +if ("${VERSION}" STREQUAL "${version_h_contents}") + message(FATAL_ERROR "Failed to extract VERSION from version.h") +endif() # VERSION_QUAD must be either empty or be in the form of X.Y.Z.Y -STRING( - REGEX REPLACE - ".*#define[ \\t]+VERSION_QUAD[ \\t]+\"(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)?)\".*" - "\\1" VERSION_QUAD "${version_h_contents}" +string( + REGEX REPLACE + ".*#define[ \\t]+VERSION_QUAD[ \\t]+\"(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)?)\".*" + "\\1" VERSION_QUAD "${version_h_contents}" ) -IF ("${VERSION_QUAD}" STREQUAL "${version_h_contents}") - MESSAGE(FATAL_ERROR "Failed to extract VERSION_QUAD from version.h") -ENDIF () +if ("${VERSION_QUAD}" STREQUAL "${version_h_contents}") + message(FATAL_ERROR "Failed to extract VERSION_QUAD from version.h") +endif() # This has to go quite early on, as otherwise we risk picking # up an identically named header from a system include path. -INCLUDE_DIRECTORIES(. foundation math interaction zones) +include_directories(. foundation math interaction zones) # For config.h -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") - -INCLUDE(cmake/FindPthreads.cmake) -INCLUDE(cmake/SetDefaultBuildType.cmake) -INCLUDE(cmake/UpdateTranslations.cmake) -INCLUDE(cmake/CopyToBuildDir.cmake) -INCLUDE(cmake/LibToDLL.cmake) - -ST_SET_DEFAULT_BUILD_TYPE(Release) - -IF (WIN32) - FILE(GLOB libs_dir_ "${PROJECT_SOURCE_DIR}/../libs/*libs-build*") - get_filename_component(libs_dir_name_ ${libs_dir_} NAME) - FIND_PATH( - LIB_DIR ${libs_dir_name_}/CMakeLists.txt - HINTS "${libs_dir_}/.." - DOC "Libs directory" - ) - IF (NOT LIB_DIR) - MESSAGE( - FATAL_ERROR "Libs directory could not be found! " - "You can specify it manually in LIB_DIR variable. Make sure you build the dependencies first!" - ) - ENDIF () - - FILE(GLOB jpeg_dir_ "${LIB_DIR}/jpeg-[0-9]*") - FILE(GLOB zlib_dir_ "${LIB_DIR}/zlib-[0-9]*.[0-9]*.[0-9]*") - FILE(GLOB png_dir_1 "${LIB_DIR}/libpng-[0-9]*.[0-9]*.[0-9]*") - FILE(GLOB png_dir_2 "${LIB_DIR}/lpng[0-9]*") - FILE(GLOB tiff_dir_ "${LIB_DIR}/tiff-[0-9]*.[0-9]*.[0-9]*") - FILE(GLOB qt_dir_ "${LIB_DIR}/qt-*-*-[0-9]*.[0-9]*") - FILE(GLOB boost_dir_ "${LIB_DIR}/boost_[0-9]*_[0-9]*_[0-9]*") - #FILE(GLOB opencv_dir_ "${LIB_DIR}/opencv*") -ENDIF () +include_directories("${CMAKE_CURRENT_BINARY_DIR}") + +include(cmake/SetDefaultBuildType.cmake) +include(cmake/UpdateTranslations.cmake) +include(cmake/CopyToBuildDir.cmake) +include(cmake/LibToDLL.cmake) + +st_set_default_build_type(Release) + +if (WIN32) + file(GLOB libs_dirs "${PROJECT_SOURCE_DIR}/../libs/*libs-build*") + find_path(LIBS_BUILD_DIR CMakeLists.txt + HINTS "${libs_dirs}" + DOC "Libs directory") + if (NOT LIBS_BUILD_DIR) + message(WARNING "Libs-build directory could not be found! " + "You can specify it manually in LIBS_BUILD_DIR variable. Make sure you build the dependencies first!") + else() + set(LIB_DIR "${LIBS_BUILD_DIR}/..") + file(GLOB jpeg_dirs "${LIB_DIR}/jpeg-[0-9]*") + file(GLOB zlib_dirs "${LIB_DIR}/zlib-[0-9]*.[0-9]*.[0-9]*") + file(GLOB png_dirs1 "${LIB_DIR}/libpng-[0-9]*.[0-9]*.[0-9]*") + file(GLOB png_dirs2 "${LIB_DIR}/lpng[0-9]*") + file(GLOB tiff_dirs "${LIB_DIR}/tiff-[0-9]*.[0-9]*.[0-9]*") + file(GLOB qt_dirs "${LIB_DIR}/qt-*-*-[0-9]*.[0-9]*") + file(GLOB boost_dirs "${LIB_DIR}/boost_[0-9]*_[0-9]*_[0-9]*") + endif() +endif() #=================================== JPEG ===================================# -FIND_PATH( - JPEG_INCLUDE_DIR jpeglib.h - PATHS /usr/local/include /usr/include - HINTS ${jpeg_dir_} - DOC "Path to libjpeg headers." +find_path( + JPEG_INCLUDE_DIR jpeglib.h + PATHS /usr/local/include /usr/include + HINTS ${jpeg_dirs} + DOC "Path to libjpeg headers." ) -IF (NOT JPEG_INCLUDE_DIR) - MESSAGE( - FATAL_ERROR - "Could not find jpeg headers.\n" - ) -ENDIF () +if (NOT JPEG_INCLUDE_DIR) + message(FATAL_ERROR "Could not find jpeg headers.\n") +endif() -INCLUDE_DIRECTORIES("${JPEG_INCLUDE_DIR}") +include_directories("${JPEG_INCLUDE_DIR}") -FIND_LIBRARY( - JPEG_LIBRARY_REL NAMES jpeg libjpeg.lib - PATHS /usr/local/lib /usr/lib - HINTS "${jpeg_dir_}/stage/lib" - DOC "Path to jpeg library." +find_library( + JPEG_LIBRARY_REL NAMES jpeg libjpeg.lib + PATHS /usr/local/lib /usr/lib + HINTS "${jpeg_dirs}/stage/lib" + DOC "Path to jpeg library." ) -FIND_LIBRARY( - JPEG_LIBRARY_DEB NAMES jpegd libjpegd.lib - PATHS /usr/local/lib /usr/lib - HINTS "${jpeg_dir_}/stage/lib" - DOC "Path to jpeg library." +find_library( + JPEG_LIBRARY_DEB NAMES jpegd libjpegd.lib + PATHS /usr/local/lib /usr/lib + HINTS "${jpeg_dirs}/stage/lib" + DOC "Path to jpeg library." ) -IF (JPEG_LIBRARY_DEB) - SET(JPEG_LIBRARY optimized ${JPEG_LIBRARY_REL} debug ${JPEG_LIBRARY_DEB}) -ELSE () - SET(JPEG_LIBRARY ${JPEG_LIBRARY_REL}) -ENDIF () +if (JPEG_LIBRARY_REL AND NOT JPEG_LIBRARY_DEB) + set(JPEG_LIBRARY_DEB ${JPEG_LIBRARY_REL}) +elseif (NOT JPEG_LIBRARY_REL) + message(FATAL_ERROR "Could not find jpeg library.\n") +endif() -IF (NOT JPEG_LIBRARY) - MESSAGE( - FATAL_ERROR - "Could not find jpeg library.\n" - ) -ENDIF () +set(JPEG_LIBRARY optimized "${JPEG_LIBRARY_REL}" debug "${JPEG_LIBRARY_DEB}") #=================================== ZLIB ===================================# -FIND_PATH( - ZLIB_INCLUDE_DIR zlib.h - PATHS /usr/local/include /usr/include - HINTS ${zlib_dir_} - DOC "Path to zlib headers." +find_path( + ZLIB_INCLUDE_DIR zlib.h + PATHS /usr/local/include /usr/include + HINTS ${zlib_dirs} + DOC "Path to zlib headers." ) -IF (NOT ZLIB_INCLUDE_DIR) - MESSAGE( - FATAL_ERROR - "Could not find zlib headers.\n" - ) -ENDIF () +if (NOT ZLIB_INCLUDE_DIR) + message(FATAL_ERROR "Could not find zlib headers.\n") +endif() -INCLUDE_DIRECTORIES("${ZLIB_INCLUDE_DIR}") +include_directories("${ZLIB_INCLUDE_DIR}") -FIND_LIBRARY( - ZLIB_LIBRARY_REL NAMES z zdll.lib - PATHS /usr/local/lib /usr/lib - HINTS "${zlib_dir_}/stage/lib" - DOC "Path to zlib library." +find_library( + ZLIB_LIBRARY_REL NAMES z zdll.lib + PATHS /usr/local/lib /usr/lib + HINTS "${zlib_dirs}/stage/lib" + DOC "Path to zlib library." ) -FIND_LIBRARY( - ZLIB_LIBRARY_DEB NAMES zd zdlld.lib - PATHS /usr/local/lib /usr/lib - HINTS "${zlib_dir_}/stage/lib" - DOC "Path to jpeg library." +find_library( + ZLIB_LIBRARY_DEB NAMES zd zdlld.lib + PATHS /usr/local/lib /usr/lib + HINTS "${zlib_dirs}/stage/lib" + DOC "Path to zlib library." ) -IF (ZLIB_LIBRARY_DEB) - SET(ZLIB_LIBRARY optimized ${ZLIB_LIBRARY_REL} debug ${ZLIB_LIBRARY_DEB}) -ELSE () - SET(ZLIB_LIBRARY ${ZLIB_LIBRARY_REL}) -ENDIF () +if (ZLIB_LIBRARY_REL AND NOT ZLIB_LIBRARY_DEB) + set(ZLIB_LIBRARY_DEB ${ZLIB_LIBRARY_REL}) +elseif (NOT ZLIB_LIBRARY_REL) + message(FATAL_ERROR "Could not find zlib library.\n") +endif() -IF (NOT ZLIB_LIBRARY) - MESSAGE( - FATAL_ERROR - "Could not find zlib library.\n" - ) -ENDIF () +set(ZLIB_LIBRARY optimized "${ZLIB_LIBRARY_REL}" debug "${ZLIB_LIBRARY_DEB}") -#================================== LIBPNG ==================================# +#================================== PNG ==================================# -FIND_PATH( - PNG_INCLUDE_DIR png.h - PATHS /usr/local/include /usr/include - HINTS ${png_dir_1} ${png_dir_2} - DOC "Path to libpng headers." +find_path( + PNG_INCLUDE_DIR png.h + PATHS /usr/local/include /usr/include + HINTS ${png_dirs1} ${png_dirs2} + DOC "Path to libpng headers." ) -IF (NOT PNG_INCLUDE_DIR) - MESSAGE( - FATAL_ERROR - "Could not find libpng headers.\n" - ) -ENDIF () +if (NOT PNG_INCLUDE_DIR) + message(FATAL_ERROR "Could not find libpng headers.\n") +endif() -INCLUDE_DIRECTORIES("${PNG_INCLUDE_DIR}") +include_directories("${PNG_INCLUDE_DIR}") -FIND_LIBRARY( - PNG_LIBRARY_REL NAMES png libpng.lib - PATHS /usr/local/lib /usr/lib - HINTS "${png_dir_1}/stage/lib" "${png_dir_2}/stage/lib" - DOC "Path to png library." +find_library( + PNG_LIBRARY_REL NAMES png libpng.lib + PATHS /usr/local/lib /usr/lib + HINTS "${png_dirs1}/stage/lib" "${png_dirs2}/stage/lib" + DOC "Path to png library." ) -FIND_LIBRARY( - PNG_LIBRARY_DEB NAMES pngd libpngd.lib - PATHS /usr/local/lib /usr/lib - HINTS "${png_dir_1}/stage/lib" "${png_dir_2}/stage/lib" - DOC "Path to png library." +find_library( + PNG_LIBRARY_DEB NAMES pngd libpngd.lib + PATHS /usr/local/lib /usr/lib + HINTS "${png_dirs1}/stage/lib" "${png_dirs2}/stage/lib" + DOC "Path to png library." ) -IF (PNG_LIBRARY_DEB) - SET(PNG_LIBRARY optimized ${PNG_LIBRARY_REL} debug ${PNG_LIBRARY_DEB}) -ELSE () - SET(PNG_LIBRARY ${PNG_LIBRARY_REL}) -ENDIF () +if (PNG_LIBRARY_REL AND NOT PNG_LIBRARY_DEB) + set(PNG_LIBRARY_DEB ${PNG_LIBRARY_REL}) +elseif (NOT PNG_LIBRARY_REL) + message(FATAL_ERROR "Could not find png library.\n") +endif() -IF (NOT PNG_LIBRARY) - MESSAGE( - FATAL_ERROR - "Could not find libpng library.\n" - ) -ENDIF () +set(PNG_LIBRARY optimized "${PNG_LIBRARY_REL}" debug "${PNG_LIBRARY_DEB}") #=================================== TIFF ===================================# -FIND_PATH( - TIFF_INCLUDE_DIR tiff.h - PATHS /usr/local/include /usr/include - HINTS ${tiff_dir_}/libtiff - PATH_SUFFIXES libtiff - DOC "Path to libtiff headers." +find_path( + TIFF_INCLUDE_DIR tiff.h + PATHS /usr/local/include /usr/include + HINTS ${tiff_dirs}/libtiff + PATH_SUFFIXES libtiff + DOC "Path to libtiff headers." ) -IF (NOT TIFF_INCLUDE_DIR) - MESSAGE( - FATAL_ERROR - "Could not find libtiff headers.\n" - ) -ENDIF () - -INCLUDE_DIRECTORIES("${TIFF_INCLUDE_DIR}") - -FIND_LIBRARY( - TIFF_LIBRARY_REL tiff libtiff.lib - PATHS /usr/local/lib /usr/lib - HINTS "${tiff_dir_}/stage/lib" - PATH_SUFFIXES libtiff - DOC "Path to tiff library." +if (NOT TIFF_INCLUDE_DIR) + message(FATAL_ERROR "Could not find libtiff headers.\n") +endif() + +include_directories("${TIFF_INCLUDE_DIR}") + +find_library( + TIFF_LIBRARY_REL tiff libtiff.lib + PATHS /usr/local/lib /usr/lib + HINTS "${tiff_dirs}/stage/lib" + PATH_SUFFIXES libtiff + DOC "Path to tiff library." ) -FIND_LIBRARY( - TIFF_LIBRARY_DEB tiffd libtiffd.lib - PATHS /usr/local/lib /usr/lib - HINTS "${tiff_dir_}/stage/lib" - PATH_SUFFIXES libtiff - DOC "Path to tiff library." +find_library( + TIFF_LIBRARY_DEB tiffd libtiffd.lib + PATHS /usr/local/lib /usr/lib + HINTS "${tiff_dirs}/stage/lib" + PATH_SUFFIXES libtiff + DOC "Path to tiff library." ) -IF (TIFF_LIBRARY_DEB) - SET(TIFF_LIBRARY optimized ${TIFF_LIBRARY_REL} debug ${TIFF_LIBRARY_DEB}) -ELSE () - SET(TIFF_LIBRARY ${TIFF_LIBRARY_REL}) -ENDIF () +if (TIFF_LIBRARY_REL AND NOT TIFF_LIBRARY_DEB) + set(TIFF_LIBRARY_DEB ${TIFF_LIBRARY_REL}) +elseif (NOT TIFF_LIBRARY_REL) + message(FATAL_ERROR "Could not find libtiff library.\n") +endif() -IF (NOT TIFF_LIBRARY) - MESSAGE( - FATAL_ERROR - "Could not find libtiff library.\n" - ) -ENDIF () +set(TIFF_LIBRARY optimized "${TIFF_LIBRARY_REL}" debug "${TIFF_LIBRARY_DEB}") -IF (WIN32) - ADD_DEFINITIONS(-DUSE_LIBTIFF_DLL) -ENDIF () +if (WIN32) + add_definitions(-DUSE_LIBTIFF_DLL) +endif() #================================= Boost ================================# -IF (WIN32) - FIND_PATH( - BOOST_ROOT boost-build.jam PATHS ${boost_dir_} - DOC "Path to top-level Boost source directory." - ) - SET(Boost_USE_STATIC_LIBS ON) -ELSE (WIN32) - ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) -ENDIF (WIN32) -SET(Boost_USE_MULTITHREADED ON) - -FIND_PACKAGE(Boost 1.60 COMPONENTS unit_test_framework prg_exec_monitor REQUIRED) -IF (NOT Boost_FOUND) - MESSAGE( - FATAL_ERROR - "Could not find boost headers or libraries.\n" - ) -ENDIF (NOT Boost_FOUND) +if (WIN32) + find_path( + BOOST_ROOT boost-build.jam PATHS ${boost_dirs} + DOC "Path to top-level Boost source directory." + ) + set(Boost_USE_STATIC_LIBS ON) +else() + add_definitions(-DBOOST_TEST_DYN_LINK) +endif() +set(Boost_USE_MULTITHREADED ON) -INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) +find_package(Boost 1.60 REQUIRED COMPONENTS unit_test_framework prg_exec_monitor) -#=================================== Qt ===================================# +include_directories(${Boost_INCLUDE_DIRS}) +link_directories(${Boost_LIBRARY_DIRS}) -IF (WIN32) - SET(Qt5Core_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5Core") - SET(Qt5Gui_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5Gui") - SET(Qt5Widgets_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5Widgets") - SET(Qt5Xml_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5Xml") - SET(Qt5Network_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5Network") - SET(Qt5OpenGL_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5OpenGL") - SET(Qt5LinguistTools_DIR "${qt_dir_}/qttools/lib/cmake/Qt5LinguistTools") - IF (WIN_XP) - SET(Qt5LinguistTools_DIR "${qt_dir_}/qtbase/lib/cmake/Qt5LinguistTools") - ENDIF (WIN_XP) -ENDIF (WIN32) - -SET(qt_min_version 5.6) -FIND_PACKAGE(Qt5Core ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5Gui ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5Widgets ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5Xml ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5Network ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5LinguistTools ${qt_min_version} REQUIRED) -FIND_PACKAGE(Qt5OpenGL ${qt_min_version} REQUIRED) - -INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5Core_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5GUI_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5GUI_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDE_DIRS}) -ADD_DEFINITIONS(${Qt5Widgets_DEFINITIONS}) -LINK_DIRECTORIES(${Qt5Widgets_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5Xml_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5Xml_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5Network_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5Network_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5OpenGL_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5OpenGL_LIBRARIES}) -INCLUDE_DIRECTORIES(${Qt5LinguistTools_INCLUDE_DIRS}) -LINK_DIRECTORIES(${Qt5LinguistTools_LIBRARIES}) - -#=================================== OpenCV ===================================# - - -# find_package(OpenCV PATHS opencv_dir_ REQUIRED) -# IF (NOT OpenCV_FOUND) -# MESSAGE( -# FATAL_ERROR -# "Could not find OpenCV headers or libraries.\n" -# ) -# ENDIF () -# -# INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) -# LINK_DIRECTORIES(${OpenCV_LIBRARIES}) +#=================================== Qt ===================================# +if (WIN32) + set(Qt5Core_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5Core") + set(Qt5Gui_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5Gui") + set(Qt5Widgets_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5Widgets") + set(Qt5Xml_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5Xml") + set(Qt5Network_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5Network") + set(Qt5OpenGL_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5OpenGL") + set(Qt5LinguistTools_DIR "${qt_dirs}/qttools/lib/cmake/Qt5LinguistTools") + if (WIN_XP) + set(Qt5LinguistTools_DIR "${qt_dirs}/qtbase/lib/cmake/Qt5LinguistTools") + endif() +endif() + +set(qt_min_version 5.6) +find_package(Qt5Core ${qt_min_version} REQUIRED) +find_package(Qt5Gui ${qt_min_version} REQUIRED) +find_package(Qt5Widgets ${qt_min_version} REQUIRED) +find_package(Qt5Xml ${qt_min_version} REQUIRED) +find_package(Qt5Network ${qt_min_version} REQUIRED) +find_package(Qt5LinguistTools ${qt_min_version} REQUIRED) +find_package(Qt5OpenGL ${qt_min_version} REQUIRED) + +include_directories(${Qt5Core_INCLUDE_DIRS}) +link_directories(${Qt5Core_LIBRARIES}) +include_directories(${Qt5GUI_INCLUDE_DIRS}) +link_directories(${Qt5GUI_LIBRARIES}) +include_directories(${Qt5Widgets_INCLUDE_DIRS}) +add_definitions(${Qt5Widgets_DEFINITIONS}) +link_directories(${Qt5Widgets_LIBRARIES}) +include_directories(${Qt5Xml_INCLUDE_DIRS}) +link_directories(${Qt5Xml_LIBRARIES}) +include_directories(${Qt5Network_INCLUDE_DIRS}) +link_directories(${Qt5Network_LIBRARIES}) +include_directories(${Qt5OpenGL_INCLUDE_DIRS}) +link_directories(${Qt5OpenGL_LIBRARIES}) +include_directories(${Qt5LinguistTools_INCLUDE_DIRS}) +link_directories(${Qt5LinguistTools_LIBRARIES}) #=================================== Main ===================================# -SET(EXTRA_LIBS "") +set(EXTRA_LIBS "") -IF (UNIX) - FindPthreads() - IF (PTHREADS_FOUND) - ADD_DEFINITIONS(${PTHREADS_CFLAGS}) - LINK_LIBRARIES(${PTHREADS_LIBS}) - ELSE (PTHREADS_FOUND) - MESSAGE( - FATAL_ERROR - "Could not detect threading flags.\n" - "Try specifying them manually in PTHREADS_CFLAGS and PTHREADS_LIBS." - ) - ENDIF (PTHREADS_FOUND) -ELSEIF (WIN32 AND MSVC) - ADD_DEFINITIONS(-DNOMINMAX) -ENDIF (UNIX) +if (UNIX) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + link_libraries(Threads::Threads) +elseif (WIN32 AND MSVC) + add_definitions(-DNOMINMAX) +endif() -ADD_DEFINITIONS(-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION) +add_definitions(-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION) -IF (WIN32) - LIST(APPEND EXTRA_LIBS winmm imm32 ws2_32 ole32 oleaut32 uuid gdi32 comdlg32 winspool) -ENDIF () +if (WIN32) + list(APPEND EXTRA_LIBS winmm imm32 ws2_32 ole32 oleaut32 uuid gdi32 comdlg32 winspool) +endif() -LIST(APPEND EXTRA_LIBS ${TIFF_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} ${JPEG_LIBRARY}) +list(APPEND EXTRA_LIBS ${TIFF_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} ${JPEG_LIBRARY}) # Prepare config.h -IF (WIN32) - SET(TRANSLATION_DIRS ".:translations") - SET(PLUGIN_DIRS ".") -ELSE () - SET(TRANSLATION_DIRS ".:${CMAKE_INSTALL_PREFIX}/share/scantailor/translations") - SET(PLUGIN_DIRS ".:${CMAKE_INSTALL_PREFIX}/lib/scantailor") -ENDIF () - -CONFIGURE_FILE(config.h.in ${CMAKE_BINARY_DIR}/config.h @ONLY) - -ADD_SUBDIRECTORY(dewarping) -ADD_SUBDIRECTORY(foundation) -ADD_SUBDIRECTORY(math) -ADD_SUBDIRECTORY(imageproc) -ADD_SUBDIRECTORY(interaction) -ADD_SUBDIRECTORY(zones) -ADD_SUBDIRECTORY(tests) - -FILE(GLOB common_ui_files ui/ErrorWidget.ui) -FILE(GLOB gui_only_ui_files "ui/*.ui") -FOREACH (ui_file ${common_ui_files}) - LIST(REMOVE_ITEM gui_only_ui_files "${ui_file}") -ENDFOREACH () - -SOURCE_GROUP("UI Files" FILES ${common_ui_files} ${gui_only_ui_files}) -QT5_WRAP_UI(common_ui_sources ${common_ui_files}) -QT5_WRAP_UI(gui_only_ui_sources ${gui_only_ui_files}) -SET_SOURCE_FILES_PROPERTIES(${common_ui_sources} ${gui_only_ui_files} PROPERTIES GENERATED TRUE) -ADD_SUBDIRECTORY(ui) -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") # for ui files - -ADD_CUSTOM_TARGET(toplevel_ui_sources DEPENDS ${common_ui_sources} ${gui_only_ui_sources}) - -ADD_SUBDIRECTORY(filters/fix_orientation) -ADD_SUBDIRECTORY(filters/page_split) -ADD_SUBDIRECTORY(filters/deskew) -ADD_SUBDIRECTORY(filters/select_content) -ADD_SUBDIRECTORY(filters/page_layout) -ADD_SUBDIRECTORY(filters/output) - -SET(resource_files "resources/resources.qrc" "resources/DarkScheme.qrc" "resources/LightScheme.qrc") -SET(resource_sources) -FOREACH (resource_file ${resource_files}) - QT5_ADD_RESOURCES(resource_sources ${resource_file}) -ENDFOREACH () -SET_SOURCE_FILES_PROPERTIES(${resource_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("Generated" FILES ${common_ui_sources} ${gui_only_ui_sources} ${resource_sources}) -SOURCE_GROUP("Resources" FILES ${resource_files}) -IF (WIN32) - SOURCE_GROUP("Resources" FILES resources/win32/resources.rc) -ENDIF () - -SET( - common_sources - BackgroundExecutor.cpp BackgroundExecutor.h - PixmapRenderer.cpp PixmapRenderer.h - BubbleAnimation.cpp BubbleAnimation.h - ProcessingIndicationWidget.cpp ProcessingIndicationWidget.h - NonOwningWidget.cpp NonOwningWidget.h - Dpi.cpp Dpi.h Dpm.cpp Dpm.h - SmartFilenameOrdering.cpp SmartFilenameOrdering.h - AbstractRelinker.h - RelinkablePath.cpp RelinkablePath.h - ImageInfo.cpp ImageInfo.h - ImageFileInfo.cpp ImageFileInfo.h - ImageMetadata.cpp ImageMetadata.h - RecentProjects.cpp RecentProjects.h - OutOfMemoryHandler.cpp OutOfMemoryHandler.h - CommandLine.cpp CommandLine.h - PageSelectionAccessor.cpp PageSelectionAccessor.h - PageSelectionProvider.h - ContentSpanFinder.cpp ContentSpanFinder.h - ImageTransformation.cpp ImageTransformation.h - ImagePixmapUnion.h - ImageViewBase.cpp ImageViewBase.h - BasicImageView.cpp BasicImageView.h - StageListView.cpp StageListView.h - DebugImageView.cpp DebugImageView.h - TabbedDebugImages.cpp TabbedDebugImages.h - ThumbnailLoadResult.h - ThumbnailPixmapCache.cpp ThumbnailPixmapCache.h - ThumbnailBase.cpp ThumbnailBase.h - ThumbnailFactory.cpp ThumbnailFactory.h - IncompleteThumbnail.cpp IncompleteThumbnail.h - ContentBoxPropagator.cpp ContentBoxPropagator.h - PageOrientationPropagator.cpp PageOrientationPropagator.h - DebugImages.cpp DebugImages.h - ImageId.cpp ImageId.h - PageId.cpp PageId.h - PageInfo.cpp PageInfo.h - BackgroundTask.cpp BackgroundTask.h - ProcessingTaskQueue.cpp ProcessingTaskQueue.h - PageSequence.cpp PageSequence.h - StageSequence.cpp StageSequence.h - ProjectPages.cpp ProjectPages.h - FilterData.cpp FilterData.h - ImageMetadataLoader.cpp ImageMetadataLoader.h - TiffReader.cpp TiffReader.h - TiffWriter.cpp TiffWriter.h - PngMetadataLoader.cpp PngMetadataLoader.h - TiffMetadataLoader.cpp TiffMetadataLoader.h - JpegMetadataLoader.cpp JpegMetadataLoader.h - ImageLoader.cpp ImageLoader.h - ErrorWidget.cpp ErrorWidget.h - OrthogonalRotation.cpp OrthogonalRotation.h - WorkerThreadPool.cpp WorkerThreadPool.h - LoadFileTask.cpp LoadFileTask.h - FilterOptionsWidget.cpp FilterOptionsWidget.h - TaskStatus.h FilterUiInterface.h - ProjectReader.cpp ProjectReader.h - ProjectWriter.cpp ProjectWriter.h - XmlMarshaller.cpp XmlMarshaller.h - XmlUnmarshaller.cpp XmlUnmarshaller.h - AtomicFileOverwriter.cpp AtomicFileOverwriter.h - EstimateBackground.cpp EstimateBackground.h - Despeckle.cpp Despeckle.h - ThreadPriority.cpp ThreadPriority.h - FileNameDisambiguator.cpp FileNameDisambiguator.h - OpenGLSupport.cpp OpenGLSupport.h - OutputFileNameGenerator.cpp OutputFileNameGenerator.h - ColorScheme.h - DarkScheme.cpp DarkScheme.h - LightScheme.cpp LightScheme.h - ColorSchemeManager.cpp ColorSchemeManager.h - PageRange.cpp PageRange.h - SelectedPage.cpp SelectedPage.h - Utils.cpp Utils.h - PageView.h - AutoManualMode.h - AbstractCommand.h - AbstractFilter.h - BeforeOrAfter.h - FilterResult.h - CompositeCacheDrivenTask.h - Margins.h - ChangedStateItemDelegate.h - PageOrderProvider.h - PageOrderOption.h - PayloadEvent.h - filter_dc/AbstractFilterDataCollector.h - filter_dc/ThumbnailCollector.h - filter_dc/ContentBoxCollector.h - filter_dc/PageOrientationCollector.h - ImageViewInfoProvider.cpp ImageViewInfoProvider.h - ImageViewInfoObserver.h - UnitsProvider.cpp UnitsProvider.h - UnitsObserver.h UnitsObserver.cpp - UnitsConverter.cpp UnitsConverter.h - Units.cpp Units.h - DefaultParams.cpp DefaultParams.h - DefaultParamsProfileManager.cpp DefaultParamsProfileManager.h - DefaultParamsProvider.cpp DefaultParamsProvider.h - DeviationProvider.h - OrderByDeviationProvider.cpp OrderByDeviationProvider.h - ImageSettings.cpp ImageSettings.h - version.h - config.h.in - ${common_ui_files}) - -SET( - gui_only_sources - Application.cpp Application.h - SkinnedButton.cpp SkinnedButton.h - RelinkablePathVisualization.cpp RelinkablePathVisualization.h - RelinkingModel.cpp RelinkingModel.h - RelinkingSortingModel.cpp RelinkingSortingModel.h - RelinkingListView.cpp RelinkingListView.h - RelinkingDialog.cpp RelinkingDialog.h - SettingsDialog.cpp SettingsDialog.h - FixDpiDialog.cpp FixDpiDialog.h - LoadFilesStatusDialog.cpp LoadFilesStatusDialog.h - ProjectCreationContext.cpp ProjectCreationContext.h - ProjectOpeningContext.cpp ProjectOpeningContext.h - OutOfMemoryDialog.cpp OutOfMemoryDialog.h - ThumbnailSequence.cpp ThumbnailSequence.h - ProjectFilesDialog.cpp ProjectFilesDialog.h - NewOpenProjectPanel.cpp NewOpenProjectPanel.h - SystemLoadWidget.cpp SystemLoadWidget.h - MainWindow.cpp MainWindow.h - main.cpp - StatusBarPanel.cpp StatusBarPanel.h - DefaultParamsDialog.cpp DefaultParamsDialog.h - CollapsibleGroupBox.cpp CollapsibleGroupBox.h) - -SET( - cli_only_sources - ConsoleBatch.cpp ConsoleBatch.h - main-cli.cpp -) - -SOURCE_GROUP("Sources" FILES ${common_sources} ${gui_only_sources} ${cli_only_sources}) +set(PORTABLE_VERSION TRUE CACHE BOOLEAN "Whether to build the portable version or not.") +if (PORTABLE_VERSION) + set(PORTABLE_CONFIG_DIR "config") +endif() +set(APPLICATION_NAME "scantailor-advanced") +if (WIN32) + set(TRANSLATION_DIRS ".:translations") + set(PLUGIN_DIRS ".") +else() + set(TRANSLATION_DIRS ".:translations:../share/${APPLICATION_NAME}/translations") + set(PLUGIN_DIRS ".:../lib/${APPLICATION_NAME}") +endif() + +configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h @ONLY) + +add_subdirectory(dewarping) +add_subdirectory(foundation) +add_subdirectory(math) +add_subdirectory(imageproc) +add_subdirectory(interaction) +add_subdirectory(zones) +add_subdirectory(tests) + +file(GLOB common_ui_files ui/ErrorWidget.ui) +file(GLOB gui_only_ui_files "ui/*.ui") +foreach (ui_file ${common_ui_files}) + list(REMOVE_ITEM gui_only_ui_files "${ui_file}") +endforeach() + +source_group("UI Files" FILES ${common_ui_files} ${gui_only_ui_files}) +qt5_wrap_ui(common_ui_sources ${common_ui_files}) +qt5_wrap_ui(gui_only_ui_sources ${gui_only_ui_files}) +set_source_files_properties(${common_ui_sources} ${gui_only_ui_files} PROPERTIES GENERATED TRUE) +add_subdirectory(ui) +include_directories("${CMAKE_CURRENT_BINARY_DIR}") # for ui files + +add_custom_target(toplevel_ui_sources DEPENDS ${common_ui_sources} ${gui_only_ui_sources}) + +add_subdirectory(filters/fix_orientation) +add_subdirectory(filters/page_split) +add_subdirectory(filters/deskew) +add_subdirectory(filters/select_content) +add_subdirectory(filters/page_layout) +add_subdirectory(filters/output) + +set(resource_files "resources/resources.qrc" "resources/DarkScheme.qrc" "resources/LightScheme.qrc") +set(resource_sources) +foreach (resource_file ${resource_files}) + qt5_add_resources(resource_sources ${resource_file}) +endforeach() +set_source_files_properties(${resource_sources} PROPERTIES GENERATED TRUE) +source_group("Generated" FILES ${common_ui_sources} ${gui_only_ui_sources} ${resource_sources}) +source_group("Resources" FILES ${resource_files}) +if (WIN32) + source_group("Resources" FILES resources/win32/resources.rc) +endif() + +set(common_sources + BackgroundExecutor.cpp BackgroundExecutor.h + PixmapRenderer.cpp PixmapRenderer.h + BubbleAnimation.cpp BubbleAnimation.h + ProcessingIndicationWidget.cpp ProcessingIndicationWidget.h + NonOwningWidget.cpp NonOwningWidget.h + Dpi.cpp Dpi.h Dpm.cpp Dpm.h + SmartFilenameOrdering.cpp SmartFilenameOrdering.h + AbstractRelinker.h + RelinkablePath.cpp RelinkablePath.h + ImageInfo.cpp ImageInfo.h + ImageFileInfo.cpp ImageFileInfo.h + ImageMetadata.cpp ImageMetadata.h + RecentProjects.cpp RecentProjects.h + OutOfMemoryHandler.cpp OutOfMemoryHandler.h + CommandLine.cpp CommandLine.h + PageSelectionAccessor.cpp PageSelectionAccessor.h + PageSelectionProvider.h + ContentSpanFinder.cpp ContentSpanFinder.h + ImageTransformation.cpp ImageTransformation.h + ImagePixmapUnion.h + ImageViewBase.cpp ImageViewBase.h + BasicImageView.cpp BasicImageView.h + StageListView.cpp StageListView.h + DebugImageView.cpp DebugImageView.h + TabbedDebugImages.cpp TabbedDebugImages.h + ThumbnailLoadResult.h + ThumbnailPixmapCache.cpp ThumbnailPixmapCache.h + ThumbnailBase.cpp ThumbnailBase.h + ThumbnailFactory.cpp ThumbnailFactory.h + IncompleteThumbnail.cpp IncompleteThumbnail.h + ContentBoxPropagator.cpp ContentBoxPropagator.h + PageOrientationPropagator.cpp PageOrientationPropagator.h + DebugImages.cpp DebugImages.h + ImageId.cpp ImageId.h + PageId.cpp PageId.h + PageInfo.cpp PageInfo.h + BackgroundTask.cpp BackgroundTask.h + ProcessingTaskQueue.cpp ProcessingTaskQueue.h + PageSequence.cpp PageSequence.h + StageSequence.cpp StageSequence.h + ProjectPages.cpp ProjectPages.h + FilterData.cpp FilterData.h + ImageMetadataLoader.cpp ImageMetadataLoader.h + TiffReader.cpp TiffReader.h + TiffWriter.cpp TiffWriter.h + PngMetadataLoader.cpp PngMetadataLoader.h + TiffMetadataLoader.cpp TiffMetadataLoader.h + JpegMetadataLoader.cpp JpegMetadataLoader.h + ImageLoader.cpp ImageLoader.h + ErrorWidget.cpp ErrorWidget.h + OrthogonalRotation.cpp OrthogonalRotation.h + WorkerThreadPool.cpp WorkerThreadPool.h + LoadFileTask.cpp LoadFileTask.h + FilterOptionsWidget.cpp FilterOptionsWidget.h + TaskStatus.h FilterUiInterface.h + ProjectReader.cpp ProjectReader.h + ProjectWriter.cpp ProjectWriter.h + XmlMarshaller.cpp XmlMarshaller.h + XmlUnmarshaller.cpp XmlUnmarshaller.h + AtomicFileOverwriter.cpp AtomicFileOverwriter.h + EstimateBackground.cpp EstimateBackground.h + Despeckle.cpp Despeckle.h + ThreadPriority.cpp ThreadPriority.h + FileNameDisambiguator.cpp FileNameDisambiguator.h + OpenGLSupport.cpp OpenGLSupport.h + OutputFileNameGenerator.cpp OutputFileNameGenerator.h + ColorScheme.h + DarkScheme.cpp DarkScheme.h + LightScheme.cpp LightScheme.h + NativeScheme.cpp NativeScheme.h + ColorSchemeManager.cpp ColorSchemeManager.h + PageRange.cpp PageRange.h + SelectedPage.cpp SelectedPage.h + Utils.cpp Utils.h + PageView.h + AutoManualMode.cpp AutoManualMode.h + AbstractCommand.h + AbstractFilter.h + BeforeOrAfter.h + FilterResult.h + CompositeCacheDrivenTask.h + Margins.h + ChangedStateItemDelegate.h + PageOrderProvider.h + PageOrderOption.h + PayloadEvent.h + filter_dc/AbstractFilterDataCollector.h + filter_dc/ThumbnailCollector.h + filter_dc/ContentBoxCollector.h + filter_dc/PageOrientationCollector.h + ImageViewInfoProvider.cpp ImageViewInfoProvider.h + ImageViewInfoObserver.h + UnitsProvider.cpp UnitsProvider.h + UnitsObserver.h UnitsObserver.cpp + UnitsConverter.cpp UnitsConverter.h + Units.cpp Units.h + DefaultParams.cpp DefaultParams.h + DefaultParamsProfileManager.cpp DefaultParamsProfileManager.h + DefaultParamsProvider.cpp DefaultParamsProvider.h + DeviationProvider.h + OrderByDeviationProvider.cpp OrderByDeviationProvider.h + BlackOnWhiteEstimator.cpp BlackOnWhiteEstimator.h + ImageSettings.cpp ImageSettings.h + EmptyTaskStatus.h + OrderByCompleteness.cpp OrderByCompleteness.h + version.h + config.h.in + ${common_ui_files}) + +set(gui_only_sources + Application.cpp Application.h + SkinnedButton.cpp SkinnedButton.h + RelinkablePathVisualization.cpp RelinkablePathVisualization.h + RelinkingModel.cpp RelinkingModel.h + RelinkingSortingModel.cpp RelinkingSortingModel.h + RelinkingListView.cpp RelinkingListView.h + RelinkingDialog.cpp RelinkingDialog.h + SettingsDialog.cpp SettingsDialog.h + FixDpiDialog.cpp FixDpiDialog.h + LoadFilesStatusDialog.cpp LoadFilesStatusDialog.h + ProjectCreationContext.cpp ProjectCreationContext.h + ProjectOpeningContext.cpp ProjectOpeningContext.h + OutOfMemoryDialog.cpp OutOfMemoryDialog.h + ThumbnailSequence.cpp ThumbnailSequence.h + ProjectFilesDialog.cpp ProjectFilesDialog.h + NewOpenProjectPanel.cpp NewOpenProjectPanel.h + SystemLoadWidget.cpp SystemLoadWidget.h + MainWindow.cpp MainWindow.h + main.cpp + StatusBarPanel.cpp StatusBarPanel.h + DefaultParamsDialog.cpp DefaultParamsDialog.h + CollapsibleGroupBox.cpp CollapsibleGroupBox.h) + +set(cli_only_sources + ConsoleBatch.cpp ConsoleBatch.h + main-cli.cpp) + +source_group("Sources" FILES ${common_sources} ${gui_only_sources} ${cli_only_sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -IF (POLICY CMP0071) - cmake_policy(SET CMP0071 NEW) -ENDIF (POLICY CMP0071) +if (POLICY CMP0071) + cmake_policy(SET CMP0071 NEW) +endif() + +source_group("Special Headers" FILES version.h config.h.in) + +set(win32_resource_file) +if (WIN32) + set(rc_file "${CMAKE_SOURCE_DIR}/resources/win32/resources.rc") + file(GLOB win32_resources resources/win32/*.ico) + set_source_files_properties( + "${rc_file}" PROPERTIES + OBJECT_DEPENDS ${win32_resources} + ) + if (MINGW) + # CMake doesn't know how to process .rc files with MinGW. + set(win32_resource_file "${CMAKE_BINARY_DIR}/win32_resources.o") + add_custom_command( + OUTPUT "${win32_resource_file}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/resources/win32" + COMMAND windres -i "${rc_file}" -o "${win32_resource_file}" + MAIN_DEPENDENCY "${rc_file}" + DEPENDS ${win32_resources} + ) + else() + set(win32_resource_file "${rc_file}") + endif() +endif() -SOURCE_GROUP("Special Headers" FILES version.h config.h.in) +add_library(stcore STATIC ${common_sources} ${common_ui_sources}) -SET(win32_resource_file) -IF (WIN32) - SET(rc_file "${CMAKE_SOURCE_DIR}/resources/win32/resources.rc") - FILE(GLOB win32_resources resources/win32/*.ico) - SET_SOURCE_FILES_PROPERTIES( - "${rc_file}" PROPERTIES - OBJECT_DEPENDS ${win32_resources} - ) - IF (MINGW) - # CMake doesn't know how to process .rc files with MinGW. - SET(win32_resource_file "${CMAKE_BINARY_DIR}/win32_resources.o") - ADD_CUSTOM_COMMAND( - OUTPUT "${win32_resource_file}" - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/resources/win32" - COMMAND windres -i "${rc_file}" -o "${win32_resource_file}" - MAIN_DEPENDENCY "${rc_file}" - DEPENDS ${win32_resources} - ) - ELSE (MINGW) - SET(win32_resource_file "${rc_file}") - ENDIF (MINGW) -ENDIF (WIN32) - -ADD_LIBRARY(stcore STATIC ${common_sources} ${common_ui_sources}) - -ADD_EXECUTABLE( - scantailor WIN32 ${gui_only_sources} ${common_ui_sources} ${gui_only_ui_sources} - ${resource_sources} ${win32_resource_file} resources/icons/COPYING +add_executable( + scantailor WIN32 ${gui_only_sources} ${common_ui_sources} ${gui_only_ui_sources} + ${resource_sources} ${win32_resource_file} resources/icons/COPYING ) -#ADD_EXECUTABLE(scantailor-cli ${cli_only_sources} ${common_ui_sources}) - -TARGET_LINK_LIBRARIES( - scantailor - fix_orientation page_split deskew select_content page_layout output stcore - dewarping zones interaction imageproc math foundation - ${Qt5Core_LIBRARIES} ${Qt5GUI_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Network_LIBRARIES} - ${Qt5OpenGL_LIBRARIES} ${Qt5LinguistTools_LIBRARIES} ${EXTRA_LIBS} - # ${OpenCV_LIBRARIES} +target_link_libraries( + scantailor + fix_orientation page_split deskew select_content page_layout output stcore + dewarping zones interaction imageproc math foundation + ${Qt5Core_LIBRARIES} ${Qt5GUI_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Network_LIBRARIES} + ${Qt5OpenGL_LIBRARIES} ${Qt5LinguistTools_LIBRARIES} ${EXTRA_LIBS} ) -#TARGET_LINK_LIBRARIES( -# scantailor-cli -# fix_orientation page_split deskew select_content page_layout output -# stcore dewarping zones interaction imageproc math foundation -# ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Network_LIBRARIES} -# ${Qt5LinguistTools_LIBRARIES} ${EXTRA_LIBS} -# # ${OpenCV_LIBRARIES} -#) - -#INSTALL(TARGETS scantailor scantailor-cli RUNTIME DESTINATION bin) -INSTALL(TARGETS scantailor RUNTIME DESTINATION bin) + +if (WIN32) + install(TARGETS scantailor RUNTIME DESTINATION .) +else() + install(TARGETS scantailor RUNTIME DESTINATION bin) +endif() # Translations -TRANSLATION_SOURCES( - scantailor - ${common_sources} ${gui_only_sources} ${cli_only_sources} +translation_sources( + scantailor + ${common_sources} ${gui_only_sources} ${cli_only_sources} ) -FILE(GLOB TRANSLATION_FILES translations/scantailor_*.ts) +file(GLOB TRANSLATION_FILES translations/scantailor_*.ts) -FINALIZE_TRANSLATION_SET(scantailor ${TRANSLATION_FILES}) -UPDATE_TRANSLATIONS_TARGET(update_translations scantailor) +finalize_translation_set(scantailor ${TRANSLATION_FILES}) +update_translations_target(update_translations scantailor) -SET(ts_files ${TRANSLATION_FILES}) +set(ts_files ${TRANSLATION_FILES}) # Don't build *.qm files from *untranslated.ts -SET(FILTERED_TRANSLATION_FILES) -FOREACH (ts_file ${ts_files}) - IF ("${ts_file}" MATCHES ".*untranslated.ts") - # Just skip it. - ELSE ("${ts_file}" MATCHES ".*untranslated.ts") - LIST(APPEND FILTERED_TRANSLATION_FILES "${ts_file}") - ENDIF ("${ts_file}" MATCHES ".*untranslated.ts") -ENDFOREACH (ts_file) - -QT5_ADD_TRANSLATION(QM_FILES ${FILTERED_TRANSLATION_FILES}) -ADD_CUSTOM_TARGET(compile_translations ALL DEPENDS ${QM_FILES}) - -INSTALL(FILES ${QM_FILES} DESTINATION "share/scantailor/translations") - -IF (WIN32) - # Copy some DLLs to the staging dir. - - SET( - qt5_libs - ${Qt5Widgets_LIBRARIES} ${Qt5Gui_LIBRARIES} - ${Qt5Core_LIBRARIES} ${Qt5Xml_LIBRARIES} - ${Qt5Network_LIBRARIES} ${Qt5OpenGL_LIBRARIES} - ) - - FOREACH (target ${qt5_libs}) - GET_TARGET_PROPERTY(debug_loc "${target}" LOCATION_DEBUG) - GET_TARGET_PROPERTY(release_loc "${target}" LOCATION_RELEASE) - COPY_TO_BUILD_DIR("${debug_loc}" CONFIGURATIONS Debug) - COPY_TO_BUILD_DIR("${release_loc}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) - ENDFOREACH () - - # Qt's plugins. - SET(PLUGINS_DIR "${qt_dir_}/qtbase/plugins") - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/platforms/qwindows.dll" SUBDIR platforms - CONFIGURATIONS Release MinSizeRel RelWithDebInfo - ) - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/platforms/qwindowsd.dll" SUBDIR platforms - CONFIGURATIONS Debug - ) - - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/imageformats/qjpeg.dll" SUBDIR imageformats - CONFIGURATIONS Release MinSizeRel RelWithDebInfo - ) - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/imageformats/qjpegd.dll" SUBDIR imageformats - CONFIGURATIONS Debug - ) - - IF (EXISTS "${PLUGINS_DIR}/accessible/qtaccessiblewidgets.dll") - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/accessible/qtaccessiblewidgets.dll" SUBDIR accessible - CONFIGURATIONS Release MinSizeRel RelWithDebInfo - ) - ENDIF () - IF (EXISTS "${PLUGINS_DIR}/accessible/qtaccessiblewidgetsd.dll") - COPY_TO_BUILD_DIR( - "${PLUGINS_DIR}/accessible/qtaccessiblewidgetsd.dll" SUBDIR accessible - CONFIGURATIONS Debug - ) - ENDIF () - - FIND_FILE( - JPEG_LIBRARY_RT_REL NAMES jpeg libjpeg libjpeg.dll - HINTS "${jpeg_dir_}/bin" - DOC "Path to jpeg runtime library." - ) - FIND_FILE( - JPEG_LIBRARY_RT_DEB NAMES jpegd libjpegd libjpegd.dll - HINTS "${jpeg_dir_}/bin" - DOC "Path to jpeg runtime library." - ) - COPY_TO_BUILD_DIR("${JPEG_LIBRARY_RT_REL}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) - COPY_TO_BUILD_DIR("${JPEG_LIBRARY_RT_DEB}" CONFIGURATIONS Debug) - - FIND_FILE( - ZLIB_LIBRARY_RT_REL NAMES z libz libz.dll zdll.dll - HINTS "${zlib_dir_}/bin" - DOC "Path to zlib runtime library." - ) - FIND_FILE( - ZLIB_LIBRARY_RT_DEB NAMES zd libzd libzd.dll zdlld.dll - HINTS "${zlib_dir_}/bin" - DOC "Path to zlib runtime library." - ) - COPY_TO_BUILD_DIR("${ZLIB_LIBRARY_RT_REL}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) - COPY_TO_BUILD_DIR("${ZLIB_LIBRARY_RT_DEB}" CONFIGURATIONS Debug) - - FIND_FILE( - PNG_LIBRARY_RT_REL NAMES png libpng libpng.dll - HINTS "${png_dir_1}/bin" "${png_dir_2}/bin" - DOC "Path to png runtime library." - ) - FIND_FILE( - PNG_LIBRARY_RT_DEB NAMES pngd libpngd libpngd.dll - HINTS "${png_dir_1}/bin" "${png_dir_2}/bin" - DOC "Path to png runtime library." - ) - COPY_TO_BUILD_DIR("${PNG_LIBRARY_RT_REL}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) - COPY_TO_BUILD_DIR("${PNG_LIBRARY_RT_DEB}" CONFIGURATIONS Debug) - - FIND_FILE( - TIFF_LIBRARY_RT_REL NAMES tiff libtiff libtiff.dll - HINTS "${tiff_dir_}/bin" - DOC "Path to tiff runtime library." - ) - FIND_FILE( - TIFF_LIBRARY_RT_DEB NAMES tiffd libtiffd libtiffd.dll - HINTS "${tiff_dir_}/bin" - DOC "Path to tiff runtime library." - ) - COPY_TO_BUILD_DIR("${TIFF_LIBRARY_RT_REL}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) - COPY_TO_BUILD_DIR("${TIFF_LIBRARY_RT_DEB}" CONFIGURATIONS Debug) - - IF (MINGW) - GET_FILENAME_COMPONENT(mingw_path_ ${CMAKE_CXX_COMPILER} PATH) - COPY_TO_BUILD_DIR("${mingw_path_}/libgcc_s_seh-1.dll" "${mingw_path_}/libgcc_s_sjlj-1.dll" "${mingw_path_}/libgcc_s_dw2-1.dll" - "${mingw_path_}/libstdc++-6.dll" "${mingw_path_}/libwinpthread-1.dll") - ENDIF (MINGW) - - # Generate the target that will actually do the copying. - GENERATE_COPY_TO_BUILD_DIR_TARGET(copy_to_build_dir) -ENDIF (WIN32) +set(FILTERED_TRANSLATION_FILES) +foreach (ts_file ${ts_files}) + if ("${ts_file}" MATCHES ".*untranslated.ts") + # Just skip it. + else() + list(APPEND FILTERED_TRANSLATION_FILES "${ts_file}") + endif() +endforeach() + +qt5_add_translation(QM_FILES ${FILTERED_TRANSLATION_FILES}) +add_custom_target(compile_translations ALL DEPENDS ${QM_FILES}) + +if (WIN32) + install(FILES ${QM_FILES} DESTINATION translations) +else() + install(FILES ${QM_FILES} DESTINATION "share/${APPLICATION_NAME}/translations") +endif() + +if (WIN32) + macro (add_runtime_libs_to_install Configuration Libs) + set(configurations "${Configuration}") + if (${configurations} MATCHES "ALL") + set(configurations "DEBUG;RELEASE") + endif() + foreach (_config ${configurations}) + foreach (_lib ${Libs}) + if (EXISTS "${_lib}") + list(APPEND "ADDITIONAL_RUNTIME_LIBS_${_config}" "${_lib}") + endif() + endforeach() + endforeach() + endmacro() + + # Copy some DLLs to the staging dir. + set( + qt5_libs + ${Qt5Widgets_LIBRARIES} ${Qt5Gui_LIBRARIES} + ${Qt5Core_LIBRARIES} ${Qt5Xml_LIBRARIES} + ${Qt5Network_LIBRARIES} ${Qt5OpenGL_LIBRARIES} + ) + + foreach (target ${qt5_libs}) + get_target_property(debug_loc "${target}" LOCATION_DEBUG) + get_target_property(release_loc "${target}" LOCATION_RELEASE) + copy_to_build_dir("${debug_loc}" CONFIGURATIONS Debug) + copy_to_build_dir("${release_loc}" CONFIGURATIONS Release MinSizeRel RelWithDebInfo) + add_runtime_libs_to_install(DEBUG "${debug_loc}") + add_runtime_libs_to_install(RELEASE "${release_loc}") + endforeach() + + # Qt's plugins. + macro (set_release_and_debug_libs VarName ReleaseLib DebugLib) + set(${VarName}_RELEASE "${ReleaseLib}") + if (EXISTS "${DebugLib}") + set(${VarName}_DEBUG "${DebugLib}") + else() + set(${VarName}_DEBUG "${ReleaseLib}") + endif() + endmacro() + + set(PLUGINS_DIR "${qt_dirs}/qtbase/plugins") + + set_release_and_debug_libs(qwindows + "${PLUGINS_DIR}/platforms/qwindows.dll" + "${PLUGINS_DIR}/platforms/qwindowsd.dll") + copy_to_build_dir("${qwindows_RELEASE}" SUBDIR platforms CONFIGURATIONS Release MinSizeRel RelWithDebInfo) + copy_to_build_dir("${qwindows_DEBUG}" SUBDIR platforms CONFIGURATIONS Debug) + install(PROGRAMS "${qwindows_DEBUG}" CONFIGURATIONS Debug DESTINATION platforms) + install(PROGRAMS "${qwindows_RELEASE}" CONFIGURATIONS Release DESTINATION platforms) + + set_release_and_debug_libs(qjpeg "${PLUGINS_DIR}/imageformats/qjpeg.dll" "${PLUGINS_DIR}/imageformats/qjpegd.dll") + copy_to_build_dir("${qjpeg_RELEASE}" SUBDIR imageformats CONFIGURATIONS Release MinSizeRel RelWithDebInfo) + copy_to_build_dir("${qjpeg_DEBUG}" SUBDIR imageformats CONFIGURATIONS Debug) + install(PROGRAMS "${qjpeg_DEBUG}" CONFIGURATIONS Debug DESTINATION imageformats) + install(PROGRAMS "${qjpeg_RELEASE}" CONFIGURATIONS Release DESTINATION imageformats) + + set_release_and_debug_libs(qtaccessible + "${PLUGINS_DIR}/accessible/qtaccessiblewidgets.dll" + "${PLUGINS_DIR}/accessible/qtaccessiblewidgetsd.dll") + if (EXISTS "${qtaccessible_RELEASE}") + copy_to_build_dir("${qtaccessible_RELEASE}" SUBDIR accessible CONFIGURATIONS Release MinSizeRel RelWithDebInfo) + copy_to_build_dir("${qtaccessible_DEBUG}" SUBDIR accessible CONFIGURATIONS Debug) + install(PROGRAMS "${qtaccessible_DEBUG}" CONFIGURATIONS Debug DESTINATION accessible) + install(PROGRAMS "${qtaccessible_RELEASE}" CONFIGURATIONS Release DESTINATION accessible) + endif() + + # Copy image libs + find_file(JPEG_LIBRARY_RT_REL NAMES libjpeg.dll HINTS "${jpeg_dirs}/bin") + find_file(JPEG_LIBRARY_RT_DEB NAMES libjpegd.dll HINTS "${jpeg_dirs}/bin") + if (NOT JPEG_LIBRARY_RT_DEB) + set(JPEG_LIBRARY_RT_DEB "${JPEG_LIBRARY_RT_REL}") + endif() + + find_file(ZLIB_LIBRARY_RT_REL NAMES libz.dll zdll.dll HINTS "${zlib_dirs}/bin") + find_file(ZLIB_LIBRARY_RT_DEB NAMES libzd.dll zdlld.dll HINTS "${zlib_dirs}/bin") + if (NOT ZLIB_LIBRARY_RT_DEB) + set(ZLIB_LIBRARY_RT_DEB "${ZLIB_LIBRARY_RT_REL}") + endif() + + find_file(PNG_LIBRARY_RT_REL NAMES libpng.dll HINTS "${png_dirs1}/bin" "${png_dirs2}/bin") + find_file(PNG_LIBRARY_RT_DEB NAMES libpngd.dll HINTS "${png_dirs1}/bin" "${png_dirs2}/bin") + if (NOT PNG_LIBRARY_RT_DEB) + set(PNG_LIBRARY_RT_DEB "${PNG_LIBRARY_RT_REL}") + endif() + + find_file(TIFF_LIBRARY_RT_REL NAMES libtiff.dll HINTS "${tiff_dirs}/bin") + find_file(TIFF_LIBRARY_RT_DEB NAMES libtiffd.dll HINTS "${tiff_dirs}/bin") + if (NOT TIFF_LIBRARY_RT_DEB) + set(TIFF_LIBRARY_RT_DEB "${TIFF_LIBRARY_RT_REL}") + endif() + + copy_to_build_dir( + "${JPEG_LIBRARY_RT_REL};${ZLIB_LIBRARY_RT_REL};${TIFF_LIBRARY_RT_REL};${PNG_LIBRARY_RT_REL}" + CONFIGURATIONS Release MinSizeRel RelWithDebInfo + ) + copy_to_build_dir( + "${JPEG_LIBRARY_RT_DEB};${TIFF_LIBRARY_RT_DEB};${ZLIB_LIBRARY_RT_DEB};${PNG_LIBRARY_RT_DEB}" + CONFIGURATIONS Debug + ) + add_runtime_libs_to_install(DEBUG + "${JPEG_LIBRARY_RT_DEB};${TIFF_LIBRARY_RT_DEB};${ZLIB_LIBRARY_RT_DEB};${PNG_LIBRARY_RT_DEB}") + add_runtime_libs_to_install(RELEASE + "${JPEG_LIBRARY_RT_REL};${ZLIB_LIBRARY_RT_REL};${TIFF_LIBRARY_RT_REL};${PNG_LIBRARY_RT_REL}") + + if (MINGW) + get_filename_component(_mingw_path ${CMAKE_CXX_COMPILER} PATH) + file(GLOB libgcc_s "${_mingw_path}/libgcc_s_*.dll") + file(GLOB libstdcpp "${_mingw_path}/libstdc++*.dll") + file(GLOB libwinpthread "${_mingw_path}/libwinpthread*.dll") + copy_to_build_dir("${libgcc_s};${libstdcpp};${libwinpthread}") + add_runtime_libs_to_install(ALL "${libgcc_s};${libstdcpp};${libwinpthread}") + endif() + + # Generate the target that will actually do the copying. + generate_copy_to_build_dir_target(copy_to_build_dir) + + install(PROGRAMS ${ADDITIONAL_RUNTIME_LIBS_DEBUG} CONFIGURATIONS Debug DESTINATION .) + install(PROGRAMS ${ADDITIONAL_RUNTIME_LIBS_RELEASE} CONFIGURATIONS Release DESTINATION .) +endif() + +if (UNIX) + install(FILES "${CMAKE_SOURCE_DIR}/resources/unix/scantailor.desktop" DESTINATION "share/applications") + install(FILES "${CMAKE_SOURCE_DIR}/resources/appicon.svg" + DESTINATION "share/icons/hicolor/scalable/apps" + RENAME "ScanTailor.svg") + install(FILES "${CMAKE_SOURCE_DIR}/resources/unix/mime/scantailor-project.xml" DESTINATION "share/mime/packages") +endif() + +# Packaging +if (WIN32) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ".") + if (MSVC) + set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE) + endif() +endif() +include(InstallRequiredSystemLibraries) + +set(CPACK_PACKAGE_NAME "${APPLICATION_NAME}") +string(REGEX REPLACE "(.*)\\..*\\..*" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${VERSION}") +string(REGEX REPLACE ".*\\.(.*)\\..*" "\\1" CPACK_PACKAGE_VERSION_MINOR "${VERSION}") +string(REGEX REPLACE ".*\\..*\\.(.*)" "\\1" CPACK_PACKAGE_VERSION_PATCH "${VERSION}") +set(CPACK_PACKAGE_VENDOR "4lex4 <4lex49@zoho.com>") +set(CPACK_PACKAGE_CONTACT "${CPACK_PACKAGE_VENDOR}") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Interactive post-processing tool for scanned pages.") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") +set(CPACK_PACKAGE_EXECUTABLES "scantailor;${CMAKE_PROJECT_NAME}") +set(CPACK_CREATE_DESKTOP_LINKS "scantailor") +if (WIN32) + set(CPACK_NSIS_INSTALLED_ICON_NAME "scantailor.exe") + set(CPACK_NSIS_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") + set(CPACK_NSIS_DISPLAY_NAME "${CMAKE_PROJECT_NAME} ${VERSION}") + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") + set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}") + set(CPACK_NSIS_CREATE_ICONS_EXTRA + "CreateShortCut \\\"$DESKTOP\\\\${CMAKE_PROJECT_NAME}.lnk\\\" \\\"$INSTDIR\\\\scantailor.exe\\\"" + ) + set(CPACK_NSIS_DELETE_ICONS_EXTRA + "Delete \\\"$DESKTOP\\\\${CMAKE_PROJECT_NAME}.lnk\\\"" + ) +endif() +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${APPLICATION_NAME}-${VERSION}") +set( + CPACK_SOURCE_IGNORE_FILES + "/\\\\.svn/" + "/\\\\.git/" + "~$" + "\\\\.pcs$" + "TODO.txt" + "CMakeLists.txt.user" + "/doxygen/" + "${CMAKE_BINARY_DIR}" +) +include(CPack) + +# uninstall target +if (NOT TARGET uninstall) + configure_file( + "${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" + @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake) +endif() \ No newline at end of file diff --git a/ChangedStateItemDelegate.h b/ChangedStateItemDelegate.h index aa7921317..8a95a3cd6 100644 --- a/ChangedStateItemDelegate.h +++ b/ChangedStateItemDelegate.h @@ -19,56 +19,51 @@ #ifndef CHANGEDSTATEITEMDELEGATE_H_ #define CHANGEDSTATEITEMDELEGATE_H_ -#include -#include #include +#include +#include /** * \brief A decoration of an existing item delegate * that forces certain item states. */ -template +template class ChangedStateItemDelegate : public T { -public: - explicit ChangedStateItemDelegate(QObject* parent = nullptr) : T(parent), m_changedFlags(), m_changedMask() { - } - - void flagsForceEnabled(QStyle::State flags) { - m_changedFlags |= flags; - m_changedMask |= flags; - } - - void flagsForceDisabled(QStyle::State flags) { - m_changedFlags &= ~flags; - m_changedMask |= flags; - } - - void removeChanges(QStyle::State remove) { - m_changedMask &= ~remove; - } - - void removeAllChanges() { - m_changedMask = QStyle::State(); - } - - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - const QStyle::State orig_state = option.state; - - const QStyle::State new_state = (orig_state & ~m_changedMask) | (m_changedFlags & m_changedMask); - - // Evil but necessary: the alternative solution of modifying - // a copy doesn't work, as option doesn't really point to - // QStyleOptionViewItem, but to one of its subclasses. - QStyleOptionViewItem& non_const_opt = const_cast(option); - - non_const_opt.state = new_state; - T::paint(painter, non_const_opt, index); - non_const_opt.state = orig_state; - } - -private: - QStyle::State m_changedFlags; - QStyle::State m_changedMask; + public: + explicit ChangedStateItemDelegate(QObject* parent = nullptr) : T(parent), m_changedFlags(), m_changedMask() {} + + void flagsForceEnabled(QStyle::State flags) { + m_changedFlags |= flags; + m_changedMask |= flags; + } + + void flagsForceDisabled(QStyle::State flags) { + m_changedFlags &= ~flags; + m_changedMask |= flags; + } + + void removeChanges(QStyle::State remove) { m_changedMask &= ~remove; } + + void removeAllChanges() { m_changedMask = QStyle::State(); } + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + const QStyle::State orig_state = option.state; + + const QStyle::State new_state = (orig_state & ~m_changedMask) | (m_changedFlags & m_changedMask); + + // Evil but necessary: the alternative solution of modifying + // a copy doesn't work, as option doesn't really point to + // QStyleOptionViewItem, but to one of its subclasses. + QStyleOptionViewItem& non_const_opt = const_cast(option); + + non_const_opt.state = new_state; + T::paint(painter, non_const_opt, index); + non_const_opt.state = orig_state; + } + + private: + QStyle::State m_changedFlags; + QStyle::State m_changedMask; }; diff --git a/CollapsibleGroupBox.cpp b/CollapsibleGroupBox.cpp index 006532481..51cddf0b7 100644 --- a/CollapsibleGroupBox.cpp +++ b/CollapsibleGroupBox.cpp @@ -1,210 +1,212 @@ #include "CollapsibleGroupBox.h" +#include #include #include #include -#include CollapsibleGroupBox::CollapsibleGroupBox(QWidget* parent) : QGroupBox(parent) { - initialize(); + initialize(); } CollapsibleGroupBox::CollapsibleGroupBox(const QString& title, QWidget* parent) : QGroupBox(title, parent) { - initialize(); + initialize(); } void CollapsibleGroupBox::initialize() { - collapseIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/minus-16.png"))); - expandIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/plus-16.png"))); - collapseButton = new QToolButton(this); - collapseButton->setObjectName("collapseButton"); - collapseButton->setAutoRaise(true); - collapseButton->setFixedSize(14, 14); - collapseButton->setIconSize(QSize(12, 12)); - collapseButton->setIcon(collapseIcon); - setFocusProxy(collapseButton); - setFocusPolicy(Qt::StrongFocus); - - connect(collapseButton, &QAbstractButton::clicked, this, &CollapsibleGroupBox::toggleCollapsed); - connect(this, &QGroupBox::toggled, this, &CollapsibleGroupBox::checkToggled); - connect(this, &QGroupBox::clicked, this, &CollapsibleGroupBox::checkClicked); + m_collapseIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/minus-16.png"))); + m_expandIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/plus-16.png"))); + m_collapseButton = new QToolButton(this); + m_collapseButton->setObjectName("collapseButton"); + m_collapseButton->setAutoRaise(true); + m_collapseButton->setFixedSize(14, 14); + m_collapseButton->setIconSize(QSize(12, 12)); + m_collapseButton->setIcon(m_collapseIcon); + setFocusProxy(m_collapseButton); + setFocusPolicy(Qt::StrongFocus); + + this->setAlignment(Qt::AlignCenter); + + connect(m_collapseButton, &QAbstractButton::clicked, this, &CollapsibleGroupBox::toggleCollapsed); + connect(this, &QGroupBox::toggled, this, &CollapsibleGroupBox::checkToggled); + connect(this, &QGroupBox::clicked, this, &CollapsibleGroupBox::checkClicked); } void CollapsibleGroupBox::setCollapsed(const bool collapse) { - const bool changed = (collapse != collapsed); + const bool changed = (collapse != m_collapsed); - if (changed) { - collapsed = collapse; - collapseButton->setIcon(collapse ? expandIcon : collapseIcon); + if (changed) { + m_collapsed = collapse; + m_collapseButton->setIcon(collapse ? m_expandIcon : m_collapseIcon); - updateWidgets(); + updateWidgets(); - emit collapsedStateChanged(isCollapsed()); - } + emit collapsedStateChanged(isCollapsed()); + } } bool CollapsibleGroupBox::isCollapsed() const { - return collapsed; + return m_collapsed; } void CollapsibleGroupBox::checkToggled(bool) { - collapseButton->setEnabled(true); + m_collapseButton->setEnabled(true); } void CollapsibleGroupBox::checkClicked(bool checked) { - if (checked && isCollapsed()) { - setCollapsed(false); - } else if (!checked && !isCollapsed()) { - setCollapsed(true); - } + if (checked && isCollapsed()) { + setCollapsed(false); + } else if (!checked && !isCollapsed()) { + setCollapsed(true); + } } void CollapsibleGroupBox::toggleCollapsed() { - // verify if sender is this group box's collapse button - auto* sender = dynamic_cast(QObject::sender()); - const bool isSenderCollapseButton = (sender && (sender == collapseButton)); + // verify if sender is this group box's collapse button + auto* sender = dynamic_cast(QObject::sender()); + const bool isSenderCollapseButton = (sender && (sender == m_collapseButton)); - if (isSenderCollapseButton) { - setCollapsed(!isCollapsed()); - } + if (isSenderCollapseButton) { + setCollapsed(!isCollapsed()); + } } void CollapsibleGroupBox::updateWidgets() { - const ScopedIncDec guard(ignoreVisibilityEvents); - - if (collapsed) { - for (QObject* child : children()) { - auto* widget = dynamic_cast(child); - if (widget && (widget != collapseButton) && widget->isVisible()) { - collapsedWidgets.insert(widget); - widget->hide(); - } - } - } else { - for (QObject* child : children()) { - auto* widget = dynamic_cast(child); - if (widget && (widget != collapseButton) && (collapsedWidgets.find(widget) != collapsedWidgets.end())) { - collapsedWidgets.erase(widget); - widget->show(); - } - } + const ScopedIncDec guard(m_ignoreVisibilityEvents); + + if (m_collapsed) { + for (QObject* child : children()) { + auto* widget = dynamic_cast(child); + if (widget && (widget != m_collapseButton) && widget->isVisible()) { + m_collapsedWidgets.insert(widget); + widget->hide(); + } } + } else { + for (QObject* child : children()) { + auto* widget = dynamic_cast(child); + if (widget && (widget != m_collapseButton) && (m_collapsedWidgets.find(widget) != m_collapsedWidgets.end())) { + m_collapsedWidgets.erase(widget); + widget->show(); + } + } + } } void CollapsibleGroupBox::showEvent(QShowEvent* event) { - // initialize widget on first show event only - if (shown) { - event->accept(); - return; - } - shown = true; + // initialize widget on first show event only + if (m_shown) { + event->accept(); + return; + } + m_shown = true; - loadState(); + loadState(); - QWidget::showEvent(event); + QWidget::showEvent(event); } void CollapsibleGroupBox::changeEvent(QEvent* event) { - QGroupBox::changeEvent(event); + QGroupBox::changeEvent(event); - if ((event->type() == QEvent::EnabledChange) && isEnabled()) { - collapseButton->setEnabled(true); - } + if ((event->type() == QEvent::EnabledChange) && isEnabled()) { + m_collapseButton->setEnabled(true); + } } void CollapsibleGroupBox::childEvent(QChildEvent* event) { - auto* childWidget = dynamic_cast(event->child()); - if (childWidget && (event)->type() == QEvent::ChildAdded) { - if (collapsed) { - if (childWidget->isVisible()) { - collapsedWidgets.insert(childWidget); - childWidget->hide(); - } - } - - childWidget->installEventFilter(this); + auto* childWidget = dynamic_cast(event->child()); + if (childWidget && (event->type() == QEvent::ChildAdded)) { + if (m_collapsed) { + if (childWidget->isVisible()) { + m_collapsedWidgets.insert(childWidget); + childWidget->hide(); + } } - QGroupBox::childEvent(event); + childWidget->installEventFilter(this); + } + + QGroupBox::childEvent(event); } bool CollapsibleGroupBox::eventFilter(QObject* watched, QEvent* event) { - if (collapsed && !ignoreVisibilityEvents) { - auto* childWidget = dynamic_cast(watched); - if (childWidget) { - if (event->type() == QEvent::ShowToParent) { - const ScopedIncDec guard(ignoreVisibilityEvents); - - collapsedWidgets.insert(childWidget); - childWidget->hide(); - } else if (event->type() == QEvent::HideToParent) { - collapsedWidgets.erase(childWidget); - } - } + if (m_collapsed && !m_ignoreVisibilityEvents) { + auto* childWidget = dynamic_cast(watched); + if (childWidget) { + if (event->type() == QEvent::ShowToParent) { + const ScopedIncDec guard(m_ignoreVisibilityEvents); + + m_collapsedWidgets.insert(childWidget); + childWidget->hide(); + } else if (event->type() == QEvent::HideToParent) { + m_collapsedWidgets.erase(childWidget); + } } + } - return QObject::eventFilter(watched, event); + return QObject::eventFilter(watched, event); } CollapsibleGroupBox::~CollapsibleGroupBox() { - saveState(); + saveState(); } void CollapsibleGroupBox::loadState() { - if (!isEnabled()) { - return; - } + if (!isEnabled()) { + return; + } - const QString key = getSettingsKey(); - if (key.isEmpty()) { - return; - } + const QString key = getSettingsKey(); + if (key.isEmpty()) { + return; + } - setUpdatesEnabled(false); + setUpdatesEnabled(false); - QSettings settings; + QSettings settings; - if (isCheckable()) { - QVariant val = settings.value(key + "/checked"); - if (!val.isNull()) { - setChecked(val.toBool()); - } + if (isCheckable()) { + QVariant val = settings.value(key + "/checked"); + if (!val.isNull()) { + setChecked(val.toBool()); } + } - { - QVariant val = settings.value(key + "/collapsed"); - if (!val.isNull()) { - setCollapsed(val.toBool()); - } + { + QVariant val = settings.value(key + "/collapsed"); + if (!val.isNull()) { + setCollapsed(val.toBool()); } + } - setUpdatesEnabled(true); + setUpdatesEnabled(true); } void CollapsibleGroupBox::saveState() { - if (!shown || !isEnabled()) { - return; - } + if (!m_shown || !isEnabled()) { + return; + } - const QString key = getSettingsKey(); - if (key.isEmpty()) { - return; - } + const QString key = getSettingsKey(); + if (key.isEmpty()) { + return; + } - QSettings settings; + QSettings settings; - if (isCheckable()) { - settings.setValue(key + "/checked", isChecked()); - } - settings.setValue(key + "/collapsed", isCollapsed()); + if (isCheckable()) { + settings.setValue(key + "/checked", isChecked()); + } + settings.setValue(key + "/collapsed", isCollapsed()); } QString CollapsibleGroupBox::getSettingsKey() const { - if (objectName().isEmpty()) { - return QString(); - } + if (objectName().isEmpty()) { + return QString(); + } - QString saveKey = '/' + objectName(); - saveKey = "CollapsibleGroupBox" + saveKey; - return saveKey; + QString saveKey = '/' + objectName(); + saveKey = "CollapsibleGroupBox" + saveKey; + return saveKey; } diff --git a/CollapsibleGroupBox.h b/CollapsibleGroupBox.h index 068793cfa..3fb59eb04 100644 --- a/CollapsibleGroupBox.h +++ b/CollapsibleGroupBox.h @@ -3,80 +3,80 @@ #define SCANTAILOR_COLLAPSIBLEGROUPBOX_H -#include #include +#include #include class CollapsibleGroupBox : public QGroupBox { - Q_OBJECT + Q_OBJECT - /** - * The collapsed state of this group box. If it is set to true, all content is hidden - * if it is set to false all content is shown. - */ - Q_PROPERTY(bool collapsed READ isCollapsed WRITE setCollapsed USER true) + /** + * The collapsed state of this group box. If it is set to true, all content is hidden + * if it is set to false all content is shown. + */ + Q_PROPERTY(bool collapsed READ isCollapsed WRITE setCollapsed USER true) -public: - explicit CollapsibleGroupBox(QWidget* parent = nullptr); + public: + explicit CollapsibleGroupBox(QWidget* parent = nullptr); - explicit CollapsibleGroupBox(const QString& title, QWidget* parent = nullptr); + explicit CollapsibleGroupBox(const QString& title, QWidget* parent = nullptr); - ~CollapsibleGroupBox() override; + ~CollapsibleGroupBox() override; - /** - * Returns the current collapsed state of this group box. - */ - bool isCollapsed() const; + /** + * Returns the current collapsed state of this group box. + */ + bool isCollapsed() const; - /** - * Collapse or expand this group box. - * - * \param collapse Will collapse on true and expand on false - */ - void setCollapsed(bool collapse); + /** + * Collapse or expand this group box. + * + * \param collapse Will collapse on true and expand on false + */ + void setCollapsed(bool collapse); -signals: + signals: - /** Signal emitted when the group box collapsed/expanded state is changed, and when first shown */ - void collapsedStateChanged(bool collapsed); + /** Signal emitted when the group box collapsed/expanded state is changed, and when first shown */ + void collapsedStateChanged(bool collapsed); -public slots: + public slots: - void checkToggled(bool); + void checkToggled(bool); - void checkClicked(bool checked); + void checkClicked(bool checked); - void toggleCollapsed(); + void toggleCollapsed(); -protected: - void updateWidgets(); + protected: + void updateWidgets(); - void showEvent(QShowEvent* event) override; + void showEvent(QShowEvent* event) override; - void changeEvent(QEvent* event) override; + void changeEvent(QEvent* event) override; - void childEvent(QChildEvent* event) override; + void childEvent(QChildEvent* event) override; - bool eventFilter(QObject* watched, QEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; - void initialize(); + void initialize(); - void loadState(); + void loadState(); - void saveState(); + void saveState(); - QString getSettingsKey() const; + QString getSettingsKey() const; -private: - bool collapsed = false; - bool shown = false; - QToolButton* collapseButton = nullptr; + private: + bool m_collapsed = false; + bool m_shown = false; + QToolButton* m_collapseButton = nullptr; - QIcon collapseIcon; - QIcon expandIcon; + QIcon m_collapseIcon; + QIcon m_expandIcon; - int ignoreVisibilityEvents = 0; - std::unordered_set collapsedWidgets; + int m_ignoreVisibilityEvents = 0; + std::unordered_set m_collapsedWidgets; }; diff --git a/ColorScheme.h b/ColorScheme.h index 90102e853..0edde61b4 100644 --- a/ColorScheme.h +++ b/ColorScheme.h @@ -2,38 +2,44 @@ #ifndef SCANTAILOR_COLORSCHEME_H #define SCANTAILOR_COLORSCHEME_H +#include #include #include -typedef std::unordered_map ColorParams; - class ColorScheme { -public: - virtual ~ColorScheme() = default; - - virtual std::unique_ptr getPalette() const = 0; - - virtual std::unique_ptr getStyleSheet() const = 0; - - /** - * List of colors for elements that don't support styling. - * - * Available parameters: - * thumbnail_sequence_selected_item_text, - * thumbnail_sequence_item_text, - * thumbnail_sequence_selected_item_background, - * relinkable_path_visualization_border_color, - * open_new_project_border_color, - * processing_indication_head_color, - * processing_indication_tail_color, - * processing_indication_fade_color, - * stage_list_head_color, - * stage_list_tail_color, - * fix_dpi_dialog_error_text_color - * - * @return the list of colors to override the default values by the color scheme - */ - virtual std::unique_ptr getColorParams() const = 0; + public: + virtual ~ColorScheme() = default; + + virtual QStyle* getStyle() const = 0; + + virtual QPalette getPalette() const = 0; + + virtual std::unique_ptr getStyleSheet() const = 0; + + enum ColorParam { + ThumbnailSequenceItemText, + ThumbnailSequenceSelectedItemText, + ThumbnailSequenceSelectedItemBackground, + ThumbnailSequenceSelectionLeaderText, + ThumbnailSequenceSelectionLeaderBackground, + RelinkablePathVisualizationBorder, + OpenNewProjectBorder, + ProcessingIndicationHeadColor, + ProcessingIndicationTail, + ProcessingIndicationFade, + StageListHead, + StageListTail, + FixDpiDialogErrorText + }; + + using ColorParams = std::unordered_map>; + + /** + * List of colors for elements that don't support styling. + * + * @return the list of colors to override the default values by the color scheme + */ + virtual ColorParams getColorParams() const = 0; }; #endif // SCANTAILOR_COLORSCHEME_H diff --git a/ColorSchemeManager.cpp b/ColorSchemeManager.cpp index 88949ebbc..1e8a1902b 100644 --- a/ColorSchemeManager.cpp +++ b/ColorSchemeManager.cpp @@ -1,36 +1,43 @@ -#include -#include -#include #include "ColorSchemeManager.h" +#include +#include -std::unique_ptr ColorSchemeManager::m_ptrInstance = nullptr; +std::unique_ptr ColorSchemeManager::m_instance = nullptr; ColorSchemeManager* ColorSchemeManager::instance() { - if (m_ptrInstance == nullptr) { - m_ptrInstance.reset(new ColorSchemeManager()); - } + if (m_instance == nullptr) { + m_instance.reset(new ColorSchemeManager()); + } - return m_ptrInstance.get(); + return m_instance.get(); } void ColorSchemeManager::setColorScheme(const ColorScheme& colorScheme) { - qApp->setStyle(QStyleFactory::create("Fusion")); - - qApp->setPalette(*colorScheme.getPalette()); - - std::unique_ptr styleSheet = colorScheme.getStyleSheet(); - if (styleSheet != nullptr) { - qApp->setStyleSheet(*styleSheet); - } + if (QStyle* style = colorScheme.getStyle()) { + qApp->setStyle(style); + } + qApp->setPalette(colorScheme.getPalette()); + if (std::unique_ptr styleSheet = colorScheme.getStyleSheet()) { + qApp->setStyleSheet(*styleSheet); + } + m_colorParams = colorScheme.getColorParams(); +} - m_ptrColorParams = colorScheme.getColorParams(); +QBrush ColorSchemeManager::getColorParam(const ColorScheme::ColorParam colorParam, const QBrush& defaultBrush) const { + const auto it = m_colorParams.find(colorParam); + if (it != m_colorParams.end()) { + return QBrush(it->second, defaultBrush.style()); + } else { + return defaultBrush; + } } -QBrush ColorSchemeManager::getColorParam(const std::string& colorParam, const QBrush& defaultColor) const { - if (m_ptrColorParams->find(colorParam) != m_ptrColorParams->end()) { - return m_ptrColorParams->at(colorParam); - } else { - return defaultColor; - } +QColor ColorSchemeManager::getColorParam(ColorScheme::ColorParam colorParam, const QColor& defaultColor) const { + const auto it = m_colorParams.find(colorParam); + if (it != m_colorParams.end()) { + return it->second; + } else { + return defaultColor; + } } diff --git a/ColorSchemeManager.h b/ColorSchemeManager.h index e99f1cb95..901997011 100644 --- a/ColorSchemeManager.h +++ b/ColorSchemeManager.h @@ -3,22 +3,28 @@ #define SCANTAILOR_COLORSCHEMEMANAGER_H +#include #include #include "ColorScheme.h" class ColorSchemeManager { -private: - static std::unique_ptr m_ptrInstance; - std::unique_ptr m_ptrColorParams; + DECLARE_NON_COPYABLE(ColorSchemeManager) + private: + ColorSchemeManager() = default; - ColorSchemeManager() = default; + public: + static ColorSchemeManager* instance(); -public: - static ColorSchemeManager* instance(); + void setColorScheme(const ColorScheme& colorScheme); - void setColorScheme(const ColorScheme& colorScheme); + QBrush getColorParam(ColorScheme::ColorParam colorParam, const QBrush& defaultBrush) const; - QBrush getColorParam(const std::string& colorParam, const QBrush& defaultColor) const; + QColor getColorParam(ColorScheme::ColorParam colorParam, const QColor& defaultColor) const; + + private: + static std::unique_ptr m_instance; + + ColorScheme::ColorParams m_colorParams; }; diff --git a/CommandLine.cpp b/CommandLine.cpp index 81251ed17..82867cdee 100644 --- a/CommandLine.cpp +++ b/CommandLine.cpp @@ -17,767 +17,765 @@ along with this program. If not, see . */ -#include +#include #include +#include #include -#include #include #include +#include "CommandLine.h" #include "Dpi.h" #include "ImageId.h" #include "version.h" -#include "CommandLine.h" CommandLine CommandLine::m_globalInstance; void CommandLine::set(const CommandLine& cl) { - assert(!m_globalInstance.isGlobal()); + assert(!m_globalInstance.isGlobal()); - m_globalInstance = cl; - m_globalInstance.setGlobal(); + m_globalInstance = cl; + m_globalInstance.setGlobal(); } bool CommandLine::parseCli(const QStringList& argv) { - QRegExp rx("^--([^=]+)=(.*)$"); - QRegExp rx_switch("^--([^=]+)$"); - QRegExp rx_short("^-([^=]+)=(.*)$"); - QRegExp rx_short_switch("^-([^=]+)$"); - QRegExp rx_project(".*\\.ScanTailor$", Qt::CaseInsensitive); - - QList opts; - opts << "help"; - opts << "verbose"; - opts << "layout"; - opts << "layout-direction"; - opts << "orientation"; - opts << "rotate"; - opts << "deskew"; - opts << "skew-deviation"; - opts << "disable-content-detection"; - opts << "enable-page-detection"; - opts << "enable-fine-tuning"; - opts << "force-disable-page-detection"; - opts << "content-detection"; - opts << "content-box"; - opts << "content-deviation"; - opts << "enable-auto-margins"; - opts << "margins"; - opts << "margins-left"; - opts << "margins-right"; - opts << "margins-top"; - opts << "margins-bottom"; - opts << "default-margins"; - opts << "default-margins-left"; - opts << "default-margins-right"; - opts << "default-margins-top"; - opts << "default-margins-bottom"; - opts << "match-layout"; - opts << "match-layout-tolerance"; - opts << "match-layout-default"; - opts << "alignment"; - opts << "alignment-vertical"; - opts << "alignment-horizontal"; - opts << "alignment-tolerance"; - opts << "dpi"; - opts << "output-dpi"; - opts << "dpi-x"; - opts << "dpi-y"; - opts << "output-dpi-x"; - opts << "output-dpi-y"; - opts << "color-mode"; - opts << "cut-margins"; - opts << "normalize-illumination"; - opts << "threshold"; - opts << "despeckle"; - opts << "dewarping"; - opts << "depth-perception"; - opts << "start-filter"; - opts << "end-filter"; - opts << "output-project"; - opts << "picture-shape"; - opts << "language"; - opts << "disable-content-text-mask"; - opts << "window-title"; - opts << "page-detection-box"; - opts << "page-detection-tolerance"; - opts << "page-borders"; - opts << "page-borders-left"; - opts << "page-borders-top"; - opts << "page-borders-right"; - opts << "page-borders-bottom"; - opts << "disable-check-output"; - opts << "default-output-dpi"; - opts << "default-color-mode"; - opts << "tiff-force-rgb"; - opts << "tiff-force-grayscale"; - opts << "tiff-force-keep-color-space"; - - QMap shortMap; - shortMap["h"] = "help"; - shortMap["help"] = "help"; - shortMap["v"] = "verbose"; - shortMap["l"] = "layout"; - shortMap["ld"] = "layout-direction"; - shortMap["o"] = "output-project"; - - // skip first argument (scantailor) - for (int i = 1; i < argv.size(); i++) { + QRegExp rx("^--([^=]+)=(.*)$"); + QRegExp rx_switch("^--([^=]+)$"); + QRegExp rx_short("^-([^=]+)=(.*)$"); + QRegExp rx_short_switch("^-([^=]+)$"); + QRegExp rx_project(".*\\.ScanTailor$", Qt::CaseInsensitive); + + QList opts; + opts << "help"; + opts << "verbose"; + opts << "layout"; + opts << "layout-direction"; + opts << "orientation"; + opts << "rotate"; + opts << "deskew"; + opts << "skew-deviation"; + opts << "disable-content-detection"; + opts << "enable-page-detection"; + opts << "enable-fine-tuning"; + opts << "force-disable-page-detection"; + opts << "content-detection"; + opts << "content-box"; + opts << "content-deviation"; + opts << "enable-auto-margins"; + opts << "margins"; + opts << "margins-left"; + opts << "margins-right"; + opts << "margins-top"; + opts << "margins-bottom"; + opts << "default-margins"; + opts << "default-margins-left"; + opts << "default-margins-right"; + opts << "default-margins-top"; + opts << "default-margins-bottom"; + opts << "match-layout"; + opts << "match-layout-tolerance"; + opts << "match-layout-default"; + opts << "alignment"; + opts << "alignment-vertical"; + opts << "alignment-horizontal"; + opts << "alignment-tolerance"; + opts << "dpi"; + opts << "output-dpi"; + opts << "dpi-x"; + opts << "dpi-y"; + opts << "output-dpi-x"; + opts << "output-dpi-y"; + opts << "color-mode"; + opts << "fill-margins"; + opts << "normalize-illumination"; + opts << "threshold"; + opts << "despeckle"; + opts << "dewarping"; + opts << "depth-perception"; + opts << "start-filter"; + opts << "end-filter"; + opts << "output-project"; + opts << "picture-shape"; + opts << "language"; + opts << "disable-content-text-mask"; + opts << "window-title"; + opts << "page-detection-box"; + opts << "page-detection-tolerance"; + opts << "page-borders"; + opts << "page-borders-left"; + opts << "page-borders-top"; + opts << "page-borders-right"; + opts << "page-borders-bottom"; + opts << "disable-check-output"; + opts << "default-output-dpi"; + opts << "default-color-mode"; + opts << "tiff-force-rgb"; + opts << "tiff-force-grayscale"; + opts << "tiff-force-keep-color-space"; + + QMap shortMap; + shortMap["h"] = "help"; + shortMap["help"] = "help"; + shortMap["v"] = "verbose"; + shortMap["l"] = "layout"; + shortMap["ld"] = "layout-direction"; + shortMap["o"] = "output-project"; + + // skip first argument (scantailor) + for (int i = 1; i < argv.size(); i++) { #ifdef DEBUG - std::cout << "arg[" << i << "]=" << argv[i].toLatin1().constData() << std::endl; + std::cout << "arg[" << i << "]=" << argv[i].toLatin1().constData() << std::endl; #endif - if (rx.exactMatch(argv[i])) { - QString key = rx.cap(1); - if (!opts.contains(key)) { - m_error = true; - std::cout << "Unknown option '" << key.toStdString() << "'" << std::endl; - continue; - } - m_options[key] = rx.cap(2); - } else if (rx_switch.exactMatch(argv[i])) { - QString key = rx_switch.cap(1); - if (!opts.contains(key)) { - m_error = true; - std::cout << "Unknown switch '" << key.toStdString() << "'" << std::endl; - continue; - } - m_options[key] = "true"; - } else if (rx_short.exactMatch(argv[i])) { - QString key = shortMap[rx_short.cap(1)]; - if (key == "") { - std::cout << "Unknown option: '" << rx_short.cap(1).toStdString() << "'" << std::endl; - m_error = true; - continue; - } - m_options[key] = rx_short.cap(2); - } else if (rx_short_switch.exactMatch(argv[i])) { - QString key = shortMap[rx_short_switch.cap(1)]; - if (key == "") { - std::cout << "Unknown switch: '" << rx_short_switch.cap(1).toStdString() << "'" << std::endl; - m_error = true; - continue; - } - m_options[key] = "true"; - } else if (rx_project.exactMatch(argv[i])) { - // project file - CommandLine::m_projectFile = argv[i]; + if (rx.exactMatch(argv[i])) { + QString key = rx.cap(1); + if (!opts.contains(key)) { + m_error = true; + std::cout << "Unknown option '" << key.toStdString() << "'" << std::endl; + continue; + } + m_options[key] = rx.cap(2); + } else if (rx_switch.exactMatch(argv[i])) { + QString key = rx_switch.cap(1); + if (!opts.contains(key)) { + m_error = true; + std::cout << "Unknown switch '" << key.toStdString() << "'" << std::endl; + continue; + } + m_options[key] = "true"; + } else if (rx_short.exactMatch(argv[i])) { + QString key = shortMap[rx_short.cap(1)]; + if (key == "") { + std::cout << "Unknown option: '" << rx_short.cap(1).toStdString() << "'" << std::endl; + m_error = true; + continue; + } + m_options[key] = rx_short.cap(2); + } else if (rx_short_switch.exactMatch(argv[i])) { + QString key = shortMap[rx_short_switch.cap(1)]; + if (key == "") { + std::cout << "Unknown switch: '" << rx_short_switch.cap(1).toStdString() << "'" << std::endl; + m_error = true; + continue; + } + m_options[key] = "true"; + } else if (rx_project.exactMatch(argv[i])) { + // project file + CommandLine::m_projectFile = argv[i]; + } else { + // handle input images and output directory + QFileInfo file(argv[i]); + if (i == (argv.size() - 1)) { + // output directory + if (file.isDir()) { + CommandLine::m_outputDirectory = file.filePath(); } else { - // handle input images and output directory - QFileInfo file(argv[i]); - if (i == (argv.size() - 1)) { - // output directory - if (file.isDir()) { - CommandLine::m_outputDirectory = file.filePath(); - } else { - std::cout << "Error: Last argument must be an existing directory" << std::endl; - exit(1); - } - } else if (file.filePath() == "-") { - // file names from stdin - std::string fname; - while (!std::cin.eof()) { - std::cin >> fname; - addImage(fname.c_str()); - } - } else if (file.isDir()) { - // add all files from given directory as images - QDir dir(argv[i]); - QStringList files = dir.entryList(QDir::Files, QDir::Name); - for (int f = 0; f < files.count(); f++) { - addImage(dir.filePath(files[f])); - } - } else { - // argument is image - addImage(file.filePath()); - } + std::cout << "Error: Last argument must be an existing directory" << std::endl; + exit(1); } + } else if (file.filePath() == "-") { + // file names from stdin + std::string fname; + while (!std::cin.eof()) { + std::cin >> fname; + addImage(fname.c_str()); + } + } else if (file.isDir()) { + // add all files from given directory as images + QDir dir(argv[i]); + QStringList files = dir.entryList(QDir::Files, QDir::Name); + for (int f = 0; f < files.count(); f++) { + addImage(dir.filePath(files[f])); + } + } else { + // argument is image + addImage(file.filePath()); + } } + } - setup(); + setup(); #ifdef DEBUG - QStringList params = m_options.keys(); - for (int i = 0; i < params.size(); i++) { - std::cout << params[i].toLatin1().constData() << "=" << m_options[params[i]].toLatin1().constData() - << std::endl; - } - std::cout << "Images: " << CommandLine::m_images.size() << std::endl; + QStringList params = m_options.keys(); + for (int i = 0; i < params.size(); i++) { + std::cout << params[i].toLatin1().constData() << "=" << m_options[params[i]].toLatin1().constData() << std::endl; + } + std::cout << "Images: " << CommandLine::m_images.size() << std::endl; #endif - return m_error; + return m_error; } // CommandLine::parseCli void CommandLine::addImage(const QString& path) { - QFileInfo file(path); - m_files.push_back(file); + QFileInfo file(path); + m_files.push_back(file); } void CommandLine::setup() { - m_outputProjectFile = fetchOutputProjectFile(); - m_layoutType = fetchLayoutType(); - m_layoutDirection = fetchLayoutDirection(); - m_colorMode = fetchColorMode(); - m_defaultColorMode = fetchDefaultColorMode(); - m_pictureShape = fetchPictureShape(); - m_dpi = fetchDpi(); - m_outputDpi = fetchDpi("output-dpi"); - m_defaultOutputDpi = fetchDpi("default-output-dpi"); - m_margins = fetchMargins(); - m_defaultMargins = fetchMargins("default-margins"); - m_pageBorders = fetchPageBorders(); - m_alignment = fetchAlignment(); - m_contentDetection = fetchContentDetection(); - m_contentRect = fetchContentRect(); - m_contentDeviation = fetchContentDeviation(); - m_orientation = fetchOrientation(); - m_threshold = fetchThreshold(); - m_deskewAngle = fetchDeskewAngle(); - m_deskewMode = fetchDeskewMode(); - m_skewDeviation = fetchSkewDeviation(); - m_startFilterIdx = fetchStartFilterIdx(); - m_endFilterIdx = fetchEndFilterIdx(); - m_matchLayoutTolerance = fetchMatchLayoutTolerance(); - m_dewarpingOptions = output::DewarpingOptions(fetchDewarpingMode()); - m_language = fetchLanguage(); - m_windowTitle = fetchWindowTitle(); - m_pageDetectionBox = fetchPageDetectionBox(); - m_pageDetectionTolerance = fetchPageDetectionTolerance(); - m_defaultNull = fetchDefaultNull(); - - QRegExp exp(".*(tif|tiff|jpg|jpeg|bmp|gif|png|pbm|pgm|ppm|xbm|xpm)$", Qt::CaseInsensitive); - for (auto& m_file : m_files) { - if (!exp.exactMatch(m_file.filePath())) { + m_outputProjectFile = fetchOutputProjectFile(); + m_layoutType = fetchLayoutType(); + m_layoutDirection = fetchLayoutDirection(); + m_colorMode = fetchColorMode(); + m_defaultColorMode = fetchDefaultColorMode(); + m_pictureShape = fetchPictureShape(); + m_dpi = fetchDpi(); + m_outputDpi = fetchDpi("output-dpi"); + m_defaultOutputDpi = fetchDpi("default-output-dpi"); + m_margins = fetchMargins(); + m_defaultMargins = fetchMargins("default-margins"); + m_pageBorders = fetchPageBorders(); + m_alignment = fetchAlignment(); + m_contentDetection = fetchContentDetection(); + m_contentRect = fetchContentRect(); + m_contentDeviation = fetchContentDeviation(); + m_orientation = fetchOrientation(); + m_threshold = fetchThreshold(); + m_deskewAngle = fetchDeskewAngle(); + m_deskewMode = fetchDeskewMode(); + m_skewDeviation = fetchSkewDeviation(); + m_startFilterIdx = fetchStartFilterIdx(); + m_endFilterIdx = fetchEndFilterIdx(); + m_matchLayoutTolerance = fetchMatchLayoutTolerance(); + m_dewarpingOptions = output::DewarpingOptions(fetchDewarpingMode()); + m_language = fetchLanguage(); + m_windowTitle = fetchWindowTitle(); + m_pageDetectionBox = fetchPageDetectionBox(); + m_pageDetectionTolerance = fetchPageDetectionTolerance(); + m_defaultNull = fetchDefaultNull(); + + QRegExp exp(".*(tif|tiff|jpg|jpeg|bmp|gif|png|pbm|pgm|ppm|xbm|xpm)$", Qt::CaseInsensitive); + for (auto& m_file : m_files) { + if (!exp.exactMatch(m_file.filePath())) { #ifdef DEBUG - std::cout << "Skipping file: " << m_files[i].filePath().toStdString() << std::endl; + std::cout << "Skipping file: " << m_files[i].filePath().toStdString() << std::endl; #endif - continue; - } - const ImageId image_id(m_file.filePath()); - ImageMetadata metadata; - metadata.setDpi(m_dpi); - std::vector vMetadata; - vMetadata.push_back(metadata); - ImageFileInfo image_info(m_file, vMetadata); - m_images.push_back(image_info); - } + continue; + } + const ImageId image_id(m_file.filePath()); + ImageMetadata metadata; + metadata.setDpi(m_dpi); + std::vector vMetadata; + vMetadata.push_back(metadata); + ImageFileInfo image_info(m_file, vMetadata); + m_images.push_back(image_info); + } } // CommandLine::setup void CommandLine::printHelp() { - std::cout << std::endl; - std::cout << "Scan Tailor is a post-processing tool for scanned pages." << std::endl; - std::cout << "Version: " << VERSION << std::endl; - std::cout << std::endl; - std::cout << "ScanTailor usage: " << std::endl; - std::cout << "\t1) scantailor" << std::endl; - std::cout << "\t2) scantailor " << std::endl; - std::cout << "\t3) scantailor-cli [options] " << std::endl; - std::cout << "\t4) scantailor-cli [options] [output_directory]" << std::endl; - std::cout << std::endl; - std::cout << "1)" << std::endl; - std::cout << "\tstart ScanTailor's GUI interface" << std::endl; - std::cout << "2)" << std::endl; - std::cout << "\tstart ScanTailor's GUI interface and load project file" << std::endl; - std::cout << "3)" << std::endl; - std::cout << "\tbatch processing images from command line; no GUI" << std::endl; - std::cout << "\tfile names are collected from arguments, input directory or stdin (-)" << std::endl; - std::cout << "4)" << std::endl; - std::cout << "\tbatch processing project from command line; no GUI" << std::endl; - std::cout << "\tif output_directory is specified as last argument, it overwrites the one in project file" - << std::endl; - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << "\t--help, -h" << std::endl; - std::cout << "\t--verbose, -v" << std::endl; - std::cout << "\t--languge=\t-- default: system language" << std::endl; - std::cout << "\t--layout=, -l=<0|1|1.5|2>\t\t-- default: 0" << std::endl; - std::cout << "\t\t\t 0: auto detect" << std::endl; - std::cout << "\t\t\t 1: one page layout" << std::endl; - std::cout << "\t\t\t1.5: one page layout but cutting is needed" << std::endl; - std::cout << "\t\t\t 2: two page layout" << std::endl; - std::cout << "\t--layout-direction=, -ld=\t-- default: lr" << std::endl; - std::cout << "\t--orientation=\n\t\t\t\t\t\t-- default: none" << std::endl; - std::cout << "\t--rotate=<0.0...360.0>\t\t\t-- it also sets deskew to manual mode" << std::endl; - std::cout << "\t--deskew=\t\t\t-- default: auto" << std::endl; - std::cout - << "\t--skew-deviation=<0.0...)\t\t-- default: 5.0; pages with bigger skew deviation will be painted in red" + std::cout << std::endl; + std::cout << "Scan Tailor is a post-processing tool for scanned pages." << std::endl; + std::cout << "Version: " << VERSION << std::endl; + std::cout << std::endl; + std::cout << "ScanTailor usage: " << std::endl; + std::cout << "\t1) scantailor" << std::endl; + std::cout << "\t2) scantailor " << std::endl; + std::cout << "\t3) scantailor-cli [options] " << std::endl; + std::cout << "\t4) scantailor-cli [options] [output_directory]" << std::endl; + std::cout << std::endl; + std::cout << "1)" << std::endl; + std::cout << "\tstart ScanTailor's GUI interface" << std::endl; + std::cout << "2)" << std::endl; + std::cout << "\tstart ScanTailor's GUI interface and load project file" << std::endl; + std::cout << "3)" << std::endl; + std::cout << "\tbatch processing images from command line; no GUI" << std::endl; + std::cout << "\tfile names are collected from arguments, input directory or stdin (-)" << std::endl; + std::cout << "4)" << std::endl; + std::cout << "\tbatch processing project from command line; no GUI" << std::endl; + std::cout << "\tif output_directory is specified as last argument, it overwrites the one in project file" + << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "\t--help, -h" << std::endl; + std::cout << "\t--verbose, -v" << std::endl; + std::cout << "\t--languge=\t-- default: system language" << std::endl; + std::cout << "\t--layout=, -l=<0|1|1.5|2>\t\t-- default: 0" << std::endl; + std::cout << "\t\t\t 0: auto detect" << std::endl; + std::cout << "\t\t\t 1: one page layout" << std::endl; + std::cout << "\t\t\t1.5: one page layout but cutting is needed" << std::endl; + std::cout << "\t\t\t 2: two page layout" << std::endl; + std::cout << "\t--layout-direction=, -ld=\t-- default: lr" << std::endl; + std::cout << "\t--orientation=\n\t\t\t\t\t\t-- default: none" << std::endl; + std::cout << "\t--rotate=<0.0...360.0>\t\t\t-- it also sets deskew to manual mode" << std::endl; + std::cout << "\t--deskew=\t\t\t-- default: auto" << std::endl; + std::cout << "\t--skew-deviation=<0.0...)\t\t-- default: 5.0; pages with bigger skew deviation will be painted in red" + << std::endl; + std::cout << "\t--disable-content-detection\t\t-- default: enabled" << std::endl; + std::cout << "\t--enable-page-detection\t\t\t-- default: disabled" << std::endl; + std::cout << "\t--enable-fine-tuning\t\t\t-- default: disabled; if page detection enabled it moves edges while " + "corners are in black" << std::endl; - std::cout << "\t--disable-content-detection\t\t-- default: enabled" << std::endl; - std::cout << "\t--enable-page-detection\t\t\t-- default: disabled" << std::endl; - std::cout << "\t--enable-fine-tuning\t\t\t-- default: disabled; if page detection enabled it moves edges while " - "corners are in black" - << std::endl; - std::cout << "\t--force-disable-page-detection\t\t-- switch page detection from page project off if enabled and " - "set content detection to manual mode" - << std::endl; - std::cout << "\t--disable-content-text-mask\n\t\t\t\t\t\t-- disable using text mask to estimate a content box" - << std::endl; - std::cout << "\t--content-detection=\n\t\t\t\t\t\t-- default: normal" << std::endl; - std::cout << "\t--content-deviation=<0.0...)\t\t-- default: 1.0; pages with bigger content deviation will be " - "painted in red" - << std::endl; - std::cout << "\t--content-box=<x:x>" << std::endl; - std::cout << "\t\t\t\t\t\t-- if set the content detection is se to manual mode" << std::endl; - std::cout << "\t\t\t\t\t\t example: --content-box=100x100:1500x2500" << std::endl; - std::cout << "\t--enable-auto-margins\t\t\t-- sets the margins to original ones (based on detected page or image " - "size)" - << std::endl; - std::cout << "\t--margins=\t\t\t-- sets left, top, right and bottom margins to same number." << std::endl; - std::cout << "\t\t--margins-left=" << std::endl; - std::cout << "\t\t--margins-right=" << std::endl; - std::cout << "\t\t--margins-top=" << std::endl; - std::cout << "\t\t--margins-bottom=" << std::endl; - std::cout << "\t--default-margins=\t\t\t-- sets left, top, right and bottom margins, for new pages, to " - "same number." - << std::endl; - std::cout << "\t\t--default-margins-left=" << std::endl; - std::cout << "\t\t--default-margins-right=" << std::endl; - std::cout << "\t\t--default-margins-top=" << std::endl; - std::cout << "\t\t--default-margins-bottom=" << std::endl; - std::cout << "\t--match-layout=\t\t-- default: true" << std::endl; - std::cout << "\t--match-layout-tolerance=<0.0...)\t-- default: off" << std::endl; - std::cout << "\t--match-layout-default=\t-- default: true" << std::endl; - std::cout << "\t--alignment=\t-- sets vertical to original and horizontal to center" - << std::endl; - std::cout << "\t\t--alignment-vertical=" << std::endl; - std::cout << "\t\t--alignment-horizontal=" << std::endl; - std::cout << "\t--alignment-tolerance=\t\t-- sets tolerance for auto alignment" << std::endl; - std::cout << "\t--dpi=\t\t\t\t-- sets x and y dpi. default: 600" << std::endl; - std::cout << "\t\t--dpi-x=" << std::endl; - std::cout << "\t\t--dpi-y=" << std::endl; - std::cout << "\t--output-dpi=\t\t\t-- sets x and y output dpi. default: 600" << std::endl; - std::cout << "\t\t--output-dpi-x=" << std::endl; - std::cout << "\t\t--output-dpi-y=" << std::endl; - std::cout << "\t--default-output-dpi=\t\t-- default output dpi for pages created by split filter in gui" - << std::endl; - std::cout << "\t--color-mode=\n\t\t\t\t\t\t-- default: black_and_white" - << std::endl; - std::cout << "\t--default-color-mode=<...>\t\t-- sets default value for new images created by split filter" - << std::endl; - std::cout << "\t--picture-shape=\n\t\t\t\t\t\t-- default: free" << std::endl; - std::cout << "\t--cut-margins\t\t\t\t-- default: false" << std::endl; - std::cout << "\t--normalize-illumination\t\t-- default: false" << std::endl; - std::cout << "\t--threshold=\t\t\t\t-- n<0 thinner, n>0 thicker; default: 0" << std::endl; - std::cout << "\t--despeckle=\n\t\t\t\t\t\t-- default: normal" << std::endl; - std::cout << "\t--dewarping=\t\t\t-- default: off" << std::endl; - std::cout << "\t--depth-perception=<1.0...3.0>\t\t-- default: 2.0" << std::endl; - std::cout << "\t--start-filter=<1...6>\t\t\t-- default: 4" << std::endl; - std::cout << "\t--end-filter=<1...6>\t\t\t-- default: 6" << std::endl; - std::cout << "\t--output-project=, -o=" << std::endl; - std::cout << "\t--tiff-force-rgb\t\t\t-- all output tiffs will be rgb" << std::endl; - std::cout << "\t--tiff-force-grayscale\t\t\t-- all output tiffs will be grayscale" << std::endl; - std::cout << "\t--tiff-force-keep-color-space\t\t-- output tiffs will be in original color space" << std::endl; - std::cout << "\t--window-title=WindowTitle\t\t-- default: project name" << std::endl; - std::cout << "\t--page-detection-box=\t\t-- in mm" << std::endl; - std::cout << "\t\t--page-detection-tolerance=<0.0..1.0>\t-- default: 0.1" << std::endl; - std::cout << "\t--disable-check-output\t\t\t-- don't check if page is valid when switching to step 6"; - std::cout << std::endl; + std::cout << "\t--force-disable-page-detection\t\t-- switch page detection from page project off if enabled and " + "set content detection to manual mode" + << std::endl; + std::cout << "\t--disable-content-text-mask\n\t\t\t\t\t\t-- disable using text mask to estimate a content box" + << std::endl; + std::cout << "\t--content-detection=\n\t\t\t\t\t\t-- default: normal" << std::endl; + std::cout << "\t--content-deviation=<0.0...)\t\t-- default: 1.0; pages with bigger content deviation will be " + "painted in red" + << std::endl; + std::cout << "\t--content-box=<x:x>" << std::endl; + std::cout << "\t\t\t\t\t\t-- if set the content detection is se to manual mode" << std::endl; + std::cout << "\t\t\t\t\t\t example: --content-box=100x100:1500x2500" << std::endl; + std::cout << "\t--enable-auto-margins\t\t\t-- sets the margins to original ones (based on detected page or image " + "size)" + << std::endl; + std::cout << "\t--margins=\t\t\t-- sets left, top, right and bottom margins to same number." << std::endl; + std::cout << "\t\t--margins-left=" << std::endl; + std::cout << "\t\t--margins-right=" << std::endl; + std::cout << "\t\t--margins-top=" << std::endl; + std::cout << "\t\t--margins-bottom=" << std::endl; + std::cout << "\t--default-margins=\t\t\t-- sets left, top, right and bottom margins, for new pages, to " + "same number." + << std::endl; + std::cout << "\t\t--default-margins-left=" << std::endl; + std::cout << "\t\t--default-margins-right=" << std::endl; + std::cout << "\t\t--default-margins-top=" << std::endl; + std::cout << "\t\t--default-margins-bottom=" << std::endl; + std::cout << "\t--match-layout=\t\t-- default: true" << std::endl; + std::cout << "\t--match-layout-tolerance=<0.0...)\t-- default: off" << std::endl; + std::cout << "\t--match-layout-default=\t-- default: true" << std::endl; + std::cout << "\t--alignment=\t-- sets vertical to original and horizontal to center" + << std::endl; + std::cout << "\t\t--alignment-vertical=" << std::endl; + std::cout << "\t\t--alignment-horizontal=" << std::endl; + std::cout << "\t--alignment-tolerance=\t\t-- sets tolerance for auto alignment" << std::endl; + std::cout << "\t--dpi=\t\t\t\t-- sets x and y dpi. default: 600" << std::endl; + std::cout << "\t\t--dpi-x=" << std::endl; + std::cout << "\t\t--dpi-y=" << std::endl; + std::cout << "\t--output-dpi=\t\t\t-- sets x and y output dpi. default: 600" << std::endl; + std::cout << "\t\t--output-dpi-x=" << std::endl; + std::cout << "\t\t--output-dpi-y=" << std::endl; + std::cout << "\t--default-output-dpi=\t\t-- default output dpi for pages created by split filter in gui" + << std::endl; + std::cout << "\t--color-mode=\n\t\t\t\t\t\t-- default: black_and_white" + << std::endl; + std::cout << "\t--default-color-mode=<...>\t\t-- sets default value for new images created by split filter" + << std::endl; + std::cout << "\t--picture-shape=\n\t\t\t\t\t\t-- default: free" << std::endl; + std::cout << "\t--fill-margins\t\t\t\t-- default: false" << std::endl; + std::cout << "\t--normalize-illumination\t\t-- default: false" << std::endl; + std::cout << "\t--threshold=\t\t\t\t-- n<0 thinner, n>0 thicker; default: 0" << std::endl; + std::cout << "\t--despeckle=<1.0...3.0>\n\t\t\t\t\t\t-- default: normal" << std::endl; + std::cout << "\t--dewarping=\t\t\t-- default: off" << std::endl; + std::cout << "\t--depth-perception=<1.0...3.0>\t\t-- default: 2.0" << std::endl; + std::cout << "\t--start-filter=<1...6>\t\t\t-- default: 4" << std::endl; + std::cout << "\t--end-filter=<1...6>\t\t\t-- default: 6" << std::endl; + std::cout << "\t--output-project=, -o=" << std::endl; + std::cout << "\t--tiff-force-rgb\t\t\t-- all output tiffs will be rgb" << std::endl; + std::cout << "\t--tiff-force-grayscale\t\t\t-- all output tiffs will be grayscale" << std::endl; + std::cout << "\t--tiff-force-keep-color-space\t\t-- output tiffs will be in original color space" << std::endl; + std::cout << "\t--window-title=WindowTitle\t\t-- default: project name" << std::endl; + std::cout << "\t--page-detection-box=\t\t-- in mm" << std::endl; + std::cout << "\t\t--page-detection-tolerance=<0.0..1.0>\t-- default: 0.1" << std::endl; + std::cout << "\t--disable-check-output\t\t\t-- don't check if page is valid when switching to step 6"; + std::cout << std::endl; } // CommandLine::printHelp page_split::LayoutType CommandLine::fetchLayoutType() { - page_split::LayoutType lt = page_split::AUTO_LAYOUT_TYPE; + page_split::LayoutType lt = page_split::AUTO_LAYOUT_TYPE; - if (!hasLayout()) { - return lt; - } + if (!hasLayout()) { + return lt; + } - if (m_options["layout"] == "1") { - lt = page_split::SINGLE_PAGE_UNCUT; - } else if (m_options["layout"] == "1.5") { - lt = page_split::PAGE_PLUS_OFFCUT; - } else if (m_options["layout"] == "2") { - lt = page_split::TWO_PAGES; - } + if (m_options["layout"] == "1") { + lt = page_split::SINGLE_PAGE_UNCUT; + } else if (m_options["layout"] == "1.5") { + lt = page_split::PAGE_PLUS_OFFCUT; + } else if (m_options["layout"] == "2") { + lt = page_split::TWO_PAGES; + } - return lt; + return lt; } Qt::LayoutDirection CommandLine::fetchLayoutDirection() { - Qt::LayoutDirection l = Qt::LeftToRight; - if (!hasLayoutDirection()) { - return l; - } + Qt::LayoutDirection l = Qt::LeftToRight; + if (!hasLayoutDirection()) { + return l; + } - QString ld = m_options["layout-direction"].toLower(); - if (ld == "rl") { - l = Qt::RightToLeft; - } + QString ld = m_options["layout-direction"].toLower(); + if (ld == "rl") { + l = Qt::RightToLeft; + } - return l; + return l; } Dpi CommandLine::fetchDpi(QString oname) { - int xdpi = 600; - int ydpi = 600; + int xdpi = 600; + int ydpi = 600; - if (m_options.contains(oname + "-x")) { - xdpi = m_options[oname + "-x"].toInt(); - } - if (m_options.contains(oname + "-y")) { - ydpi = m_options[oname + "-y"].toInt(); - } - if (m_options.contains(oname)) { - xdpi = m_options[oname].toInt(); - ydpi = m_options[oname].toInt(); - } + if (m_options.contains(oname + "-x")) { + xdpi = m_options[oname + "-x"].toInt(); + } + if (m_options.contains(oname + "-y")) { + ydpi = m_options[oname + "-y"].toInt(); + } + if (m_options.contains(oname)) { + xdpi = m_options[oname].toInt(); + ydpi = m_options[oname].toInt(); + } - return Dpi(xdpi, ydpi); + return Dpi(xdpi, ydpi); } output::ColorMode CommandLine::fetchColorMode() { - if (!hasColorMode()) { - return output::BLACK_AND_WHITE; - } + if (!hasColorMode()) { + return output::BLACK_AND_WHITE; + } - QString cm = m_options["color-mode"].toLower(); + QString cm = m_options["color-mode"].toLower(); - if (cm == "color_grayscale") { - return output::COLOR_GRAYSCALE; - } else if (cm == "mixed") { - return output::MIXED; - } + if (cm == "color_grayscale") { + return output::COLOR_GRAYSCALE; + } else if (cm == "mixed") { + return output::MIXED; + } - return output::BLACK_AND_WHITE; + return output::BLACK_AND_WHITE; } output::ColorMode CommandLine::fetchDefaultColorMode() { - if (!hasDefaultColorMode()) { - return output::BLACK_AND_WHITE; - } + if (!hasDefaultColorMode()) { + return output::BLACK_AND_WHITE; + } - QString cm = m_options["default-color-mode"].toLower(); + QString cm = m_options["default-color-mode"].toLower(); - if (cm == "color_grayscale") { - return output::COLOR_GRAYSCALE; - } else if (cm == "mixed") { - return output::MIXED; - } + if (cm == "color_grayscale") { + return output::COLOR_GRAYSCALE; + } else if (cm == "mixed") { + return output::MIXED; + } - return output::BLACK_AND_WHITE; + return output::BLACK_AND_WHITE; } output::PictureShape CommandLine::fetchPictureShape() { - if (!hasPictureShape()) { - return output::FREE_SHAPE; - } + if (!hasPictureShape()) { + return output::FREE_SHAPE; + } - QString ps = m_options["picture-shape"].toLower(); + QString ps = m_options["picture-shape"].toLower(); - if (ps == "rectangular") { - return output::RECTANGULAR_SHAPE; - } + if (ps == "rectangular") { + return output::RECTANGULAR_SHAPE; + } - return output::FREE_SHAPE; + return output::FREE_SHAPE; } Margins CommandLine::fetchMargins(QString base, Margins def) { - Margins margins(def); - - if (m_options.contains(base)) { - double m = m_options[base].toDouble(); - margins.setTop(m); - margins.setBottom(m); - margins.setLeft(m); - margins.setRight(m); - } + Margins margins(def); + + if (m_options.contains(base)) { + double m = m_options[base].toDouble(); + margins.setTop(m); + margins.setBottom(m); + margins.setLeft(m); + margins.setRight(m); + } + + QString lstr = base + "-left", tstr = base + "-top", rstr = base + "-right", bstr = base + "-bottom"; + + + if (m_options.contains(lstr)) { + margins.setLeft(m_options[lstr].toFloat()); + } + if (m_options.contains(rstr)) { + margins.setRight(m_options[rstr].toFloat()); + } + if (m_options.contains(tstr)) { + margins.setTop(m_options[tstr].toFloat()); + } + if (m_options.contains(bstr)) { + margins.setBottom(m_options[bstr].toFloat()); + } + + return margins; +} // CommandLine::fetchMargins - QString lstr = base + "-left", tstr = base + "-top", rstr = base + "-right", bstr = base + "-bottom"; +page_layout::Alignment CommandLine::fetchAlignment() { + page_layout::Alignment alignment(page_layout::Alignment::TOP, page_layout::Alignment::HCENTER); + if (m_options.contains("match-layout")) { + m_defaultNull = false; + if (m_options["match-layout"] == "false") { + alignment.setNull(true); + } + if (m_options["match-layout"] == "true") { + alignment.setNull(false); + m_defaultNull = true; + } + } - if (m_options.contains(lstr)) { - margins.setLeft(m_options[lstr].toFloat()); + if (m_options.contains("alignment")) { + if (m_options["alignment"] == "original") { + alignment.setVertical(page_layout::Alignment::VORIGINAL); + } else if (m_options["alignment"] == "auto") { + alignment.setVertical(page_layout::Alignment::VAUTO); + } else { + alignment.setVertical(page_layout::Alignment::VCENTER); } - if (m_options.contains(rstr)) { - margins.setRight(m_options[rstr].toFloat()); + alignment.setHorizontal(page_layout::Alignment::HCENTER); + } + + if (m_options.contains("alignment-vertical")) { + QString a = m_options["alignment-vertical"].toLower(); + if (a == "top") { + alignment.setVertical(page_layout::Alignment::TOP); } - if (m_options.contains(tstr)) { - margins.setTop(m_options[tstr].toFloat()); + if (a == "center") { + alignment.setVertical(page_layout::Alignment::VCENTER); } - if (m_options.contains(bstr)) { - margins.setBottom(m_options[bstr].toFloat()); + if (a == "bottom") { + alignment.setVertical(page_layout::Alignment::BOTTOM); } - - return margins; -} // CommandLine::fetchMargins - -page_layout::Alignment CommandLine::fetchAlignment() { - page_layout::Alignment alignment(page_layout::Alignment::TOP, page_layout::Alignment::HCENTER); - - if (m_options.contains("match-layout")) { - m_defaultNull = false; - if (m_options["match-layout"] == "false") { - alignment.setNull(true); - } - if (m_options["match-layout"] == "true") { - alignment.setNull(false); - m_defaultNull = true; - } + if (a == "original") { + alignment.setVertical(page_layout::Alignment::VORIGINAL); } - - if (m_options.contains("alignment")) { - if (m_options["alignment"] == "original") { - alignment.setVertical(page_layout::Alignment::VORIGINAL); - } else if (m_options["alignment"] == "auto") { - alignment.setVertical(page_layout::Alignment::VAUTO); - } else { - alignment.setVertical(page_layout::Alignment::VCENTER); - } - alignment.setHorizontal(page_layout::Alignment::HCENTER); + if (a == "auto") { + alignment.setVertical(page_layout::Alignment::VAUTO); } + } - if (m_options.contains("alignment-vertical")) { - QString a = m_options["alignment-vertical"].toLower(); - if (a == "top") { - alignment.setVertical(page_layout::Alignment::TOP); - } - if (a == "center") { - alignment.setVertical(page_layout::Alignment::VCENTER); - } - if (a == "bottom") { - alignment.setVertical(page_layout::Alignment::BOTTOM); - } - if (a == "original") { - alignment.setVertical(page_layout::Alignment::VORIGINAL); - } - if (a == "auto") { - alignment.setVertical(page_layout::Alignment::VAUTO); - } + if (m_options.contains("alignment-horizontal")) { + QString a = m_options["alignment-horizontal"].toLower(); + if (a == "left") { + alignment.setHorizontal(page_layout::Alignment::LEFT); } - - if (m_options.contains("alignment-horizontal")) { - QString a = m_options["alignment-horizontal"].toLower(); - if (a == "left") { - alignment.setHorizontal(page_layout::Alignment::LEFT); - } - if (a == "center") { - alignment.setHorizontal(page_layout::Alignment::HCENTER); - } - if (a == "right") { - alignment.setHorizontal(page_layout::Alignment::RIGHT); - } - if (a == "original") { - alignment.setHorizontal(page_layout::Alignment::HORIGINAL); - } - if (a == "auto") { - alignment.setHorizontal(page_layout::Alignment::HAUTO); - } + if (a == "center") { + alignment.setHorizontal(page_layout::Alignment::HCENTER); + } + if (a == "right") { + alignment.setHorizontal(page_layout::Alignment::RIGHT); } + if (a == "original") { + alignment.setHorizontal(page_layout::Alignment::HORIGINAL); + } + if (a == "auto") { + alignment.setHorizontal(page_layout::Alignment::HAUTO); + } + } - return alignment; + return alignment; } // CommandLine::fetchAlignment Despeckle::Level CommandLine::fetchContentDetection() { - Despeckle::Level level = Despeckle::NORMAL; - - if (hasContentDetection()) { - QString cm = m_options["content-detection"].toLower(); - if (cm == "cautious") { - level = Despeckle::CAUTIOUS; - } else if (cm == "aggressive") { - level = Despeckle::AGGRESSIVE; - } + Despeckle::Level level = Despeckle::NORMAL; + + if (hasContentDetection()) { + QString cm = m_options["content-detection"].toLower(); + if (cm == "cautious") { + level = Despeckle::CAUTIOUS; + } else if (cm == "aggressive") { + level = Despeckle::AGGRESSIVE; } + } - return level; + return level; } QRectF CommandLine::fetchContentRect() { - if (!hasContentRect()) { - return QRectF(); - } + if (!hasContentRect()) { + return QRectF(); + } - QRegExp rx(R"(([\d\.]+)x([\d\.]+):([\d\.]+)x([\d\.]+))"); + QRegExp rx(R"(([\d\.]+)x([\d\.]+):([\d\.]+)x([\d\.]+))"); - if (rx.exactMatch(m_options["content-box"])) { - return QRectF(rx.cap(1).toFloat(), rx.cap(2).toFloat(), rx.cap(3).toFloat(), rx.cap(4).toFloat()); - } + if (rx.exactMatch(m_options["content-box"])) { + return QRectF(rx.cap(1).toFloat(), rx.cap(2).toFloat(), rx.cap(3).toFloat(), rx.cap(4).toFloat()); + } - std::cout << "invalid --content-box=" << m_options["content-box"].toLatin1().constData() << std::endl; - exit(1); + std::cout << "invalid --content-box=" << m_options["content-box"].toLatin1().constData() << std::endl; + exit(1); } double CommandLine::fetchContentDeviation() { - if (!hasContentDeviation()) { - return 1.0; - } + if (!hasContentDeviation()) { + return 1.0; + } - return m_options["content-deviation"].toDouble(); + return m_options["content-deviation"].toDouble(); } CommandLine::Orientation CommandLine::fetchOrientation() { - if (!hasOrientation()) { - return TOP; - } - - Orientation orient; - QString cli_orient = m_options["orientation"]; - - if (cli_orient == "left") { - orient = LEFT; - } else if (cli_orient == "right") { - orient = RIGHT; - } else if (cli_orient == "upsidedown") { - orient = UPSIDEDOWN; - } else { - std::cout << "Wrong orientation " << m_options["orientation"].toLatin1().constData() << std::endl; - exit(1); - } + if (!hasOrientation()) { + return TOP; + } + + Orientation orient; + QString cli_orient = m_options["orientation"]; + + if (cli_orient == "left") { + orient = LEFT; + } else if (cli_orient == "right") { + orient = RIGHT; + } else if (cli_orient == "upsidedown") { + orient = UPSIDEDOWN; + } else { + std::cout << "Wrong orientation " << m_options["orientation"].toLatin1().constData() << std::endl; + exit(1); + } - return orient; + return orient; } QString CommandLine::fetchOutputProjectFile() { - if (!hasOutputProject()) { - return QString(); - } + if (!hasOutputProject()) { + return QString(); + } - return m_options["output-project"]; + return m_options["output-project"]; } int CommandLine::fetchThreshold() { - if (!hasThreshold()) { - return 0; - } + if (!hasThreshold()) { + return 0; + } - return m_options["threshold"].toInt(); + return m_options["threshold"].toInt(); } double CommandLine::fetchDeskewAngle() { - if (!hasDeskewAngle()) { - return 0.0; - } + if (!hasDeskewAngle()) { + return 0.0; + } - return m_options["rotate"].toDouble(); + return m_options["rotate"].toDouble(); } AutoManualMode CommandLine::fetchDeskewMode() { - if (!hasDeskew()) { - return MODE_AUTO; - } + if (!hasDeskew()) { + return MODE_AUTO; + } - return (m_options["deskew"].toLower() == "manual") ? MODE_MANUAL : MODE_AUTO; + return (m_options["deskew"].toLower() == "manual") ? MODE_MANUAL : MODE_AUTO; } double CommandLine::fetchSkewDeviation() { - if (!hasSkewDeviation()) { - return 5.0; - } + if (!hasSkewDeviation()) { + return 5.0; + } - return m_options["skew-deviation"].toDouble(); + return m_options["skew-deviation"].toDouble(); } int CommandLine::fetchStartFilterIdx() { - if (!hasStartFilterIdx()) { - return 0; - } + if (!hasStartFilterIdx()) { + return 0; + } - return m_options["start-filter"].toInt() - 1; + return m_options["start-filter"].toInt() - 1; } int CommandLine::fetchEndFilterIdx() { - if (!hasEndFilterIdx()) { - return 5; - } + if (!hasEndFilterIdx()) { + return 5; + } - return m_options["end-filter"].toInt() - 1; + return m_options["end-filter"].toInt() - 1; } output::DewarpingMode CommandLine::fetchDewarpingMode() { - if (!hasDewarping()) { - return output::OFF; - } + if (!hasDewarping()) { + return output::OFF; + } - return output::DewarpingOptions::parseDewarpingMode(m_options["dewarping"].toLower()); + return output::DewarpingOptions::parseDewarpingMode(m_options["dewarping"].toLower()); } -output::DespeckleLevel CommandLine::fetchDespeckleLevel() { - if (!hasDespeckle()) { - return output::DESPECKLE_NORMAL; - } +double CommandLine::fetchDespeckleLevel() { + if (!hasDespeckle()) { + return 2.0; + } - return output::despeckleLevelFromString(m_options["despeckle"]); + return m_options["despeckle"].toDouble(); } output::DepthPerception CommandLine::fetchDepthPerception() { - if (!hasDepthPerception()) { - return output::DepthPerception(); - } + if (!hasDepthPerception()) { + return output::DepthPerception(); + } - return output::DepthPerception(m_options["depth-perception"]); + return output::DepthPerception(m_options["depth-perception"]); } float CommandLine::fetchMatchLayoutTolerance() { - if (!hasMatchLayoutTolerance()) { - return 0.2f; - } + if (!hasMatchLayoutTolerance()) { + return 0.2f; + } - return m_options["match-layout-tolerance"].toFloat(); + return m_options["match-layout-tolerance"].toFloat(); } bool CommandLine::hasMargins(QString base) const { - return m_options.contains(base) || m_options.contains(base + "-left") || m_options.contains(base + "-right") - || m_options.contains(base + "-top") || m_options.contains(base + "-bottom"); + return m_options.contains(base) || m_options.contains(base + "-left") || m_options.contains(base + "-right") + || m_options.contains(base + "-top") || m_options.contains(base + "-bottom"); } bool CommandLine::hasAlignment() const { - return hasMatchLayoutTolerance() || m_options.contains("alignment") || m_options.contains("alignment-vertical") - || m_options.contains("alignment-horizontal") || isAutoMarginsEnabled(); + return hasMatchLayoutTolerance() || m_options.contains("alignment") || m_options.contains("alignment-vertical") + || m_options.contains("alignment-horizontal") || isAutoMarginsEnabled(); } bool CommandLine::hasOutputDpi() const { - return m_options.contains("output-dpi") || m_options.contains("output-dpi-x") || m_options.contains("output-dpi-y"); + return m_options.contains("output-dpi") || m_options.contains("output-dpi-x") || m_options.contains("output-dpi-y"); } bool CommandLine::hasLanguage() const { - return m_options.contains("language"); + return m_options.contains("language"); } QString CommandLine::fetchLanguage() const { - if (hasLanguage()) { - return m_options["language"]; - } + if (hasLanguage()) { + return m_options["language"]; + } - return "untranslated"; + return "untranslated"; } QString CommandLine::fetchWindowTitle() const { - if (hasWindowTitle()) { - return m_options["window-title"]; - } + if (hasWindowTitle()) { + return m_options["window-title"]; + } - return ""; + return ""; } QSizeF CommandLine::fetchPageDetectionBox() const { - if (!hasPageDetectionBox()) { - return QSizeF(); - } + if (!hasPageDetectionBox()) { + return QSizeF(); + } - QRegExp rx(R"(([\d\.]+)x([\d\.]+))"); - if (rx.exactMatch(m_options["page-detection-box"])) { - return QSizeF(rx.cap(1).toFloat(), rx.cap(2).toFloat()); - } + QRegExp rx(R"(([\d\.]+)x([\d\.]+))"); + if (rx.exactMatch(m_options["page-detection-box"])) { + return QSizeF(rx.cap(1).toFloat(), rx.cap(2).toFloat()); + } - std::cout << "invalid --page-detection-box=" << m_options["page-detection-box"].toLatin1().constData() << std::endl; - exit(1); + std::cout << "invalid --page-detection-box=" << m_options["page-detection-box"].toLatin1().constData() << std::endl; + exit(1); } double CommandLine::fetchPageDetectionTolerance() const { - if (hasPageDetectionTolerance()) { - return m_options["page-detection-tolerance"].toFloat(); - } + if (hasPageDetectionTolerance()) { + return m_options["page-detection-tolerance"].toFloat(); + } - return 0.1; + return 0.1; } bool CommandLine::fetchDefaultNull() { - m_defaultNull = false; + m_defaultNull = false; - if (contains("match-layout-default") && (m_options["match-layout-default"] == "false")) { - m_defaultNull = true; - } + if (contains("match-layout-default") && (m_options["match-layout-default"] == "false")) { + m_defaultNull = true; + } - return m_defaultNull; + return m_defaultNull; } diff --git a/CommandLine.h b/CommandLine.h index 17a6b538c..125249fae 100644 --- a/CommandLine.h +++ b/CommandLine.h @@ -26,19 +26,19 @@ #include #include +#include "AutoManualMode.h" +#include "Despeckle.h" #include "Dpi.h" -#include "filters/page_split/LayoutType.h" +#include "ImageFileInfo.h" +#include "Margins.h" #include "filters/output/ColorParams.h" -#include "filters/output/Params.h" +#include "filters/output/DepthPerception.h" #include "filters/output/DespeckleLevel.h" #include "filters/output/DewarpingOptions.h" -#include "filters/output/DepthPerception.h" -#include "filters/page_layout/Settings.h" +#include "filters/output/Params.h" #include "filters/page_layout/Alignment.h" -#include "ImageFileInfo.h" -#include "AutoManualMode.h" -#include "Margins.h" -#include "Despeckle.h" +#include "filters/page_layout/Settings.h" +#include "filters/page_split/LayoutType.h" namespace page_layout { class Alignment; @@ -50,460 +50,307 @@ class Alignment; * use CommandLine::set(const CommandLine&) to set the global class */ class CommandLine { - // Member-wise copying is OK. -public: - enum Orientation { TOP, LEFT, RIGHT, UPSIDEDOWN }; + // Member-wise copying is OK. + public: + enum Orientation { TOP, LEFT, RIGHT, UPSIDEDOWN }; + + static const CommandLine& get() { return m_globalInstance; } + + static void set(const CommandLine& cl); + + explicit CommandLine(const QStringList& argv, bool g = true) + : m_error(false), m_gui(g), m_global(false), m_defaultNull(false) { + CommandLine::parseCli(argv); + } + + bool isGui() const { return m_gui; } + + bool isVerbose() const { return contains("verbose"); } + + bool isError() const { return m_error; } + + const std::vector& images() const { return m_images; } + + const QString& outputDirectory() const { return m_outputDirectory; } + + const QString& projectFile() const { return m_projectFile; } + + const QString& outputProjectFile() const { return m_outputProjectFile; } + + bool isContentDetectionEnabled() const { return !contains("disable-content-detection"); } + + bool isPageDetectionEnabled() const { return contains("enable-page-detection"); } + + bool isForcePageDetectionDisabled() const { return contains("force-disable-page-detection"); } + + bool isFineTuningEnabled() const { return contains("enable-fine-tuning"); } + + bool isAutoMarginsEnabled() const { return contains("enable-auto-margins"); } - static const CommandLine& get() { - return m_globalInstance; - } + bool hasMargins(QString base = "margins") const; - static void set(const CommandLine& cl); + bool hasPageBorders() const { return hasMargins("page-borders"); } - explicit CommandLine(const QStringList& argv, bool g = true) - : m_error(false), m_gui(g), m_global(false), m_defaultNull(false) { - CommandLine::parseCli(argv); - } + bool hasAlignment() const; - bool isGui() const { - return m_gui; - } + bool hasOutputDpi() const; - bool isVerbose() const { - return contains("verbose"); - } + bool hasLanguage() const; - bool isError() const { - return m_error; - } + bool hasHelp() const { return contains("help"); } - const std::vector& images() const { - return m_images; - } + bool hasOutputProject() const { return contains("output-project") && !m_options["output-project"].isEmpty(); } - const QString& outputDirectory() const { - return m_outputDirectory; - } + bool hasLayout() const { return contains("layout") && !m_options["layout"].isEmpty(); } - const QString& projectFile() const { - return m_projectFile; - } + bool hasLayoutDirection() const { return contains("layout-direction") && !m_options["layout-direction"].isEmpty(); } - const QString& outputProjectFile() const { - return m_outputProjectFile; - } + bool hasStartFilterIdx() const { return contains("start-filter") && !m_options["start-filter"].isEmpty(); } - bool isContentDetectionEnabled() const { - return !contains("disable-content-detection"); - } + bool hasEndFilterIdx() const { return contains("end-filter") && !m_options["end-filter"].isEmpty(); } - bool isPageDetectionEnabled() const { - return contains("enable-page-detection"); - } + bool hasOrientation() const { return contains("orientation") && !m_options["orientation"].isEmpty(); } - bool isForcePageDetectionDisabled() const { - return contains("force-disable-page-detection"); - } + bool hasDeskewAngle() const { return contains("rotate") && !m_options["rotate"].isEmpty(); } - bool isFineTuningEnabled() const { - return contains("enable-fine-tuning"); - } + bool hasDeskew() const { return contains("deskew") && !m_options["deskew"].isEmpty(); } - bool isAutoMarginsEnabled() const { - return contains("enable-auto-margins"); - } + bool hasSkewDeviation() const { return contains("skew-deviation") && !m_options["skew-deviation"].isEmpty(); } - bool hasMargins(QString base = "margins") const; + bool hasContentRect() const { return contains("content-box") && !m_options["content-box"].isEmpty(); } - bool hasPageBorders() const { - return hasMargins("page-borders"); - } + bool hasContentDeviation() const { + return contains("content-deviation") && !m_options["content-deviation"].isEmpty(); + } - bool hasAlignment() const; + bool hasContentDetection() const { return !contains("disable-content-detection"); } - bool hasOutputDpi() const; + bool hasContentText() const { return !contains("disable-content-text-mask"); } - bool hasLanguage() const; + bool hasColorMode() const { return contains("color-mode") && !m_options["color-mode"].isEmpty(); } - bool hasHelp() const { - return contains("help"); - } + bool hasDefaultColorMode() const { + return contains("default-color-mode") && !m_options["default-color-mode"].isEmpty(); + } - bool hasOutputProject() const { - return contains("output-project") && !m_options["output-project"].isEmpty(); - } + bool hasPictureShape() const { return contains("picture-shape") && !m_options["picture-shape"].isEmpty(); } - bool hasLayout() const { - return contains("layout") && !m_options["layout"].isEmpty(); - } + bool hasFillMargins() const { return contains("fill-margins"); } - bool hasLayoutDirection() const { - return contains("layout-direction") && !m_options["layout-direction"].isEmpty(); - } + bool hasNormalizeIllumination() const { return contains("normalize-illumination"); } - bool hasStartFilterIdx() const { - return contains("start-filter") && !m_options["start-filter"].isEmpty(); - } + bool hasThreshold() const { return contains("threshold") && !m_options["threshold"].isEmpty(); } - bool hasEndFilterIdx() const { - return contains("end-filter") && !m_options["end-filter"].isEmpty(); - } + bool hasDespeckle() const { return contains("despeckle") && !m_options["despeckle"].isEmpty(); } - bool hasOrientation() const { - return contains("orientation") && !m_options["orientation"].isEmpty(); - } + bool hasDewarping() const { return contains("dewarping"); } - bool hasDeskewAngle() const { - return contains("rotate") && !m_options["rotate"].isEmpty(); - } + bool hasMatchLayoutTolerance() const { + return contains("match-layout-tolerance") && !m_options["match-layout-tolerance"].isEmpty(); + } - bool hasDeskew() const { - return contains("deskew") && !m_options["deskew"].isEmpty(); - } + bool hasDepthPerception() const { return contains("depth-perception") && !m_options["depth-perception"].isEmpty(); } - bool hasSkewDeviation() const { - return contains("skew-deviation") && !m_options["skew-deviation"].isEmpty(); - } + bool hasTiffForceRGB() const { return contains("tiff-force-rgb"); } - bool hasContentRect() const { - return contains("content-box") && !m_options["content-box"].isEmpty(); - } + bool hasTiffForceGrayscale() const { return contains("tiff-force-grayscale"); } - bool hasContentDeviation() const { - return contains("content-deviation") && !m_options["content-deviation"].isEmpty(); - } + bool hasTiffForceKeepColorSpace() const { return contains("tiff-force-keep-color-space"); } - bool hasContentDetection() const { - return !contains("disable-content-detection"); - } + bool hasWindowTitle() const { return contains("window-title") && !m_options["window-title"].isEmpty(); } - bool hasContentText() const { - return !contains("disable-content-text-mask"); - } + bool hasPageDetectionBox() const { + return contains("page-detection-box") && !m_options["page-detection-box"].isEmpty(); + } - bool hasColorMode() const { - return contains("color-mode") && !m_options["color-mode"].isEmpty(); - } + bool hasPageDetectionTolerance() const { + return contains("page-detection-tolerance") && !m_options["page-detection-tolerance"].isEmpty(); + } - bool hasDefaultColorMode() const { - return contains("default-color-mode") && !m_options["default-color-mode"].isEmpty(); - } + bool hasDisableCheckOutput() const { return contains("disable-check-output"); } - bool hasPictureShape() const { - return contains("picture-shape") && !m_options["picture-shape"].isEmpty(); - } + page_split::LayoutType getLayout() const { return m_layoutType; } - bool hasCutMargins() const { - return contains("cut-margins"); - } + Qt::LayoutDirection getLayoutDirection() const { return m_layoutDirection; } - bool hasNormalizeIllumination() const { - return contains("normalize-illumination"); - } + output::ColorMode getColorMode() const { return m_colorMode; } - bool hasThreshold() const { - return contains("threshold") && !m_options["threshold"].isEmpty(); - } + output::ColorMode getDefaultColorMode() const { return m_defaultColorMode; } - bool hasDespeckle() const { - return contains("despeckle") && !m_options["despeckle"].isEmpty(); - } + output::PictureShape getPictureShape() const { return m_pictureShape; } - bool hasDewarping() const { - return contains("dewarping"); - } + Dpi getInputDpi() const { return m_dpi; } - bool hasMatchLayoutTolerance() const { - return contains("match-layout-tolerance") && !m_options["match-layout-tolerance"].isEmpty(); - } + Dpi getOutputDpi() const { return m_outputDpi; } - bool hasDepthPerception() const { - return contains("depth-perception") && !m_options["depth-perception"].isEmpty(); - } + Dpi getDefaultOutputDpi() const { return m_defaultOutputDpi; } - bool hasTiffForceRGB() const { - return contains("tiff-force-rgb"); - } + Margins getMargins() const { return m_margins; } - bool hasTiffForceGrayscale() const { - return contains("tiff-force-grayscale"); - } + Margins getDefaultMargins() const { return m_defaultMargins; } - bool hasTiffForceKeepColorSpace() const { - return contains("tiff-force-keep-color-space"); - } + Margins getPageBorders() const { return m_pageBorders; } - bool hasWindowTitle() const { - return contains("window-title") && !m_options["window-title"].isEmpty(); - } + page_layout::Alignment getAlignment() const { return m_alignment; } - bool hasPageDetectionBox() const { - return contains("page-detection-box") && !m_options["page-detection-box"].isEmpty(); - } + Despeckle::Level getContentDetection() const { return m_contentDetection; } - bool hasPageDetectionTolerance() const { - return contains("page-detection-tolerance") && !m_options["page-detection-tolerance"].isEmpty(); - } + QRectF getContentRect() const { return m_contentRect; } - bool hasDisableCheckOutput() const { - return contains("disable-check-output"); - } + double getContentDeviation() const { return m_contentDeviation; } - page_split::LayoutType getLayout() const { - return m_layoutType; - } + Orientation getOrientation() const { return m_orientation; } - Qt::LayoutDirection getLayoutDirection() const { - return m_layoutDirection; - } + int getThreshold() const { return m_threshold; } - output::ColorMode getColorMode() const { - return m_colorMode; - } + double getDeskewAngle() const { return m_deskewAngle; } - output::ColorMode getDefaultColorMode() const { - return m_defaultColorMode; - } + AutoManualMode getDeskewMode() const { return m_deskewMode; } - output::PictureShape getPictureShape() const { - return m_pictureShape; - } + double getSkewDeviation() const { return m_skewDeviation; } - Dpi getInputDpi() const { - return m_dpi; - } + int getStartFilterIdx() const { return m_startFilterIdx; } - Dpi getOutputDpi() const { - return m_outputDpi; - } + int getEndFilterIdx() const { return m_endFilterIdx; } - Dpi getDefaultOutputDpi() const { - return m_defaultOutputDpi; - } + output::DewarpingOptions getDewarpingMode() const { return m_dewarpingOptions; } - Margins getMargins() const { - return m_margins; - } + double getDespeckleLevel() const { return m_despeckleLevel; } - Margins getDefaultMargins() const { - return m_defaultMargins; - } + output::DepthPerception getDepthPerception() const { return m_depthPerception; } - Margins getPageBorders() const { - return m_pageBorders; - } + float getMatchLayoutTolerance() const { return m_matchLayoutTolerance; } - page_layout::Alignment getAlignment() const { - return m_alignment; - } + QString getLanguage() const { return m_language; } - Despeckle::Level getContentDetection() const { - return m_contentDetection; - } + QString getWindowTitle() const { return m_windowTitle; } - QRectF getContentRect() const { - return m_contentRect; - } + QSizeF getPageDetectionBox() const { return m_pageDetectionBox; } - double getContentDeviation() const { - return m_contentDeviation; - } - - Orientation getOrientation() const { - return m_orientation; - } - - int getThreshold() const { - return m_threshold; - } - - double getDeskewAngle() const { - return m_deskewAngle; - } - - AutoManualMode getDeskewMode() const { - return m_deskewMode; - } - - double getSkewDeviation() const { - return m_skewDeviation; - } - - int getStartFilterIdx() const { - return m_startFilterIdx; - } - - int getEndFilterIdx() const { - return m_endFilterIdx; - } - - output::DewarpingOptions getDewarpingMode() const { - return m_dewarpingOptions; - } - - output::DespeckleLevel getDespeckleLevel() const { - return m_despeckleLevel; - } - - output::DepthPerception getDepthPerception() const { - return m_depthPerception; - } - - float getMatchLayoutTolerance() const { - return m_matchLayoutTolerance; - } - - QString getLanguage() const { - return m_language; - } - - QString getWindowTitle() const { - return m_windowTitle; - } - - QSizeF getPageDetectionBox() const { - return m_pageDetectionBox; - } - - double getPageDetectionTolerance() const { - return m_pageDetectionTolerance; - } + double getPageDetectionTolerance() const { return m_pageDetectionTolerance; } - bool getDefaultNull() const { - return m_defaultNull; - } + bool getDefaultNull() const { return m_defaultNull; } - bool help() { - return m_options.contains("help"); - } + bool help() { return m_options.contains("help"); } - void printHelp(); + void printHelp(); -private: - CommandLine() : m_gui(true), m_global(false) { - } + private: + CommandLine() : m_gui(true), m_global(false) {} - static CommandLine m_globalInstance; - bool m_error; - bool m_gui; - bool m_global; - QString m_language; - QString m_windowTitle; - QSizeF m_pageDetectionBox; - double m_pageDetectionTolerance{0.1}; - bool m_defaultNull; + static CommandLine m_globalInstance; + bool m_error; + bool m_gui; + bool m_global; + QString m_language; + QString m_windowTitle; + QSizeF m_pageDetectionBox; + double m_pageDetectionTolerance{0.1}; + bool m_defaultNull; - bool isGlobal() { - return m_global; - } + bool isGlobal() { return m_global; } - void setGlobal() { - m_global = true; - } + void setGlobal() { m_global = true; } - bool contains(const QString& key) const { - return m_options.contains(key); - } + bool contains(const QString& key) const { return m_options.contains(key); } - QMap m_options; - QString m_projectFile; - QString m_outputProjectFile; - std::vector m_files; - std::vector m_images; - QString m_outputDirectory; + QMap m_options; + QString m_projectFile; + QString m_outputProjectFile; + std::vector m_files; + std::vector m_images; + QString m_outputDirectory; - page_split::LayoutType m_layoutType; - Qt::LayoutDirection m_layoutDirection; - output::ColorMode m_colorMode; - output::ColorMode m_defaultColorMode; - output::PictureShape m_pictureShape; - Dpi m_dpi; - Dpi m_outputDpi; - Dpi m_defaultOutputDpi; - Margins m_margins; - Margins m_defaultMargins; - Margins m_pageBorders; - page_layout::Alignment m_alignment; - Despeckle::Level m_contentDetection; - QRectF m_contentRect; - double m_contentDeviation{1.0}; - Orientation m_orientation; - int m_threshold{0}; - double m_deskewAngle{0.0}; - AutoManualMode m_deskewMode; - double m_skewDeviation{5.0}; - int m_startFilterIdx{0}; - int m_endFilterIdx{5}; - output::DewarpingOptions m_dewarpingOptions; - output::DespeckleLevel m_despeckleLevel; - output::DepthPerception m_depthPerception; - float m_matchLayoutTolerance{0.2f}; + page_split::LayoutType m_layoutType; + Qt::LayoutDirection m_layoutDirection; + output::ColorMode m_colorMode; + output::ColorMode m_defaultColorMode; + output::PictureShape m_pictureShape; + Dpi m_dpi; + Dpi m_outputDpi; + Dpi m_defaultOutputDpi; + Margins m_margins; + Margins m_defaultMargins; + Margins m_pageBorders; + page_layout::Alignment m_alignment; + Despeckle::Level m_contentDetection; + QRectF m_contentRect; + double m_contentDeviation{1.0}; + Orientation m_orientation; + int m_threshold{0}; + double m_deskewAngle{0.0}; + AutoManualMode m_deskewMode; + double m_skewDeviation{5.0}; + int m_startFilterIdx{0}; + int m_endFilterIdx{5}; + output::DewarpingOptions m_dewarpingOptions; + double m_despeckleLevel{2.0}; + output::DepthPerception m_depthPerception; + float m_matchLayoutTolerance{0.2f}; - bool parseCli(const QStringList& argv); + bool parseCli(const QStringList& argv); - void addImage(const QString& path); + void addImage(const QString& path); - void setup(); + void setup(); - page_split::LayoutType fetchLayoutType(); + page_split::LayoutType fetchLayoutType(); - output::ColorMode fetchColorMode(); + output::ColorMode fetchColorMode(); - output::ColorMode fetchDefaultColorMode(); + output::ColorMode fetchDefaultColorMode(); - output::PictureShape fetchPictureShape(); + output::PictureShape fetchPictureShape(); - Qt::LayoutDirection fetchLayoutDirection(); + Qt::LayoutDirection fetchLayoutDirection(); - Dpi fetchDpi(QString oname = "dpi"); + Dpi fetchDpi(QString oname = "dpi"); - Margins fetchMargins(QString base = "margins", Margins def = Margins(10.0, 5.0, 10.0, 5.0)); + Margins fetchMargins(QString base = "margins", Margins def = Margins(10.0, 5.0, 10.0, 5.0)); - Margins fetchPageBorders() { - return fetchMargins("page-borders", Margins(0, 0, 0, 0)); - } + Margins fetchPageBorders() { return fetchMargins("page-borders", Margins(0, 0, 0, 0)); } - page_layout::Alignment fetchAlignment(); + page_layout::Alignment fetchAlignment(); - Despeckle::Level fetchContentDetection(); + Despeckle::Level fetchContentDetection(); - QRectF fetchContentRect(); + QRectF fetchContentRect(); - double fetchContentDeviation(); + double fetchContentDeviation(); - Orientation fetchOrientation(); + Orientation fetchOrientation(); - QString fetchOutputProjectFile(); + QString fetchOutputProjectFile(); - int fetchThreshold(); + int fetchThreshold(); - double fetchDeskewAngle(); + double fetchDeskewAngle(); - AutoManualMode fetchDeskewMode(); + AutoManualMode fetchDeskewMode(); - double fetchSkewDeviation(); + double fetchSkewDeviation(); - int fetchStartFilterIdx(); + int fetchStartFilterIdx(); - int fetchEndFilterIdx(); + int fetchEndFilterIdx(); - output::DewarpingMode fetchDewarpingMode(); + output::DewarpingMode fetchDewarpingMode(); - output::DespeckleLevel fetchDespeckleLevel(); + double fetchDespeckleLevel(); - output::DepthPerception fetchDepthPerception(); + output::DepthPerception fetchDepthPerception(); - float fetchMatchLayoutTolerance(); + float fetchMatchLayoutTolerance(); - QString fetchLanguage() const; + QString fetchLanguage() const; - QString fetchWindowTitle() const; + QString fetchWindowTitle() const; - QSizeF fetchPageDetectionBox() const; + QSizeF fetchPageDetectionBox() const; - double fetchPageDetectionTolerance() const; + double fetchPageDetectionTolerance() const; - bool fetchDefaultNull(); + bool fetchDefaultNull(); }; diff --git a/CompositeCacheDrivenTask.h b/CompositeCacheDrivenTask.h index 63f7b495b..ec7946b07 100644 --- a/CompositeCacheDrivenTask.h +++ b/CompositeCacheDrivenTask.h @@ -25,10 +25,10 @@ class PageInfo; class AbstractFilterDataCollector; class CompositeCacheDrivenTask : public ref_countable { -public: - ~CompositeCacheDrivenTask() override = default; + public: + ~CompositeCacheDrivenTask() override = default; - virtual void process(const PageInfo& page_info, AbstractFilterDataCollector* collector) = 0; + virtual void process(const PageInfo& page_info, AbstractFilterDataCollector* collector) = 0; }; diff --git a/ConsoleBatch.cpp b/ConsoleBatch.cpp index 65876e741..25abea510 100644 --- a/ConsoleBatch.cpp +++ b/ConsoleBatch.cpp @@ -18,413 +18,411 @@ along with this program. If not, see . */ -#include -#include #include +#include +#include -#include "Utils.h" -#include "ProjectPages.h" -#include "PageSelectionAccessor.h" -#include "StageSequence.h" -#include "ProcessingTaskQueue.h" #include "FileNameDisambiguator.h" +#include "LoadFileTask.h" #include "OutputFileNameGenerator.h" +#include "PageSelectionAccessor.h" #include "PageSequence.h" -#include "LoadFileTask.h" -#include "ProjectWriter.h" +#include "ProcessingTaskQueue.h" +#include "ProjectPages.h" #include "ProjectReader.h" +#include "ProjectWriter.h" +#include "StageSequence.h" +#include "Utils.h" +#include "filters/deskew/CacheDrivenTask.h" +#include "filters/deskew/Task.h" +#include "filters/fix_orientation/CacheDrivenTask.h" #include "filters/fix_orientation/Settings.h" #include "filters/fix_orientation/Task.h" -#include "filters/fix_orientation/CacheDrivenTask.h" +#include "filters/output/CacheDrivenTask.h" +#include "filters/output/Settings.h" +#include "filters/output/Task.h" +#include "filters/page_layout/CacheDrivenTask.h" +#include "filters/page_layout/Task.h" +#include "filters/page_split/CacheDrivenTask.h" #include "filters/page_split/Settings.h" #include "filters/page_split/Task.h" -#include "filters/page_split/CacheDrivenTask.h" -#include "filters/deskew/Task.h" -#include "filters/deskew/CacheDrivenTask.h" -#include "filters/select_content/Task.h" #include "filters/select_content/CacheDrivenTask.h" -#include "filters/page_layout/Task.h" -#include "filters/page_layout/CacheDrivenTask.h" -#include "filters/output/Settings.h" -#include "filters/output/Task.h" -#include "filters/output/CacheDrivenTask.h" +#include "filters/select_content/Task.h" #include "ConsoleBatch.h" ConsoleBatch::ConsoleBatch(const std::vector& images, const QString& output_directory, const Qt::LayoutDirection layout) - : batch(true), - debug(true), - m_ptrDisambiguator(new FileNameDisambiguator), - m_ptrPages(new ProjectPages(images, ProjectPages::AUTO_PAGES, layout)) { - const PageSelectionAccessor accessor(nullptr); // Won't really be used anyway. - m_ptrStages = make_intrusive(m_ptrPages, accessor); - // m_ptrThumbnailCache = make_intrusive(output_dir+"/cache/thumbs", - // QSize(200,200), 40, 5); - m_ptrThumbnailCache = Utils::createThumbnailCache(output_directory); - m_outFileNameGen = OutputFileNameGenerator(m_ptrDisambiguator, output_directory, m_ptrPages->layoutDirection()); + : batch(true), + debug(true), + m_disambiguator(new FileNameDisambiguator), + m_pages(new ProjectPages(images, ProjectPages::AUTO_PAGES, layout)) { + const PageSelectionAccessor accessor(nullptr); // Won't really be used anyway. + m_stages = make_intrusive(m_pages, accessor); + // m_thumbnailCache = make_intrusive(output_dir+"/cache/thumbs", + // QSize(200,200), 40, 5); + m_thumbnailCache = Utils::createThumbnailCache(output_directory); + m_outFileNameGen = OutputFileNameGenerator(m_disambiguator, output_directory, m_pages->layoutDirection()); } ConsoleBatch::ConsoleBatch(const QString project_file) : batch(true), debug(true) { - QFile file(project_file); - if (!file.open(QIODevice::ReadOnly)) { - throw std::runtime_error("ConsoleBatch: Unable to open the project file."); - } - - QDomDocument doc; - if (!doc.setContent(&file)) { - throw std::runtime_error("ConsoleBatch: The project file is broken."); - } - - file.close(); - - m_ptrReader = std::make_unique(doc); - m_ptrPages = m_ptrReader->pages(); - - const PageSelectionAccessor accessor(nullptr); // Won't be used anyway. - m_ptrDisambiguator = m_ptrReader->namingDisambiguator(); - - m_ptrStages = make_intrusive(m_ptrPages, accessor); - m_ptrReader->readFilterSettings(m_ptrStages->filters()); - - const CommandLine& cli = CommandLine::get(); - QString output_directory = m_ptrReader->outputDirectory(); - if (!cli.outputDirectory().isEmpty()) { - output_directory = cli.outputDirectory(); - } - // m_ptrThumbnailCache = make_intrusive< // ThumbnailPixmapCache>(output_directory+"/cache/thumbs", - // QSize(200,200), 40, 5); - m_ptrThumbnailCache = Utils::createThumbnailCache(output_directory); - m_outFileNameGen = OutputFileNameGenerator(m_ptrDisambiguator, output_directory, m_ptrPages->layoutDirection()); + QFile file(project_file); + if (!file.open(QIODevice::ReadOnly)) { + throw std::runtime_error("ConsoleBatch: Unable to open the project file."); + } + + QDomDocument doc; + if (!doc.setContent(&file)) { + throw std::runtime_error("ConsoleBatch: The project file is broken."); + } + + file.close(); + + m_reader = std::make_unique(doc); + m_pages = m_reader->pages(); + + const PageSelectionAccessor accessor(nullptr); // Won't be used anyway. + m_disambiguator = m_reader->namingDisambiguator(); + + m_stages = make_intrusive(m_pages, accessor); + m_reader->readFilterSettings(m_stages->filters()); + + const CommandLine& cli = CommandLine::get(); + QString output_directory = m_reader->outputDirectory(); + if (!cli.outputDirectory().isEmpty()) { + output_directory = cli.outputDirectory(); + } + // m_thumbnailCache = make_intrusive< // ThumbnailPixmapCache>(output_directory+"/cache/thumbs", + // QSize(200,200), 40, 5); + m_thumbnailCache = Utils::createThumbnailCache(output_directory); + m_outFileNameGen = OutputFileNameGenerator(m_disambiguator, output_directory, m_pages->layoutDirection()); } BackgroundTaskPtr ConsoleBatch::createCompositeTask(const PageInfo& page, const int last_filter_idx) { - intrusive_ptr fix_orientation_task; - intrusive_ptr page_split_task; - intrusive_ptr deskew_task; - intrusive_ptr select_content_task; - intrusive_ptr page_layout_task; - intrusive_ptr output_task; - - if (batch) { - debug = false; - } - - if (last_filter_idx >= m_ptrStages->outputFilterIdx()) { - output_task = m_ptrStages->outputFilter()->createTask(page.id(), m_ptrThumbnailCache, m_outFileNameGen, batch, - debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->pageLayoutFilterIdx()) { - page_layout_task = m_ptrStages->pageLayoutFilter()->createTask(page.id(), output_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->selectContentFilterIdx()) { - select_content_task = m_ptrStages->selectContentFilter()->createTask(page.id(), page_layout_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->deskewFilterIdx()) { - deskew_task = m_ptrStages->deskewFilter()->createTask(page.id(), select_content_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->pageSplitFilterIdx()) { - page_split_task = m_ptrStages->pageSplitFilter()->createTask(page, deskew_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->fixOrientationFilterIdx()) { - fix_orientation_task = m_ptrStages->fixOrientationFilter()->createTask(page.id(), page_split_task, batch); - debug = false; - } - assert(fix_orientation_task); - - return make_intrusive(BackgroundTask::BATCH, page, m_ptrThumbnailCache, m_ptrPages, - fix_orientation_task); + intrusive_ptr fix_orientation_task; + intrusive_ptr page_split_task; + intrusive_ptr deskew_task; + intrusive_ptr select_content_task; + intrusive_ptr page_layout_task; + intrusive_ptr output_task; + + if (batch) { + debug = false; + } + + if (last_filter_idx >= m_stages->outputFilterIdx()) { + output_task = m_stages->outputFilter()->createTask(page.id(), m_thumbnailCache, m_outFileNameGen, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->pageLayoutFilterIdx()) { + page_layout_task = m_stages->pageLayoutFilter()->createTask(page.id(), output_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->selectContentFilterIdx()) { + select_content_task = m_stages->selectContentFilter()->createTask(page.id(), page_layout_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->deskewFilterIdx()) { + deskew_task = m_stages->deskewFilter()->createTask(page.id(), select_content_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->pageSplitFilterIdx()) { + page_split_task = m_stages->pageSplitFilter()->createTask(page, deskew_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->fixOrientationFilterIdx()) { + fix_orientation_task = m_stages->fixOrientationFilter()->createTask(page.id(), page_split_task, batch); + debug = false; + } + assert(fix_orientation_task); + + return make_intrusive(BackgroundTask::BATCH, page, m_thumbnailCache, m_pages, fix_orientation_task); } // ConsoleBatch::createCompositeTask // process the image vector **images** and save output to **output_dir** void ConsoleBatch::process() { - const CommandLine& cli = CommandLine::get(); + const CommandLine& cli = CommandLine::get(); - int startFilterIdx = m_ptrStages->fixOrientationFilterIdx(); - if (cli.hasStartFilterIdx()) { - unsigned int sf = cli.getStartFilterIdx(); - if ((sf < 0) || (sf >= m_ptrStages->filters().size())) { - throw std::runtime_error("ConsoleBatch: Start filter out of range"); - } - startFilterIdx = sf; + int startFilterIdx = m_stages->fixOrientationFilterIdx(); + if (cli.hasStartFilterIdx()) { + unsigned int sf = cli.getStartFilterIdx(); + if ((sf < 0) || (sf >= m_stages->filters().size())) { + throw std::runtime_error("ConsoleBatch: Start filter out of range"); } - - int endFilterIdx = m_ptrStages->outputFilterIdx(); - if (cli.hasEndFilterIdx()) { - unsigned int ef = cli.getEndFilterIdx(); - if ((ef < 0) || (ef >= m_ptrStages->filters().size())) { - throw std::runtime_error("ConsoleBatch: End filter out of range"); - } - endFilterIdx = ef; + startFilterIdx = sf; + } + + int endFilterIdx = m_stages->outputFilterIdx(); + if (cli.hasEndFilterIdx()) { + unsigned int ef = cli.getEndFilterIdx(); + if ((ef < 0) || (ef >= m_stages->filters().size())) { + throw std::runtime_error("ConsoleBatch: End filter out of range"); } + endFilterIdx = ef; + } - for (int j = startFilterIdx; j <= endFilterIdx; j++) { - if (cli.isVerbose()) { - std::cout << "Filter: " << (j + 1) << "\n"; - } - - PageSequence page_sequence = m_ptrPages->toPageSequence(PAGE_VIEW); - setupFilter(j, page_sequence.selectAll()); - for (unsigned i = 0; i < page_sequence.numPages(); i++) { - PageInfo page = page_sequence.pageAt(i); - if (cli.isVerbose()) { - std::cout << "\tProcessing: " << page.imageId().filePath().toLatin1().constData() << "\n"; - } - BackgroundTaskPtr bgTask = createCompositeTask(page, j); - (*bgTask)(); - } + for (int j = startFilterIdx; j <= endFilterIdx; j++) { + if (cli.isVerbose()) { + std::cout << "Filter: " << (j + 1) << "\n"; } - for (int j = endFilterIdx + 1; j <= m_ptrStages->count(); j++) { - PageSequence page_sequence = m_ptrPages->toPageSequence(PAGE_VIEW); - setupFilter(j, page_sequence.selectAll()); + PageSequence page_sequence = m_pages->toPageSequence(PAGE_VIEW); + setupFilter(j, page_sequence.selectAll()); + for (unsigned i = 0; i < page_sequence.numPages(); i++) { + PageInfo page = page_sequence.pageAt(i); + if (cli.isVerbose()) { + std::cout << "\tProcessing: " << page.imageId().filePath().toLatin1().constData() << "\n"; + } + BackgroundTaskPtr bgTask = createCompositeTask(page, j); + (*bgTask)(); } + } - for (int j = 0; j <= endFilterIdx; j++) { - m_ptrStages->filterAt(j)->updateStatistics(); - } + for (int j = endFilterIdx + 1; j <= m_stages->count(); j++) { + PageSequence page_sequence = m_pages->toPageSequence(PAGE_VIEW); + setupFilter(j, page_sequence.selectAll()); + } + + for (int j = 0; j <= endFilterIdx; j++) { + m_stages->filterAt(j)->updateStatistics(); + } } // ConsoleBatch::process void ConsoleBatch::saveProject(const QString project_file) { - PageInfo fpage = m_ptrPages->toPageSequence(PAGE_VIEW).pageAt(0); - SelectedPage sPage(fpage.id(), IMAGE_VIEW); - ProjectWriter writer(m_ptrPages, sPage, m_outFileNameGen); - writer.write(project_file, m_ptrStages->filters()); + PageInfo fpage = m_pages->toPageSequence(PAGE_VIEW).pageAt(0); + SelectedPage sPage(fpage.id(), IMAGE_VIEW); + ProjectWriter writer(m_pages, sPage, m_outFileNameGen); + writer.write(project_file, m_stages->filters()); } void ConsoleBatch::setupFilter(int idx, std::set allPages) { - if (idx == m_ptrStages->fixOrientationFilterIdx()) { - setupFixOrientation(allPages); - } else if (idx == m_ptrStages->pageSplitFilterIdx()) { - setupPageSplit(allPages); - } else if (idx == m_ptrStages->deskewFilterIdx()) { - setupDeskew(allPages); - } else if (idx == m_ptrStages->selectContentFilterIdx()) { - setupSelectContent(allPages); - } else if (idx == m_ptrStages->pageLayoutFilterIdx()) { - setupPageLayout(allPages); - } else if (idx == m_ptrStages->outputFilterIdx()) { - setupOutput(allPages); - } + if (idx == m_stages->fixOrientationFilterIdx()) { + setupFixOrientation(allPages); + } else if (idx == m_stages->pageSplitFilterIdx()) { + setupPageSplit(allPages); + } else if (idx == m_stages->deskewFilterIdx()) { + setupDeskew(allPages); + } else if (idx == m_stages->selectContentFilterIdx()) { + setupSelectContent(allPages); + } else if (idx == m_stages->pageLayoutFilterIdx()) { + setupPageLayout(allPages); + } else if (idx == m_stages->outputFilterIdx()) { + setupOutput(allPages); + } } void ConsoleBatch::setupFixOrientation(std::set allPages) { - intrusive_ptr fix_orientation = m_ptrStages->fixOrientationFilter(); - const CommandLine& cli = CommandLine::get(); - - for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { - PageId page = *i; - - OrthogonalRotation rotation; - // FIX ORIENTATION FILTER - if (cli.hasOrientation()) { - switch (cli.getOrientation()) { - case CommandLine::LEFT: - rotation.prevClockwiseDirection(); - break; - case CommandLine::RIGHT: - rotation.nextClockwiseDirection(); - break; - case CommandLine::UPSIDEDOWN: - rotation.nextClockwiseDirection(); - rotation.nextClockwiseDirection(); - break; - default: - break; - } - fix_orientation->getSettings()->applyRotation(page.imageId(), rotation); - } + intrusive_ptr fix_orientation = m_stages->fixOrientationFilter(); + const CommandLine& cli = CommandLine::get(); + + for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { + PageId page = *i; + + OrthogonalRotation rotation; + // FIX ORIENTATION FILTER + if (cli.hasOrientation()) { + switch (cli.getOrientation()) { + case CommandLine::LEFT: + rotation.prevClockwiseDirection(); + break; + case CommandLine::RIGHT: + rotation.nextClockwiseDirection(); + break; + case CommandLine::UPSIDEDOWN: + rotation.nextClockwiseDirection(); + rotation.nextClockwiseDirection(); + break; + default: + break; + } + fix_orientation->getSettings()->applyRotation(page.imageId(), rotation); } + } } void ConsoleBatch::setupPageSplit(std::set allPages) { - intrusive_ptr page_split = m_ptrStages->pageSplitFilter(); - const CommandLine& cli = CommandLine::get(); + intrusive_ptr page_split = m_stages->pageSplitFilter(); + const CommandLine& cli = CommandLine::get(); - // PAGE SPLIT - if (cli.hasLayout()) { - page_split->getSettings()->setLayoutTypeForAllPages(cli.getLayout()); - } + // PAGE SPLIT + if (cli.hasLayout()) { + page_split->getSettings()->setLayoutTypeForAllPages(cli.getLayout()); + } } void ConsoleBatch::setupDeskew(std::set allPages) { - intrusive_ptr deskew = m_ptrStages->deskewFilter(); - const CommandLine& cli = CommandLine::get(); - - for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { - PageId page = *i; - // DESKEW FILTER - OrthogonalRotation rotation; - if (cli.hasDeskewAngle() || cli.hasDeskew()) { - double angle = 0.0; - if (cli.hasDeskewAngle()) { - angle = cli.getDeskewAngle(); - } - deskew::Dependencies deps(QPolygonF(), rotation); - deskew::Params params(angle, deps, MODE_MANUAL); - deskew->getSettings()->setPageParams(page, params); - } + intrusive_ptr deskew = m_stages->deskewFilter(); + const CommandLine& cli = CommandLine::get(); + + for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { + PageId page = *i; + // DESKEW FILTER + OrthogonalRotation rotation; + if (cli.hasDeskewAngle() || cli.hasDeskew()) { + double angle = 0.0; + if (cli.hasDeskewAngle()) { + angle = cli.getDeskewAngle(); + } + deskew::Dependencies deps(QPolygonF(), rotation); + deskew::Params params(angle, deps, MODE_MANUAL); + deskew->getSettings()->setPageParams(page, params); } + } - if (cli.hasSkewDeviation()) { - deskew->getSettings()->setMaxDeviation(cli.getSkewDeviation()); - } + if (cli.hasSkewDeviation()) { + deskew->getSettings()->setMaxDeviation(cli.getSkewDeviation()); + } } void ConsoleBatch::setupSelectContent(std::set allPages) { - intrusive_ptr select_content = m_ptrStages->selectContentFilter(); - const CommandLine& cli = CommandLine::get(); - - for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { - PageId page = *i; - select_content::Dependencies deps; - - select_content::Params params(deps); - std::unique_ptr old_params = select_content->getSettings()->getPageParams(page); + intrusive_ptr select_content = m_stages->selectContentFilter(); + const CommandLine& cli = CommandLine::get(); - if (old_params) { - params = *old_params; - } - // SELECT CONTENT FILTER - if (cli.hasContentRect()) { - params.setContentRect(cli.getContentRect()); - } + for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { + PageId page = *i; + select_content::Dependencies deps; - params.setContentDetect(cli.isContentDetectionEnabled()); - params.setPageDetect(cli.isPageDetectionEnabled()); - params.setFineTuneCorners(cli.isFineTuningEnabled()); - if (cli.hasPageBorders()) { - params.setPageBorders(cli.getPageBorders()); - } + select_content::Params params(deps); + std::unique_ptr old_params = select_content->getSettings()->getPageParams(page); - select_content->getSettings()->setPageParams(page, params); + if (old_params) { + params = *old_params; } - - if (cli.hasContentDeviation()) { - select_content->getSettings()->setMaxDeviation(cli.getContentDeviation()); + // SELECT CONTENT FILTER + if (cli.hasContentRect()) { + params.setContentRect(cli.getContentRect()); } - if (cli.hasPageDetectionBox()) { - select_content->getSettings()->setPageDetectionBox(cli.getPageDetectionBox()); + params.setContentDetect(cli.isContentDetectionEnabled()); + params.setPageDetect(cli.isPageDetectionEnabled()); + params.setFineTuneCorners(cli.isFineTuningEnabled()); + if (cli.hasPageBorders()) { + params.setPageBorders(cli.getPageBorders()); } - if (cli.hasPageDetectionTolerance()) { - select_content->getSettings()->setPageDetectionTolerance(cli.getPageDetectionTolerance()); - } + select_content->getSettings()->setPageParams(page, params); + } + + if (cli.hasContentDeviation()) { + select_content->getSettings()->setMaxDeviation(cli.getContentDeviation()); + } + + if (cli.hasPageDetectionBox()) { + select_content->getSettings()->setPageDetectionBox(cli.getPageDetectionBox()); + } + + if (cli.hasPageDetectionTolerance()) { + select_content->getSettings()->setPageDetectionTolerance(cli.getPageDetectionTolerance()); + } } // ConsoleBatch::setupSelectContent void ConsoleBatch::setupPageLayout(std::set allPages) { - intrusive_ptr page_layout = m_ptrStages->pageLayoutFilter(); - const CommandLine& cli = CommandLine::get(); - QMap img_cache; - - for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { - PageId page = *i; - - // PAGE LAYOUT FILTER - page_layout::Alignment alignment = cli.getAlignment(); - if (cli.hasMatchLayoutTolerance()) { - const QString path = page.imageId().filePath(); - if (!img_cache.contains(path)) { - QImage img(path); - img_cache[path] = float(img.width()) / float(img.height()); - } - float imgAspectRatio = img_cache[path]; - float tolerance = cli.getMatchLayoutTolerance(); - std::vector diffs; - for (std::set::iterator pi = allPages.begin(); pi != allPages.end(); pi++) { - ImageId pimageId = pi->imageId(); - QString ppath = pimageId.filePath(); - if (!img_cache.contains(ppath)) { - QImage img(ppath); - img_cache[ppath] = float(img.width()) / float(img.height()); - } - float pimgAspectRatio = img_cache[ppath]; - float diff = imgAspectRatio - pimgAspectRatio; - if (diff < 0.0) { - diff *= -1; - } - diffs.push_back(diff); - } - unsigned bad_diffs = 0; - for (unsigned j = 0; j < diffs.size(); j++) { - if (diffs[j] > tolerance) { - bad_diffs += 1; - } - } - if (bad_diffs > (diffs.size() / 2)) { - alignment.setNull(true); - } + intrusive_ptr page_layout = m_stages->pageLayoutFilter(); + const CommandLine& cli = CommandLine::get(); + QMap img_cache; + + for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { + PageId page = *i; + + // PAGE LAYOUT FILTER + page_layout::Alignment alignment = cli.getAlignment(); + if (cli.hasMatchLayoutTolerance()) { + const QString path = page.imageId().filePath(); + if (!img_cache.contains(path)) { + QImage img(path); + img_cache[path] = float(img.width()) / float(img.height()); + } + float imgAspectRatio = img_cache[path]; + float tolerance = cli.getMatchLayoutTolerance(); + std::vector diffs; + for (std::set::iterator pi = allPages.begin(); pi != allPages.end(); pi++) { + ImageId pimageId = pi->imageId(); + QString ppath = pimageId.filePath(); + if (!img_cache.contains(ppath)) { + QImage img(ppath); + img_cache[ppath] = float(img.width()) / float(img.height()); } - if (cli.hasMargins()) { - page_layout->getSettings()->setHardMarginsMM(page, cli.getMargins()); + float pimgAspectRatio = img_cache[ppath]; + float diff = imgAspectRatio - pimgAspectRatio; + if (diff < 0.0) { + diff *= -1; } - if (cli.hasAlignment()) { - page_layout->getSettings()->setPageAlignment(page, alignment); + diffs.push_back(diff); + } + unsigned bad_diffs = 0; + for (unsigned j = 0; j < diffs.size(); j++) { + if (diffs[j] > tolerance) { + bad_diffs += 1; } + } + if (bad_diffs > (diffs.size() / 2)) { + alignment.setNull(true); + } + } + if (cli.hasMargins()) { + page_layout->getSettings()->setHardMarginsMM(page, cli.getMargins()); } + if (cli.hasAlignment()) { + page_layout->getSettings()->setPageAlignment(page, alignment); + } + } } // ConsoleBatch::setupPageLayout void ConsoleBatch::setupOutput(std::set allPages) { - intrusive_ptr output = m_ptrStages->outputFilter(); - const CommandLine& cli = CommandLine::get(); - - for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { - PageId page = *i; + intrusive_ptr output = m_stages->outputFilter(); + const CommandLine& cli = CommandLine::get(); - // OUTPUT FILTER - output::Params params(output->getSettings()->getParams(page)); - if (cli.hasOutputDpi()) { - Dpi outputDpi = cli.getOutputDpi(); - params.setOutputDpi(outputDpi); - } + for (std::set::iterator i = allPages.begin(); i != allPages.end(); i++) { + PageId page = *i; - if (cli.hasPictureShape()) { - params.setPictureShape(cli.getPictureShape()); - } + // OUTPUT FILTER + output::Params params(output->getSettings()->getParams(page)); + if (cli.hasOutputDpi()) { + Dpi outputDpi = cli.getOutputDpi(); + params.setOutputDpi(outputDpi); + } - output::ColorParams colorParams = params.colorParams(); - if (cli.hasColorMode()) { - colorParams.setColorMode(cli.getColorMode()); - } + if (cli.hasPictureShape()) { + params.setPictureShape(cli.getPictureShape()); + } - if (cli.hasCutMargins() || cli.hasNormalizeIllumination()) { - output::ColorCommonOptions cgo; - if (cli.hasCutMargins()) { - cgo.setCutMargins(true); - } - if (cli.hasNormalizeIllumination()) { - cgo.setNormalizeIllumination(true); - } - colorParams.setColorCommonOptions(cgo); - } + output::ColorParams colorParams = params.colorParams(); + if (cli.hasColorMode()) { + colorParams.setColorMode(cli.getColorMode()); + } - if (cli.hasThreshold()) { - output::BlackWhiteOptions bwo; - bwo.setThresholdAdjustment(cli.getThreshold()); - colorParams.setBlackWhiteOptions(bwo); - } + if (cli.hasFillMargins() || cli.hasNormalizeIllumination()) { + output::ColorCommonOptions cgo; + if (cli.hasFillMargins()) { + cgo.setFillMargins(true); + } + if (cli.hasNormalizeIllumination()) { + cgo.setNormalizeIllumination(true); + } + colorParams.setColorCommonOptions(cgo); + } - params.setColorParams(colorParams); + if (cli.hasThreshold()) { + output::BlackWhiteOptions bwo; + bwo.setThresholdAdjustment(cli.getThreshold()); + colorParams.setBlackWhiteOptions(bwo); + } - if (cli.hasDespeckle()) { - params.setDespeckleLevel(cli.getDespeckleLevel()); - } + params.setColorParams(colorParams); - if (cli.hasDewarping()) { - params.setDewarpingMode(cli.getDewarpingMode()); - } - if (cli.hasDepthPerception()) { - params.setDepthPerception(cli.getDepthPerception()); - } + if (cli.hasDespeckle()) { + params.setDespeckleLevel(cli.getDespeckleLevel()); + } - output->getSettings()->setParams(page, params); + if (cli.hasDewarping()) { + params.setDewarpingMode(cli.getDewarpingMode()); + } + if (cli.hasDepthPerception()) { + params.setDepthPerception(cli.getDepthPerception()); } + + output->getSettings()->setParams(page, params); + } } // ConsoleBatch::setupOutput diff --git a/ConsoleBatch.h b/ConsoleBatch.h index e6c12231b..00dad68dd 100644 --- a/ConsoleBatch.h +++ b/ConsoleBatch.h @@ -24,60 +24,59 @@ #include #include -#include "intrusive_ptr.h" #include "BackgroundTask.h" #include "FilterResult.h" +#include "ImageFileInfo.h" #include "OutputFileNameGenerator.h" #include "PageId.h" #include "PageInfo.h" +#include "PageSelectionAccessor.h" #include "PageView.h" #include "ProjectPages.h" -#include "ImageFileInfo.h" -#include "ThumbnailPixmapCache.h" -#include "OutputFileNameGenerator.h" -#include "StageSequence.h" -#include "PageSelectionAccessor.h" #include "ProjectReader.h" +#include "StageSequence.h" +#include "ThumbnailPixmapCache.h" +#include "intrusive_ptr.h" class ConsoleBatch { - // Member-wise copying is OK. -public: - ConsoleBatch(const std::vector& images, - const QString& output_directory, - const Qt::LayoutDirection layout); + // Member-wise copying is OK. + public: + ConsoleBatch(const std::vector& images, + const QString& output_directory, + const Qt::LayoutDirection layout); - ConsoleBatch(const QString project_file); + ConsoleBatch(const QString project_file); - void process(); + void process(); - void saveProject(const QString project_file); + void saveProject(const QString project_file); -private: - bool batch; - bool debug; - intrusive_ptr m_ptrDisambiguator; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrStages; - OutputFileNameGenerator m_outFileNameGen; - intrusive_ptr m_ptrThumbnailCache; - std::unique_ptr m_ptrReader; + private: + bool batch; + bool debug; + intrusive_ptr m_disambiguator; + intrusive_ptr m_pages; + intrusive_ptr m_stages; + OutputFileNameGenerator m_outFileNameGen; + intrusive_ptr m_thumbnailCache; + std::unique_ptr m_reader; - void setupFilter(int idx, std::set allPages); + void setupFilter(int idx, std::set allPages); - void setupFixOrientation(std::set allPages); + void setupFixOrientation(std::set allPages); - void setupPageSplit(std::set allPages); + void setupPageSplit(std::set allPages); - void setupDeskew(std::set allPages); + void setupDeskew(std::set allPages); - void setupSelectContent(std::set allPages); + void setupSelectContent(std::set allPages); - void setupPageLayout(std::set allPages); + void setupPageLayout(std::set allPages); - void setupOutput(std::set allPages); + void setupOutput(std::set allPages); - BackgroundTaskPtr createCompositeTask(const PageInfo& page, const int last_filter_idx); + BackgroundTaskPtr createCompositeTask(const PageInfo& page, const int last_filter_idx); }; diff --git a/ContentBoxPropagator.cpp b/ContentBoxPropagator.cpp index e2842975d..f7d6268c4 100644 --- a/ContentBoxPropagator.cpp +++ b/ContentBoxPropagator.cpp @@ -20,65 +20,57 @@ #include #include "CompositeCacheDrivenTask.h" -#include "ProjectPages.h" -#include "PageSequence.h" #include "ImageTransformation.h" -#include "filters/page_layout/Filter.h" +#include "PageSequence.h" +#include "ProjectPages.h" #include "filter_dc/ContentBoxCollector.h" +#include "filters/page_layout/Filter.h" class ContentBoxPropagator::Collector : public ContentBoxCollector { -public: - Collector(); + public: + Collector(); - void process(const ImageTransformation& xform, const QRectF& content_rect) override; + void process(const ImageTransformation& xform, const QRectF& content_rect) override; - bool collected() const { - return m_collected; - } + bool collected() const { return m_collected; } - const ImageTransformation& xform() const { - return m_xform; - } + const ImageTransformation& xform() const { return m_xform; } - const QRectF& contentRect() const { - return m_contentRect; - } + const QRectF& contentRect() const { return m_contentRect; } -private: - ImageTransformation m_xform; - QRectF m_contentRect; - bool m_collected; + private: + ImageTransformation m_xform; + QRectF m_contentRect; + bool m_collected; }; ContentBoxPropagator::ContentBoxPropagator(intrusive_ptr page_layout_filter, intrusive_ptr task) - : m_ptrPageLayoutFilter(std::move(page_layout_filter)), m_ptrTask(std::move(task)) { -} + : m_pageLayoutFilter(std::move(page_layout_filter)), m_task(std::move(task)) {} ContentBoxPropagator::~ContentBoxPropagator() = default; void ContentBoxPropagator::propagate(const ProjectPages& pages) { - const PageSequence sequence(pages.toPageSequence(PAGE_VIEW)); - - for (const PageInfo& page_info : sequence) { - Collector collector; - m_ptrTask->process(page_info, &collector); - if (collector.collected()) { - m_ptrPageLayoutFilter->setContentBox(page_info.id(), collector.xform(), collector.contentRect()); - } else { - m_ptrPageLayoutFilter->invalidateContentBox(page_info.id()); - } + const PageSequence sequence(pages.toPageSequence(PAGE_VIEW)); + + for (const PageInfo& page_info : sequence) { + Collector collector; + m_task->process(page_info, &collector); + if (collector.collected()) { + m_pageLayoutFilter->setContentBox(page_info.id(), collector.xform(), collector.contentRect()); + } else { + m_pageLayoutFilter->invalidateContentBox(page_info.id()); } + } } /*=================== ContentBoxPropagator::Collector ====================*/ -ContentBoxPropagator::Collector::Collector() : m_xform(QRectF(0, 0, 1, 1), Dpi(300, 300)), m_collected(false) { -} +ContentBoxPropagator::Collector::Collector() : m_xform(QRectF(0, 0, 1, 1), Dpi(300, 300)), m_collected(false) {} void ContentBoxPropagator::Collector::process(const ImageTransformation& xform, const QRectF& content_rect) { - m_xform = xform; - m_contentRect = content_rect; - m_collected = true; + m_xform = xform; + m_contentRect = content_rect; + m_collected = true; } diff --git a/ContentBoxPropagator.h b/ContentBoxPropagator.h index 561c707a6..c2acf3d98 100644 --- a/ContentBoxPropagator.h +++ b/ContentBoxPropagator.h @@ -19,8 +19,8 @@ #ifndef CONTENTBOXPROPAGATOR_H_ #define CONTENTBOXPROPAGATOR_H_ -#include "intrusive_ptr.h" #include +#include "intrusive_ptr.h" class CompositeCacheDrivenTask; class ProjectPages; @@ -39,19 +39,19 @@ class Filter; * there. */ class ContentBoxPropagator { -public: - ContentBoxPropagator(intrusive_ptr page_layout_filter, - intrusive_ptr task); + public: + ContentBoxPropagator(intrusive_ptr page_layout_filter, + intrusive_ptr task); - ~ContentBoxPropagator(); + ~ContentBoxPropagator(); - void propagate(const ProjectPages& pages); + void propagate(const ProjectPages& pages); -private: - class Collector; + private: + class Collector; - intrusive_ptr m_ptrPageLayoutFilter; - intrusive_ptr m_ptrTask; + intrusive_ptr m_pageLayoutFilter; + intrusive_ptr m_task; }; diff --git a/ContentSpanFinder.cpp b/ContentSpanFinder.cpp index c5b22e7b1..7d4bd3490 100644 --- a/ContentSpanFinder.cpp +++ b/ContentSpanFinder.cpp @@ -23,49 +23,49 @@ using namespace imageproc; void ContentSpanFinder::findImpl(const SlicedHistogram& histogram, const VirtualFunction& handler) const { - const auto hist_size = static_cast(histogram.size()); + const auto hist_size = static_cast(histogram.size()); - int i = 0; - int content_end = -m_minWhitespaceWidth; - int content_begin = content_end; + int i = 0; + int content_end = -m_minWhitespaceWidth; + int content_begin = content_end; - while (true) { - // Find the next content position. - for (; i < hist_size; ++i) { - if (histogram[i] != 0) { - break; - } - } - - if (i - content_end >= m_minWhitespaceWidth) { - // Whitespace is long enough to break the content block. + while (true) { + // Find the next content position. + for (; i < hist_size; ++i) { + if (histogram[i] != 0) { + break; + } + } - // Note that content_end is initialized to - // -m_minWhitespaceWidth to make this test - // pass on the first content block, in order to avoid - // growing a non existing content block. + if (i - content_end >= m_minWhitespaceWidth) { + // Whitespace is long enough to break the content block. - if (content_end - content_begin >= m_minContentWidth) { - handler(Span(content_begin, content_end)); - } + // Note that content_end is initialized to + // -m_minWhitespaceWidth to make this test + // pass on the first content block, in order to avoid + // growing a non existing content block. - content_begin = i; - } + if (content_end - content_begin >= m_minContentWidth) { + handler(Span(content_begin, content_end)); + } - if (i == hist_size) { - break; - } + content_begin = i; + } - // Find the next whitespace position. - for (; i < hist_size; ++i) { - if (histogram[i] == 0) { - break; - } - } - content_end = i; + if (i == hist_size) { + break; } - if (content_end - content_begin >= m_minContentWidth) { - handler(Span(content_begin, content_end)); + // Find the next whitespace position. + for (; i < hist_size; ++i) { + if (histogram[i] == 0) { + break; + } } + content_end = i; + } + + if (content_end - content_begin >= m_minContentWidth) { + handler(Span(content_begin, content_end)); + } } // ContentSpanFinder::findImpl diff --git a/ContentSpanFinder.h b/ContentSpanFinder.h index b2f06268a..8a5d6ea60 100644 --- a/ContentSpanFinder.h +++ b/ContentSpanFinder.h @@ -27,40 +27,35 @@ class SlicedHistogram; } class ContentSpanFinder { - // Member-wise copying is OK. -public: - ContentSpanFinder() : m_minContentWidth(1), m_minWhitespaceWidth(1) { - } + // Member-wise copying is OK. + public: + ContentSpanFinder() : m_minContentWidth(1), m_minWhitespaceWidth(1) {} - void setMinContentWidth(int value) { - m_minContentWidth = value; - } + void setMinContentWidth(int value) { m_minContentWidth = value; } - void setMinWhitespaceWidth(int value) { - m_minWhitespaceWidth = value; - } + void setMinWhitespaceWidth(int value) { m_minWhitespaceWidth = value; } - /** - * \brief Find content spans. - * - * Note that content blocks shorter than min-content-width are still - * allowed to merge with other content blocks, if whitespace that - * separates them is shorter than min-whitespace-width. - */ - template - void find(const imageproc::SlicedHistogram& histogram, T handler) const; + /** + * \brief Find content spans. + * + * Note that content blocks shorter than min-content-width are still + * allowed to merge with other content blocks, if whitespace that + * separates them is shorter than min-whitespace-width. + */ + template + void find(const imageproc::SlicedHistogram& histogram, T handler) const; -private: - void findImpl(const imageproc::SlicedHistogram& histogram, const VirtualFunction& handler) const; + private: + void findImpl(const imageproc::SlicedHistogram& histogram, const VirtualFunction& handler) const; - int m_minContentWidth; - int m_minWhitespaceWidth; + int m_minContentWidth; + int m_minWhitespaceWidth; }; -template +template void ContentSpanFinder::find(const imageproc::SlicedHistogram& histogram, Callable handler) const { - findImpl(histogram, ProxyFunction(handler)); + findImpl(histogram, ProxyFunction(handler)); } #endif // ifndef CONTENTSPANFINDER_H_ diff --git a/DarkScheme.cpp b/DarkScheme.cpp index 8898672b1..9bfe20353 100644 --- a/DarkScheme.cpp +++ b/DarkScheme.cpp @@ -1,72 +1,78 @@ -#include -#include #include "DarkScheme.h" +#include +#include +#include -std::unique_ptr DarkScheme::getPalette() const { - std::unique_ptr darkPalette(new QPalette()); +QPalette DarkScheme::getPalette() const { + QPalette darkPalette; - darkPalette->setColor(QPalette::Window, QColor(0x53, 0x53, 0x53)); - darkPalette->setColor(QPalette::WindowText, QColor(0xDD, 0xDD, 0xDD)); - darkPalette->setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x98, 0x98, 0x98)); - darkPalette->setColor(QPalette::Base, QColor(0x45, 0x45, 0x45)); - darkPalette->setColor(QPalette::Disabled, QPalette::Base, QColor(0x4D, 0x4D, 0x4D)); - darkPalette->setColor(QPalette::AlternateBase, darkPalette->color(QPalette::Window)); - darkPalette->setColor(QPalette::Disabled, QPalette::AlternateBase, - darkPalette->color(QPalette::Disabled, QPalette::Window)); - darkPalette->setColor(QPalette::ToolTipBase, QColor(0x70, 0x70, 0x70)); - darkPalette->setColor(QPalette::ToolTipText, darkPalette->color(QPalette::WindowText)); - darkPalette->setColor(QPalette::Text, darkPalette->color(QPalette::WindowText)); - darkPalette->setColor(QPalette::Disabled, QPalette::Text, - darkPalette->color(QPalette::Disabled, QPalette::WindowText)); - darkPalette->setColor(QPalette::Light, QColor(0x66, 0x66, 0x66)); - darkPalette->setColor(QPalette::Midlight, QColor(0x53, 0x53, 0x53)); - darkPalette->setColor(QPalette::Dark, QColor(0x40, 0x40, 0x40)); - darkPalette->setColor(QPalette::Mid, QColor(0x33, 0x33, 0x33)); - darkPalette->setColor(QPalette::Shadow, QColor(0x26, 0x26, 0x26)); - darkPalette->setColor(QPalette::Button, darkPalette->color(QPalette::Base)); - darkPalette->setColor(QPalette::Disabled, QPalette::Button, darkPalette->color(QPalette::Disabled, QPalette::Base)); - darkPalette->setColor(QPalette::ButtonText, darkPalette->color(QPalette::WindowText)); - darkPalette->setColor(QPalette::Disabled, QPalette::ButtonText, - darkPalette->color(QPalette::Disabled, QPalette::WindowText)); - darkPalette->setColor(QPalette::BrightText, QColor(0xFC, 0x52, 0x48)); - darkPalette->setColor(QPalette::Link, QColor(0x4F, 0x95, 0xFC)); - darkPalette->setColor(QPalette::Highlight, QColor(0x6B, 0x6B, 0x6B)); - darkPalette->setColor(QPalette::Disabled, QPalette::Highlight, QColor(0x5D, 0x5D, 0x5D)); - darkPalette->setColor(QPalette::HighlightedText, darkPalette->color(QPalette::WindowText)); - darkPalette->setColor(QPalette::Disabled, QPalette::HighlightedText, - darkPalette->color(QPalette::Disabled, QPalette::WindowText)); + darkPalette.setColor(QPalette::Window, QColor(0x53, 0x53, 0x53)); + darkPalette.setColor(QPalette::WindowText, QColor(0xDD, 0xDD, 0xDD)); + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x98, 0x98, 0x98)); + darkPalette.setColor(QPalette::Base, QColor(0x45, 0x45, 0x45)); + darkPalette.setColor(QPalette::Disabled, QPalette::Base, QColor(0x4D, 0x4D, 0x4D)); + darkPalette.setColor(QPalette::AlternateBase, darkPalette.color(QPalette::Window)); + darkPalette.setColor(QPalette::Disabled, QPalette::AlternateBase, + darkPalette.color(QPalette::Disabled, QPalette::Window)); + darkPalette.setColor(QPalette::ToolTipBase, QColor(0x70, 0x70, 0x70)); + darkPalette.setColor(QPalette::ToolTipText, darkPalette.color(QPalette::WindowText)); + darkPalette.setColor(QPalette::Text, darkPalette.color(QPalette::WindowText)); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, darkPalette.color(QPalette::Disabled, QPalette::WindowText)); + darkPalette.setColor(QPalette::Light, QColor(0x66, 0x66, 0x66)); + darkPalette.setColor(QPalette::Midlight, QColor(0x53, 0x53, 0x53)); + darkPalette.setColor(QPalette::Dark, QColor(0x40, 0x40, 0x40)); + darkPalette.setColor(QPalette::Mid, QColor(0x33, 0x33, 0x33)); + darkPalette.setColor(QPalette::Shadow, QColor(0x26, 0x26, 0x26)); + darkPalette.setColor(QPalette::Button, darkPalette.color(QPalette::Base)); + darkPalette.setColor(QPalette::Disabled, QPalette::Button, darkPalette.color(QPalette::Disabled, QPalette::Base)); + darkPalette.setColor(QPalette::ButtonText, darkPalette.color(QPalette::WindowText)); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, + darkPalette.color(QPalette::Disabled, QPalette::WindowText)); + darkPalette.setColor(QPalette::BrightText, QColor(0xFC, 0x52, 0x48)); + darkPalette.setColor(QPalette::Link, QColor(0x4F, 0x95, 0xFC)); + darkPalette.setColor(QPalette::Highlight, QColor(0x6B, 0x6B, 0x6B)); + darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(0x5D, 0x5D, 0x5D)); + darkPalette.setColor(QPalette::HighlightedText, darkPalette.color(QPalette::WindowText)); + darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, + darkPalette.color(QPalette::Disabled, QPalette::WindowText)); - return darkPalette; + return darkPalette; } std::unique_ptr DarkScheme::getStyleSheet() const { - std::unique_ptr qsStylesheet = nullptr; + std::unique_ptr styleSheet = nullptr; - QFile qfDarkStyle(QString(":/dark_scheme/stylesheet.qss")); - if (qfDarkStyle.open(QIODevice::ReadOnly | QIODevice::Text)) { - qsStylesheet = std::make_unique(qfDarkStyle.readAll()); +#ifdef _WIN32 + QFile styleSheetFile(QString(":/dark_scheme/qss/stylesheet_win.qss")); +#else + QFile styleSheetFile(QString(":/dark_scheme/qss/stylesheet.qss")); +#endif + if (styleSheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + styleSheet = std::make_unique(styleSheetFile.readAll()); - qfDarkStyle.close(); - } + styleSheetFile.close(); + } - return qsStylesheet; + return styleSheet; } -std::unique_ptr DarkScheme::getColorParams() const { - std::unique_ptr customColors(new ColorParams()); +ColorScheme::ColorParams DarkScheme::getColorParams() const { + ColorScheme::ColorParams customColors; + + customColors[ThumbnailSequenceSelectedItemBackground] = QColor(0x42, 0x42, 0x42); + customColors[ThumbnailSequenceSelectionLeaderBackground] = QColor(0x55, 0x55, 0x55); + customColors[OpenNewProjectBorder] = QColor(0x53, 0x53, 0x53); + customColors[ProcessingIndicationFade] = QColor(0x28, 0x28, 0x28); + customColors[ProcessingIndicationHeadColor] = QColor(0xDD, 0xDD, 0xDD); + customColors[ProcessingIndicationTail] = QColor(0x6B, 0x6B, 0x6B); + customColors[StageListHead] = customColors.at(ProcessingIndicationHeadColor); + customColors[StageListTail] = customColors.at(ProcessingIndicationTail); + customColors[FixDpiDialogErrorText] = QColor(0xF3, 0x49, 0x41); - customColors->insert( - ColorParams::value_type("thumbnail_sequence_selected_item_background", QColor(0x44, 0x44, 0x44))); - customColors->insert(ColorParams::value_type("open_new_project_border_color", QColor(0x53, 0x53, 0x53))); - customColors->insert(ColorParams::value_type("processing_indication_fade_color", QColor(0x28, 0x28, 0x28))); - customColors->insert(ColorParams::value_type("processing_indication_head_color", QColor(0xDD, 0xDD, 0xDD))); - customColors->insert(ColorParams::value_type("processing_indication_tail_color", QColor(0x6B, 0x6B, 0x6B))); - customColors->insert( - ColorParams::value_type("stage_list_head_color", customColors->at("processing_indication_head_color"))); - customColors->insert( - ColorParams::value_type("stage_list_tail_color", customColors->at("processing_indication_tail_color"))); - customColors->insert(ColorParams::value_type("fix_dpi_dialog_error_text_color", QColor(0xF3, 0x49, 0x41))); + return customColors; +} - return customColors; +QStyle* DarkScheme::getStyle() const { + return QStyleFactory::create("Fusion"); } diff --git a/DarkScheme.h b/DarkScheme.h index c72e2f9ff..87648926d 100644 --- a/DarkScheme.h +++ b/DarkScheme.h @@ -4,17 +4,18 @@ #include #include -#include #include #include "ColorScheme.h" class DarkScheme : public ColorScheme { -public: - std::unique_ptr getPalette() const override; + public: + QStyle* getStyle() const override; - std::unique_ptr getStyleSheet() const override; + QPalette getPalette() const override; - std::unique_ptr getColorParams() const override; + std::unique_ptr getStyleSheet() const override; + + ColorParams getColorParams() const override; }; diff --git a/DebugImageView.cpp b/DebugImageView.cpp index 9b30da914..4aafcd314 100644 --- a/DebugImageView.cpp +++ b/DebugImageView.cpp @@ -17,88 +17,85 @@ */ #include "DebugImageView.h" +#include +#include #include "AbstractCommand.h" #include "BackgroundExecutor.h" -#include "ImageViewBase.h" #include "BasicImageView.h" +#include "ImageViewBase.h" #include "ProcessingIndicationWidget.h" -#include -#include class DebugImageView::ImageLoadResult : public AbstractCommand { -public: - ImageLoadResult(QPointer owner, const QImage& image) - : m_ptrOwner(std::move(owner)), m_image(image) { - } + public: + ImageLoadResult(QPointer owner, const QImage& image) : m_owner(std::move(owner)), m_image(image) {} - // This method is called from the main thread. - void operator()() override { - if (DebugImageView* owner = m_ptrOwner) { - owner->imageLoaded(m_image); - } + // This method is called from the main thread. + void operator()() override { + if (DebugImageView* owner = m_owner) { + owner->imageLoaded(m_image); } + } -private: - QPointer m_ptrOwner; - QImage m_image; + private: + QPointer m_owner; + QImage m_image; }; class DebugImageView::ImageLoader : public AbstractCommand { -public: - ImageLoader(DebugImageView* owner, const QString& file_path) : m_ptrOwner(owner), m_filePath(file_path) { - } + public: + ImageLoader(DebugImageView* owner, const QString& file_path) : m_owner(owner), m_filePath(file_path) {} - BackgroundExecutor::TaskResultPtr operator()() override { - QImage image(m_filePath); + BackgroundExecutor::TaskResultPtr operator()() override { + QImage image(m_filePath); - return make_intrusive(m_ptrOwner, image); - } + return make_intrusive(m_owner, image); + } -private: - QPointer m_ptrOwner; - QString m_filePath; + private: + QPointer m_owner; + QString m_filePath; }; DebugImageView::DebugImageView(AutoRemovingFile file, const boost::function& image_view_factory, QWidget* parent) - : QStackedWidget(parent), - m_file(file), - m_imageViewFactory(image_view_factory), - m_pPlaceholderWidget(new ProcessingIndicationWidget(this)), - m_isLive(false) { - addWidget(m_pPlaceholderWidget); + : QStackedWidget(parent), + m_file(file), + m_imageViewFactory(image_view_factory), + m_placeholderWidget(new ProcessingIndicationWidget(this)), + m_isLive(false) { + addWidget(m_placeholderWidget); } void DebugImageView::setLive(const bool live) { - if (live && !m_isLive) { - ImageViewBase::backgroundExecutor().enqueueTask(make_intrusive(this, m_file.get())); - } else if (!live && m_isLive) { - if (QWidget* wgt = currentWidget()) { - if (wgt != m_pPlaceholderWidget) { - removeWidget(wgt); - delete wgt; - } - } + if (live && !m_isLive) { + ImageViewBase::backgroundExecutor().enqueueTask(make_intrusive(this, m_file.get())); + } else if (!live && m_isLive) { + if (QWidget* wgt = currentWidget()) { + if (wgt != m_placeholderWidget) { + removeWidget(wgt); + delete wgt; + } } + } - m_isLive = live; + m_isLive = live; } void DebugImageView::imageLoaded(const QImage& image) { - if (!m_isLive) { - return; - } - - if (currentWidget() == m_pPlaceholderWidget) { - std::unique_ptr image_view; - if (m_imageViewFactory.empty()) { - image_view = std::make_unique(image); - } else { - image_view.reset(m_imageViewFactory(image)); - } - setCurrentIndex(addWidget(image_view.release())); + if (!m_isLive) { + return; + } + + if (currentWidget() == m_placeholderWidget) { + std::unique_ptr image_view; + if (m_imageViewFactory.empty()) { + image_view = std::make_unique(image); + } else { + image_view.reset(m_imageViewFactory(image)); } + setCurrentIndex(addWidget(image_view.release())); + } } diff --git a/DebugImageView.h b/DebugImageView.h index 17469fb74..37153c0cf 100644 --- a/DebugImageView.h +++ b/DebugImageView.h @@ -19,39 +19,39 @@ #ifndef DEBUG_IMAGE_VIEW_H_ #define DEBUG_IMAGE_VIEW_H_ -#include "AutoRemovingFile.h" #include #include -#include #include +#include +#include "AutoRemovingFile.h" class QImage; class DebugImageView - : public QStackedWidget, - public boost::intrusive::list_base_hook> { -public: - explicit DebugImageView(AutoRemovingFile file, - const boost::function& image_view_factory - = boost::function(), - QWidget* parent = nullptr); - - /** - * Tells this widget to either display the actual image or just - * a placeholder. - */ - void setLive(bool live); - -private: - class ImageLoadResult; - class ImageLoader; - - void imageLoaded(const QImage& image); - - AutoRemovingFile m_file; - boost::function m_imageViewFactory; - QWidget* m_pPlaceholderWidget; - bool m_isLive; + : public QStackedWidget, + public boost::intrusive::list_base_hook> { + public: + explicit DebugImageView(AutoRemovingFile file, + const boost::function& image_view_factory + = boost::function(), + QWidget* parent = nullptr); + + /** + * Tells this widget to either display the actual image or just + * a placeholder. + */ + void setLive(bool live); + + private: + class ImageLoadResult; + class ImageLoader; + + void imageLoaded(const QImage& image); + + AutoRemovingFile m_file; + boost::function m_imageViewFactory; + QWidget* m_placeholderWidget; + bool m_isLive; }; diff --git a/DebugImages.cpp b/DebugImages.cpp index f6e223ef6..c4ac6d7c7 100644 --- a/DebugImages.cpp +++ b/DebugImages.cpp @@ -17,53 +17,53 @@ */ #include "DebugImages.h" -#include "imageproc/BinaryImage.h" +#include #include #include #include -#include +#include "imageproc/BinaryImage.h" void DebugImages::add(const QImage& image, const QString& label, const boost::function& image_view_factory) { - QTemporaryFile file(QDir::tempPath() + "/scantailor-dbg-XXXXXX.png"); - if (!file.open()) { - return; - } + QTemporaryFile file(QDir::tempPath() + "/scantailor-dbg-XXXXXX.png"); + if (!file.open()) { + return; + } - AutoRemovingFile arem_file(file.fileName()); - file.setAutoRemove(false); + AutoRemovingFile arem_file(file.fileName()); + file.setAutoRemove(false); - QImageWriter writer(&file, "png"); - writer.setCompression(2); // Trade space for speed. - if (!writer.write(image)) { - return; - } + QImageWriter writer(&file, "png"); + writer.setCompression(2); // Trade space for speed. + if (!writer.write(image)) { + return; + } - m_sequence.push_back(make_intrusive(arem_file, label, image_view_factory)); + m_sequence.push_back(make_intrusive(arem_file, label, image_view_factory)); } void DebugImages::add(const imageproc::BinaryImage& image, const QString& label, const boost::function& image_view_factory) { - add(image.toQImage(), label, image_view_factory); + add(image.toQImage(), label, image_view_factory); } AutoRemovingFile DebugImages::retrieveNext(QString* label, boost::function* image_view_factory) { - if (m_sequence.empty()) { - return AutoRemovingFile(); - } + if (m_sequence.empty()) { + return AutoRemovingFile(); + } - AutoRemovingFile file(m_sequence.front()->file); - if (label) { - *label = m_sequence.front()->label; - } - if (image_view_factory) { - *image_view_factory = m_sequence.front()->imageViewFactory; - } + AutoRemovingFile file(m_sequence.front()->file); + if (label) { + *label = m_sequence.front()->label; + } + if (image_view_factory) { + *image_view_factory = m_sequence.front()->imageViewFactory; + } - m_sequence.pop_front(); + m_sequence.pop_front(); - return file; + return file; } diff --git a/DebugImages.h b/DebugImages.h index 1dbf9e613..18e2df725 100644 --- a/DebugImages.h +++ b/DebugImages.h @@ -19,12 +19,12 @@ #ifndef DEBUG_IMAGES_H_ #define DEBUG_IMAGES_H_ -#include "ref_countable.h" -#include "intrusive_ptr.h" -#include "AutoRemovingFile.h" -#include #include +#include #include +#include "AutoRemovingFile.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class QImage; class QWidget; @@ -37,43 +37,40 @@ class BinaryImage; * \brief A sequence of image + label pairs. */ class DebugImages { -public: - void add(const QImage& image, - const QString& label, - const boost::function& image_view_factory - = boost::function()); - - void add(const imageproc::BinaryImage& image, - const QString& label, - const boost::function& image_view_factory - = boost::function()); - - bool empty() const { - return m_sequence.empty(); - } - - /** - * \brief Removes and returns the first item in the sequence. - * - * The label and viewer widget factory (that may not be bound) - * are returned by taking pointers to them as arguments. - * Returns a null AutoRemovingFile if image sequence is empty. - */ - AutoRemovingFile retrieveNext(QString* label = nullptr, - boost::function* image_view_factory = nullptr); - -private: - struct Item : public ref_countable { - AutoRemovingFile file; - QString label; - boost::function imageViewFactory; - - Item(AutoRemovingFile f, const QString& l, const boost::function& imf) - : file(f), label(l), imageViewFactory(imf) { - } - }; - - std::deque> m_sequence; + public: + void add(const QImage& image, + const QString& label, + const boost::function& image_view_factory + = boost::function()); + + void add(const imageproc::BinaryImage& image, + const QString& label, + const boost::function& image_view_factory + = boost::function()); + + bool empty() const { return m_sequence.empty(); } + + /** + * \brief Removes and returns the first item in the sequence. + * + * The label and viewer widget factory (that may not be bound) + * are returned by taking pointers to them as arguments. + * Returns a null AutoRemovingFile if image sequence is empty. + */ + AutoRemovingFile retrieveNext(QString* label = nullptr, + boost::function* image_view_factory = nullptr); + + private: + struct Item : public ref_countable { + AutoRemovingFile file; + QString label; + boost::function imageViewFactory; + + Item(AutoRemovingFile f, const QString& l, const boost::function& imf) + : file(f), label(l), imageViewFactory(imf) {} + }; + + std::deque> m_sequence; }; diff --git a/DefaultParams.cpp b/DefaultParams.cpp index ad6bec441..f103a5db5 100644 --- a/DefaultParams.cpp +++ b/DefaultParams.cpp @@ -1,58 +1,58 @@ #include "DefaultParams.h" -#include "XmlUnmarshaller.h" -#include "XmlMarshaller.h" #include "Utils.h" +#include "XmlMarshaller.h" +#include "XmlUnmarshaller.h" using namespace page_split; using namespace output; const DefaultParams::FixOrientationParams& DefaultParams::getFixOrientationParams() const { - return fixOrientationParams; + return m_fixOrientationParams; } void DefaultParams::setFixOrientationParams(const DefaultParams::FixOrientationParams& fixOrientationParams) { - DefaultParams::fixOrientationParams = fixOrientationParams; + DefaultParams::m_fixOrientationParams = fixOrientationParams; } const DefaultParams::DeskewParams& DefaultParams::getDeskewParams() const { - return deskewParams; + return m_deskewParams; } void DefaultParams::setDeskewParams(const DefaultParams::DeskewParams& deskewParams) { - DefaultParams::deskewParams = deskewParams; + DefaultParams::m_deskewParams = deskewParams; } const DefaultParams::PageSplitParams& DefaultParams::getPageSplitParams() const { - return pageSplitParams; + return m_pageSplitParams; } void DefaultParams::setPageSplitParams(const DefaultParams::PageSplitParams& pageSplitParams) { - DefaultParams::pageSplitParams = pageSplitParams; + DefaultParams::m_pageSplitParams = pageSplitParams; } const DefaultParams::SelectContentParams& DefaultParams::getSelectContentParams() const { - return selectContentParams; + return m_selectContentParams; } void DefaultParams::setSelectContentParams(const DefaultParams::SelectContentParams& selectContentParams) { - DefaultParams::selectContentParams = selectContentParams; + DefaultParams::m_selectContentParams = selectContentParams; } const DefaultParams::PageLayoutParams& DefaultParams::getPageLayoutParams() const { - return pageLayoutParams; + return m_pageLayoutParams; } void DefaultParams::setPageLayoutParams(const DefaultParams::PageLayoutParams& pageLayoutParams) { - DefaultParams::pageLayoutParams = pageLayoutParams; + DefaultParams::m_pageLayoutParams = pageLayoutParams; } const DefaultParams::OutputParams& DefaultParams::getOutputParams() const { - return outputParams; + return m_outputParams; } void DefaultParams::setOutputParams(const DefaultParams::OutputParams& outputParams) { - DefaultParams::outputParams = outputParams; + DefaultParams::m_outputParams = outputParams; } DefaultParams::DefaultParams(const DefaultParams::FixOrientationParams& fixOrientationParams, @@ -61,258 +61,228 @@ DefaultParams::DefaultParams(const DefaultParams::FixOrientationParams& fixOrien const DefaultParams::SelectContentParams& selectContentParams, const DefaultParams::PageLayoutParams& pageLayoutParams, const DefaultParams::OutputParams& outputParams) - : fixOrientationParams(fixOrientationParams), - deskewParams(deskewParams), - pageSplitParams(pageSplitParams), - selectContentParams(selectContentParams), - pageLayoutParams(pageLayoutParams), - outputParams(outputParams), - units(MILLIMETRES) { -} + : m_fixOrientationParams(fixOrientationParams), + m_deskewParams(deskewParams), + m_pageSplitParams(pageSplitParams), + m_selectContentParams(selectContentParams), + m_pageLayoutParams(pageLayoutParams), + m_outputParams(outputParams), + m_units(MILLIMETRES) {} DefaultParams::DefaultParams(const QDomElement& el) - : fixOrientationParams(el.namedItem("fix-orientation-params").toElement()), - deskewParams(el.namedItem("deskew-params").toElement()), - pageSplitParams(el.namedItem("page-split-params").toElement()), - selectContentParams(el.namedItem("select-content-params").toElement()), - pageLayoutParams(el.namedItem("page-layout-params").toElement()), - outputParams(el.namedItem("output-params").toElement()), - units(unitsFromString(el.attribute("units"))) { -} + : m_fixOrientationParams(el.namedItem("fix-orientation-params").toElement()), + m_deskewParams(el.namedItem("deskew-params").toElement()), + m_pageSplitParams(el.namedItem("page-split-params").toElement()), + m_selectContentParams(el.namedItem("select-content-params").toElement()), + m_pageLayoutParams(el.namedItem("page-layout-params").toElement()), + m_outputParams(el.namedItem("output-params").toElement()), + m_units(unitsFromString(el.attribute("units"))) {} QDomElement DefaultParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(fixOrientationParams.toXml(doc, "fix-orientation-params")); - el.appendChild(deskewParams.toXml(doc, "deskew-params")); - el.appendChild(pageSplitParams.toXml(doc, "page-split-params")); - el.appendChild(selectContentParams.toXml(doc, "select-content-params")); - el.appendChild(pageLayoutParams.toXml(doc, "page-layout-params")); - el.appendChild(outputParams.toXml(doc, "output-params")); - el.setAttribute("units", unitsToString(units)); + QDomElement el(doc.createElement(name)); + el.appendChild(m_fixOrientationParams.toXml(doc, "fix-orientation-params")); + el.appendChild(m_deskewParams.toXml(doc, "deskew-params")); + el.appendChild(m_pageSplitParams.toXml(doc, "page-split-params")); + el.appendChild(m_selectContentParams.toXml(doc, "select-content-params")); + el.appendChild(m_pageLayoutParams.toXml(doc, "page-layout-params")); + el.appendChild(m_outputParams.toXml(doc, "output-params")); + el.setAttribute("units", unitsToString(m_units)); - return el; + return el; } -DefaultParams::DefaultParams() : units(MILLIMETRES) { -} +DefaultParams::DefaultParams() : m_units(MILLIMETRES) {} Units DefaultParams::getUnits() const { - return units; + return m_units; } void DefaultParams::setUnits(Units units) { - DefaultParams::units = units; + DefaultParams::m_units = units; } const OrthogonalRotation& DefaultParams::FixOrientationParams::getImageRotation() const { - return imageRotation; + return m_imageRotation; } void DefaultParams::FixOrientationParams::setImageRotation(const OrthogonalRotation& imageRotation) { - FixOrientationParams::imageRotation = imageRotation; + FixOrientationParams::m_imageRotation = imageRotation; } DefaultParams::FixOrientationParams::FixOrientationParams(const OrthogonalRotation& imageRotation) - : imageRotation(imageRotation) { -} + : m_imageRotation(imageRotation) {} DefaultParams::FixOrientationParams::FixOrientationParams(const QDomElement& el) - : imageRotation(XmlUnmarshaller::rotation(el.namedItem("imageRotation").toElement())) { -} + : m_imageRotation(XmlUnmarshaller::rotation(el.namedItem("imageRotation").toElement())) {} QDomElement DefaultParams::FixOrientationParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(XmlMarshaller(doc).rotation(imageRotation, "imageRotation")); + QDomElement el(doc.createElement(name)); + el.appendChild(XmlMarshaller(doc).rotation(m_imageRotation, "imageRotation")); - return el; + return el; } DefaultParams::DeskewParams::DeskewParams(double deskewAngleDeg, AutoManualMode mode) - : deskewAngleDeg(deskewAngleDeg), mode(mode) { -} + : m_deskewAngleDeg(deskewAngleDeg), m_mode(mode) {} double DefaultParams::DeskewParams::getDeskewAngleDeg() const { - return deskewAngleDeg; + return m_deskewAngleDeg; } void DefaultParams::DeskewParams::setDeskewAngleDeg(double deskewAngleDeg) { - DeskewParams::deskewAngleDeg = deskewAngleDeg; + DeskewParams::m_deskewAngleDeg = deskewAngleDeg; } AutoManualMode DefaultParams::DeskewParams::getMode() const { - return mode; + return m_mode; } void DefaultParams::DeskewParams::setMode(AutoManualMode mode) { - DeskewParams::mode = mode; + DeskewParams::m_mode = mode; } -DefaultParams::DeskewParams::DeskewParams() : deskewAngleDeg(0.0), mode(MODE_AUTO) { -} +DefaultParams::DeskewParams::DeskewParams() : m_deskewAngleDeg(0.0), m_mode(MODE_AUTO) {} DefaultParams::DeskewParams::DeskewParams(const QDomElement& el) - : deskewAngleDeg(el.attribute("deskewAngleDeg").toDouble()), - mode((el.attribute("mode") == "manual") ? MODE_MANUAL : MODE_AUTO) { -} + : m_deskewAngleDeg(el.attribute("deskewAngleDeg").toDouble()), + m_mode((el.attribute("mode") == "manual") ? MODE_MANUAL : MODE_AUTO) {} QDomElement DefaultParams::DeskewParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("deskewAngleDeg", Utils::doubleToString(deskewAngleDeg)); - el.setAttribute("mode", (mode == MODE_AUTO) ? "auto" : "manual"); + QDomElement el(doc.createElement(name)); + el.setAttribute("deskewAngleDeg", Utils::doubleToString(m_deskewAngleDeg)); + el.setAttribute("mode", (m_mode == MODE_AUTO) ? "auto" : "manual"); - return el; + return el; } -DefaultParams::PageSplitParams::PageSplitParams(page_split::LayoutType layoutType) : layoutType(layoutType) { -} +DefaultParams::PageSplitParams::PageSplitParams(page_split::LayoutType layoutType) : m_layoutType(layoutType) {} LayoutType DefaultParams::PageSplitParams::getLayoutType() const { - return layoutType; + return m_layoutType; } void DefaultParams::PageSplitParams::setLayoutType(LayoutType layoutType) { - PageSplitParams::layoutType = layoutType; + PageSplitParams::m_layoutType = layoutType; } -DefaultParams::PageSplitParams::PageSplitParams() : layoutType(AUTO_LAYOUT_TYPE) { -} +DefaultParams::PageSplitParams::PageSplitParams() : m_layoutType(AUTO_LAYOUT_TYPE) {} DefaultParams::PageSplitParams::PageSplitParams(const QDomElement& el) - : layoutType(layoutTypeFromString(el.attribute("layoutType"))) { -} + : m_layoutType(layoutTypeFromString(el.attribute("layoutType"))) {} QDomElement DefaultParams::PageSplitParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("layoutType", layoutTypeToString(layoutType)); + QDomElement el(doc.createElement(name)); + el.setAttribute("layoutType", layoutTypeToString(m_layoutType)); - return el; + return el; } DefaultParams::SelectContentParams::SelectContentParams(const QSizeF& pageRectSize, - AutoManualMode pageDetectMode, bool contentDetectEnabled, - bool pageDetectEnabled, + AutoManualMode pageDetectMode, bool fineTuneCorners) - : pageRectSize(pageRectSize), - pageDetectMode(pageDetectMode), - contentDetectEnabled(contentDetectEnabled), - pageDetectEnabled(pageDetectEnabled), - fineTuneCorners(fineTuneCorners) { -} + : m_pageRectSize(pageRectSize), + m_contentDetectEnabled(contentDetectEnabled), + m_pageDetectMode(pageDetectMode), + m_fineTuneCorners(fineTuneCorners) {} DefaultParams::SelectContentParams::SelectContentParams() - : pageRectSize(QSizeF(210, 297)), - pageDetectMode(MODE_AUTO), - contentDetectEnabled(true), - pageDetectEnabled(false), - fineTuneCorners(false) { -} + : m_pageRectSize(QSizeF(210, 297)), + m_contentDetectEnabled(true), + m_pageDetectMode(MODE_DISABLED), + m_fineTuneCorners(false) {} const QSizeF& DefaultParams::SelectContentParams::getPageRectSize() const { - return pageRectSize; + return m_pageRectSize; } void DefaultParams::SelectContentParams::setPageRectSize(const QSizeF& pageRectSize) { - SelectContentParams::pageRectSize = pageRectSize; + SelectContentParams::m_pageRectSize = pageRectSize; } bool DefaultParams::SelectContentParams::isContentDetectEnabled() const { - return contentDetectEnabled; + return m_contentDetectEnabled; } void DefaultParams::SelectContentParams::setContentDetectEnabled(bool contentDetectEnabled) { - SelectContentParams::contentDetectEnabled = contentDetectEnabled; -} - -bool DefaultParams::SelectContentParams::isPageDetectEnabled() const { - return pageDetectEnabled; -} - -void DefaultParams::SelectContentParams::setPageDetectEnabled(bool pageDetectEnabled) { - SelectContentParams::pageDetectEnabled = pageDetectEnabled; + SelectContentParams::m_contentDetectEnabled = contentDetectEnabled; } bool DefaultParams::SelectContentParams::isFineTuneCorners() const { - return fineTuneCorners; + return m_fineTuneCorners; } void DefaultParams::SelectContentParams::setFineTuneCorners(bool fineTuneCorners) { - SelectContentParams::fineTuneCorners = fineTuneCorners; + SelectContentParams::m_fineTuneCorners = fineTuneCorners; } DefaultParams::SelectContentParams::SelectContentParams(const QDomElement& el) - : pageRectSize(XmlUnmarshaller::sizeF(el.namedItem("pageRectSize").toElement())), - pageDetectMode((el.attribute("pageDetectMode") == "manual") ? MODE_MANUAL : MODE_AUTO), - contentDetectEnabled(el.attribute("contentDetectEnabled") == "1"), - pageDetectEnabled(el.attribute("pageDetectEnabled") == "1"), - fineTuneCorners(el.attribute("fineTuneCorners") == "1") { -} + : m_pageRectSize(XmlUnmarshaller::sizeF(el.namedItem("pageRectSize").toElement())), + m_contentDetectEnabled(el.attribute("contentDetectEnabled") == "1"), + m_pageDetectMode(stringToAutoManualMode(el.attribute("pageDetectMode"))), + m_fineTuneCorners(el.attribute("fineTuneCorners") == "1") {} QDomElement DefaultParams::SelectContentParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(XmlMarshaller(doc).sizeF(pageRectSize, "pageRectSize")); - el.setAttribute("pageDetectMode", (pageDetectMode == MODE_AUTO) ? "auto" : "manual"); - el.setAttribute("contentDetectEnabled", contentDetectEnabled ? "1" : "0"); - el.setAttribute("pageDetectEnabled", pageDetectEnabled ? "1" : "0"); - el.setAttribute("fineTuneCorners", fineTuneCorners ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.appendChild(XmlMarshaller(doc).sizeF(m_pageRectSize, "pageRectSize")); + el.setAttribute("contentDetectEnabled", m_contentDetectEnabled ? "1" : "0"); + el.setAttribute("pageDetectMode", autoManualModeToString(m_pageDetectMode)); + el.setAttribute("fineTuneCorners", m_fineTuneCorners ? "1" : "0"); - return el; + return el; } AutoManualMode DefaultParams::SelectContentParams::getPageDetectMode() const { - return pageDetectMode; + return m_pageDetectMode; } void DefaultParams::SelectContentParams::setPageDetectMode(AutoManualMode pageDetectMode) { - SelectContentParams::pageDetectMode = pageDetectMode; + SelectContentParams::m_pageDetectMode = pageDetectMode; } DefaultParams::PageLayoutParams::PageLayoutParams(const Margins& hardMargins, const page_layout::Alignment& alignment, bool autoMargins) - : hardMargins(hardMargins), alignment(alignment), autoMargins(autoMargins) { -} + : m_hardMargins(hardMargins), m_alignment(alignment), m_autoMargins(autoMargins) {} -DefaultParams::PageLayoutParams::PageLayoutParams() : hardMargins(10, 5, 10, 5), autoMargins(false) { -} +DefaultParams::PageLayoutParams::PageLayoutParams() : m_hardMargins(10, 5, 10, 5), m_autoMargins(false) {} const Margins& DefaultParams::PageLayoutParams::getHardMargins() const { - return hardMargins; + return m_hardMargins; } void DefaultParams::PageLayoutParams::setHardMargins(const Margins& hardMargins) { - PageLayoutParams::hardMargins = hardMargins; + PageLayoutParams::m_hardMargins = hardMargins; } const page_layout::Alignment& DefaultParams::PageLayoutParams::getAlignment() const { - return alignment; + return m_alignment; } void DefaultParams::PageLayoutParams::setAlignment(const page_layout::Alignment& alignment) { - PageLayoutParams::alignment = alignment; + PageLayoutParams::m_alignment = alignment; } bool DefaultParams::PageLayoutParams::isAutoMargins() const { - return autoMargins; + return m_autoMargins; } void DefaultParams::PageLayoutParams::setAutoMargins(bool autoMargins) { - PageLayoutParams::autoMargins = autoMargins; + PageLayoutParams::m_autoMargins = autoMargins; } DefaultParams::PageLayoutParams::PageLayoutParams(const QDomElement& el) - : hardMargins(XmlUnmarshaller::margins(el.namedItem("hardMargins").toElement())), - alignment(el.namedItem("alignment").toElement()), - autoMargins(el.attribute("autoMargins") == "1") { -} + : m_hardMargins(XmlUnmarshaller::margins(el.namedItem("hardMargins").toElement())), + m_alignment(el.namedItem("alignment").toElement()), + m_autoMargins(el.attribute("autoMargins") == "1") {} QDomElement DefaultParams::PageLayoutParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(XmlMarshaller(doc).margins(hardMargins, "hardMargins")); - el.appendChild(alignment.toXml(doc, "alignment")); - el.setAttribute("autoMargins", autoMargins ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.appendChild(XmlMarshaller(doc).margins(m_hardMargins, "hardMargins")); + el.appendChild(m_alignment.toXml(doc, "alignment")); + el.setAttribute("autoMargins", m_autoMargins ? "1" : "0"); - return el; + return el; } DefaultParams::OutputParams::OutputParams(const Dpi& dpi, @@ -321,94 +291,91 @@ DefaultParams::OutputParams::OutputParams(const Dpi& dpi, const output::PictureShapeOptions& pictureShapeOptions, const output::DepthPerception& depthPerception, const output::DewarpingOptions& dewarpingOptions, - output::DespeckleLevel despeckleLevel) - : dpi(dpi), - colorParams(colorParams), - splittingOptions(splittingOptions), - pictureShapeOptions(pictureShapeOptions), - depthPerception(depthPerception), - dewarpingOptions(dewarpingOptions), - despeckleLevel(despeckleLevel) { -} + const double despeckleLevel) + : m_dpi(dpi), + m_colorParams(colorParams), + m_splittingOptions(splittingOptions), + m_pictureShapeOptions(pictureShapeOptions), + m_depthPerception(depthPerception), + m_dewarpingOptions(dewarpingOptions), + m_despeckleLevel(despeckleLevel) {} -DefaultParams::OutputParams::OutputParams() : despeckleLevel(DESPECKLE_CAUTIOUS), dpi(600, 600) { -} +DefaultParams::OutputParams::OutputParams() : m_despeckleLevel(1.0), m_dpi(600, 600) {} const Dpi& DefaultParams::OutputParams::getDpi() const { - return dpi; + return m_dpi; } void DefaultParams::OutputParams::setDpi(const Dpi& dpi) { - OutputParams::dpi = dpi; + OutputParams::m_dpi = dpi; } const ColorParams& DefaultParams::OutputParams::getColorParams() const { - return colorParams; + return m_colorParams; } void DefaultParams::OutputParams::setColorParams(const ColorParams& colorParams) { - OutputParams::colorParams = colorParams; + OutputParams::m_colorParams = colorParams; } const SplittingOptions& DefaultParams::OutputParams::getSplittingOptions() const { - return splittingOptions; + return m_splittingOptions; } void DefaultParams::OutputParams::setSplittingOptions(const SplittingOptions& splittingOptions) { - OutputParams::splittingOptions = splittingOptions; + OutputParams::m_splittingOptions = splittingOptions; } const PictureShapeOptions& DefaultParams::OutputParams::getPictureShapeOptions() const { - return pictureShapeOptions; + return m_pictureShapeOptions; } void DefaultParams::OutputParams::setPictureShapeOptions(const PictureShapeOptions& pictureShapeOptions) { - OutputParams::pictureShapeOptions = pictureShapeOptions; + OutputParams::m_pictureShapeOptions = pictureShapeOptions; } const DepthPerception& DefaultParams::OutputParams::getDepthPerception() const { - return depthPerception; + return m_depthPerception; } void DefaultParams::OutputParams::setDepthPerception(const DepthPerception& depthPerception) { - OutputParams::depthPerception = depthPerception; + OutputParams::m_depthPerception = depthPerception; } const DewarpingOptions& DefaultParams::OutputParams::getDewarpingOptions() const { - return dewarpingOptions; + return m_dewarpingOptions; } void DefaultParams::OutputParams::setDewarpingOptions(const DewarpingOptions& dewarpingOptions) { - OutputParams::dewarpingOptions = dewarpingOptions; + OutputParams::m_dewarpingOptions = dewarpingOptions; } -DespeckleLevel DefaultParams::OutputParams::getDespeckleLevel() const { - return despeckleLevel; +double DefaultParams::OutputParams::getDespeckleLevel() const { + return m_despeckleLevel; } -void DefaultParams::OutputParams::setDespeckleLevel(DespeckleLevel despeckleLevel) { - OutputParams::despeckleLevel = despeckleLevel; +void DefaultParams::OutputParams::setDespeckleLevel(double despeckleLevel) { + OutputParams::m_despeckleLevel = despeckleLevel; } DefaultParams::OutputParams::OutputParams(const QDomElement& el) - : dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), - colorParams(el.namedItem("colorParams").toElement()), - splittingOptions(el.namedItem("splittingOptions").toElement()), - pictureShapeOptions(el.namedItem("pictureShapeOptions").toElement()), - depthPerception(el.attribute("depthPerception").toDouble()), - dewarpingOptions(el.namedItem("dewarpingOptions").toElement()), - despeckleLevel(despeckleLevelFromString(el.attribute("despeckleLevel"))) { -} + : m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), + m_colorParams(el.namedItem("colorParams").toElement()), + m_splittingOptions(el.namedItem("splittingOptions").toElement()), + m_pictureShapeOptions(el.namedItem("pictureShapeOptions").toElement()), + m_depthPerception(el.attribute("depthPerception").toDouble()), + m_dewarpingOptions(el.namedItem("dewarpingOptions").toElement()), + m_despeckleLevel(el.attribute("despeckleLevel").toDouble()) {} QDomElement DefaultParams::OutputParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(XmlMarshaller(doc).dpi(dpi, "dpi")); - el.appendChild(colorParams.toXml(doc, "colorParams")); - el.appendChild(splittingOptions.toXml(doc, "splittingOptions")); - el.appendChild(pictureShapeOptions.toXml(doc, "pictureShapeOptions")); - el.setAttribute("depthPerception", Utils::doubleToString(depthPerception.value())); - el.appendChild(dewarpingOptions.toXml(doc, "dewarpingOptions")); - el.setAttribute("despeckleLevel", despeckleLevelToString(despeckleLevel)); - - return el; + QDomElement el(doc.createElement(name)); + el.appendChild(XmlMarshaller(doc).dpi(m_dpi, "dpi")); + el.appendChild(m_colorParams.toXml(doc, "colorParams")); + el.appendChild(m_splittingOptions.toXml(doc, "splittingOptions")); + el.appendChild(m_pictureShapeOptions.toXml(doc, "pictureShapeOptions")); + el.setAttribute("depthPerception", Utils::doubleToString(m_depthPerception.value())); + el.appendChild(m_dewarpingOptions.toXml(doc, "dewarpingOptions")); + el.setAttribute("despeckleLevel", Utils::doubleToString(m_despeckleLevel)); + + return el; } diff --git a/DefaultParams.h b/DefaultParams.h index 3b78abe77..7153821ef 100644 --- a/DefaultParams.h +++ b/DefaultParams.h @@ -3,255 +3,249 @@ #define SCANTAILOR_DEFAULTPARAMSPROFILE_H -#include -#include -#include #include -#include #include -#include #include -#include "OrthogonalRotation.h" +#include +#include +#include +#include +#include #include "AutoManualMode.h" -#include "Margins.h" #include "Dpi.h" +#include "Margins.h" +#include "OrthogonalRotation.h" #include "Units.h" class DefaultParams { -public: - class FixOrientationParams { - private: - OrthogonalRotation imageRotation; - - public: - FixOrientationParams() = default; + public: + class FixOrientationParams { + public: + FixOrientationParams() = default; - explicit FixOrientationParams(const OrthogonalRotation& imageRotation); + explicit FixOrientationParams(const OrthogonalRotation& imageRotation); - explicit FixOrientationParams(const QDomElement& el); + explicit FixOrientationParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; - - const OrthogonalRotation& getImageRotation() const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - void setImageRotation(const OrthogonalRotation& imageRotation); - }; + const OrthogonalRotation& getImageRotation() const; - class DeskewParams { - private: - double deskewAngleDeg; - AutoManualMode mode; + void setImageRotation(const OrthogonalRotation& imageRotation); - public: - DeskewParams(); + private: + OrthogonalRotation m_imageRotation; + }; - explicit DeskewParams(double deskewAngleDeg, AutoManualMode mode); + class DeskewParams { + public: + DeskewParams(); - explicit DeskewParams(const QDomElement& el); + explicit DeskewParams(double deskewAngleDeg, AutoManualMode mode); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + explicit DeskewParams(const QDomElement& el); - double getDeskewAngleDeg() const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - void setDeskewAngleDeg(double deskewAngleDeg); + double getDeskewAngleDeg() const; - AutoManualMode getMode() const; + void setDeskewAngleDeg(double deskewAngleDeg); - void setMode(AutoManualMode mode); - }; + AutoManualMode getMode() const; - class PageSplitParams { - private: - page_split::LayoutType layoutType; + void setMode(AutoManualMode mode); - public: - PageSplitParams(); + private: + double m_deskewAngleDeg; + AutoManualMode m_mode; + }; - explicit PageSplitParams(page_split::LayoutType layoutType); + class PageSplitParams { + public: + PageSplitParams(); - explicit PageSplitParams(const QDomElement& el); + explicit PageSplitParams(page_split::LayoutType layoutType); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + explicit PageSplitParams(const QDomElement& el); - page_split::LayoutType getLayoutType() const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - void setLayoutType(page_split::LayoutType layoutType); - }; + page_split::LayoutType getLayoutType() const; - class SelectContentParams { - private: - QSizeF pageRectSize; - AutoManualMode pageDetectMode; - bool contentDetectEnabled; - bool pageDetectEnabled; - bool fineTuneCorners; + void setLayoutType(page_split::LayoutType layoutType); - public: - SelectContentParams(); + private: + page_split::LayoutType m_layoutType; + }; - SelectContentParams(const QSizeF& pageRectSize, - AutoManualMode pageDetectMode, - bool contentDetectEnabled, - bool pageDetectEnabled, - bool fineTuneCorners); + class SelectContentParams { + public: + SelectContentParams(); - explicit SelectContentParams(const QDomElement& el); + SelectContentParams(const QSizeF& pageRectSize, + bool contentDetectEnabled, + AutoManualMode pageDetectMode, + bool fineTuneCorners); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + explicit SelectContentParams(const QDomElement& el); - const QSizeF& getPageRectSize() const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - void setPageRectSize(const QSizeF& pageRectSize); + const QSizeF& getPageRectSize() const; - bool isContentDetectEnabled() const; + void setPageRectSize(const QSizeF& pageRectSize); - void setContentDetectEnabled(bool contentDetectEnabled); + bool isContentDetectEnabled() const; - bool isPageDetectEnabled() const; + void setContentDetectEnabled(bool contentDetectEnabled); - void setPageDetectEnabled(bool pageDetectEnabled); + bool isFineTuneCorners() const; - bool isFineTuneCorners() const; + void setFineTuneCorners(bool fineTuneCorners); - void setFineTuneCorners(bool fineTuneCorners); + AutoManualMode getPageDetectMode() const; - AutoManualMode getPageDetectMode() const; + void setPageDetectMode(AutoManualMode pageDetectMode); - void setPageDetectMode(AutoManualMode pageDetectMode); - }; + private: + QSizeF m_pageRectSize; + bool m_contentDetectEnabled; + AutoManualMode m_pageDetectMode; + bool m_fineTuneCorners; + }; - class PageLayoutParams { - private: - Margins hardMargins; - page_layout::Alignment alignment; - bool autoMargins; + class PageLayoutParams { + public: + PageLayoutParams(); - public: - PageLayoutParams(); + PageLayoutParams(const Margins& hardMargins, const page_layout::Alignment& alignment, bool autoMargins); - PageLayoutParams(const Margins& hardMargins, const page_layout::Alignment& alignment, bool autoMargins); + explicit PageLayoutParams(const QDomElement& el); - explicit PageLayoutParams(const QDomElement& el); + QDomElement toXml(QDomDocument& doc, const QString& name) const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + const Margins& getHardMargins() const; - const Margins& getHardMargins() const; + void setHardMargins(const Margins& hardMargins); - void setHardMargins(const Margins& hardMargins); + const page_layout::Alignment& getAlignment() const; - const page_layout::Alignment& getAlignment() const; + void setAlignment(const page_layout::Alignment& alignment); - void setAlignment(const page_layout::Alignment& alignment); + bool isAutoMargins() const; - bool isAutoMargins() const; + void setAutoMargins(bool autoMargins); - void setAutoMargins(bool autoMargins); - }; + private: + Margins m_hardMargins; + page_layout::Alignment m_alignment; + bool m_autoMargins; + }; - class OutputParams { - private: - Dpi dpi; - output::ColorParams colorParams; - output::SplittingOptions splittingOptions; - output::PictureShapeOptions pictureShapeOptions; - output::DepthPerception depthPerception; - output::DewarpingOptions dewarpingOptions; - output::DespeckleLevel despeckleLevel; + class OutputParams { + public: + OutputParams(); - public: - OutputParams(); + OutputParams(const Dpi& dpi, + const output::ColorParams& colorParams, + const output::SplittingOptions& splittingOptions, + const output::PictureShapeOptions& pictureShapeOptions, + const output::DepthPerception& depthPerception, + const output::DewarpingOptions& dewarpingOptions, + double despeckleLevel); - OutputParams(const Dpi& dpi, - const output::ColorParams& colorParams, - const output::SplittingOptions& splittingOptions, - const output::PictureShapeOptions& pictureShapeOptions, - const output::DepthPerception& depthPerception, - const output::DewarpingOptions& dewarpingOptions, - output::DespeckleLevel despeckleLevel); + explicit OutputParams(const QDomElement& el); - explicit OutputParams(const QDomElement& el); + QDomElement toXml(QDomDocument& doc, const QString& name) const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + const Dpi& getDpi() const; - const Dpi& getDpi() const; + void setDpi(const Dpi& dpi); - void setDpi(const Dpi& dpi); + const output::ColorParams& getColorParams() const; - const output::ColorParams& getColorParams() const; + void setColorParams(const output::ColorParams& colorParams); - void setColorParams(const output::ColorParams& colorParams); + const output::SplittingOptions& getSplittingOptions() const; - const output::SplittingOptions& getSplittingOptions() const; + void setSplittingOptions(const output::SplittingOptions& splittingOptions); - void setSplittingOptions(const output::SplittingOptions& splittingOptions); + const output::PictureShapeOptions& getPictureShapeOptions() const; - const output::PictureShapeOptions& getPictureShapeOptions() const; + void setPictureShapeOptions(const output::PictureShapeOptions& pictureShapeOptions); - void setPictureShapeOptions(const output::PictureShapeOptions& pictureShapeOptions); + const output::DepthPerception& getDepthPerception() const; - const output::DepthPerception& getDepthPerception() const; + void setDepthPerception(const output::DepthPerception& depthPerception); - void setDepthPerception(const output::DepthPerception& depthPerception); + const output::DewarpingOptions& getDewarpingOptions() const; - const output::DewarpingOptions& getDewarpingOptions() const; + void setDewarpingOptions(const output::DewarpingOptions& dewarpingOptions); - void setDewarpingOptions(const output::DewarpingOptions& dewarpingOptions); + double getDespeckleLevel() const; - output::DespeckleLevel getDespeckleLevel() const; + void setDespeckleLevel(double despeckleLevel); - void setDespeckleLevel(output::DespeckleLevel despeckleLevel); - }; + private: + Dpi m_dpi; + output::ColorParams m_colorParams; + output::SplittingOptions m_splittingOptions; + output::PictureShapeOptions m_pictureShapeOptions; + output::DepthPerception m_depthPerception; + output::DewarpingOptions m_dewarpingOptions; + double m_despeckleLevel; + }; -public: - DefaultParams(); + public: + DefaultParams(); - DefaultParams(const FixOrientationParams& fixOrientationParams, - const DeskewParams& deskewParams, - const PageSplitParams& pageSplitParams, - const SelectContentParams& selectContentParams, - const PageLayoutParams& pageLayoutParams, - const OutputParams& outputParams); + DefaultParams(const FixOrientationParams& fixOrientationParams, + const DeskewParams& deskewParams, + const PageSplitParams& pageSplitParams, + const SelectContentParams& selectContentParams, + const PageLayoutParams& pageLayoutParams, + const OutputParams& outputParams); - explicit DefaultParams(const QDomElement& el); + explicit DefaultParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - const FixOrientationParams& getFixOrientationParams() const; + const FixOrientationParams& getFixOrientationParams() const; - void setFixOrientationParams(const FixOrientationParams& fixOrientationParams); + void setFixOrientationParams(const FixOrientationParams& fixOrientationParams); - const DeskewParams& getDeskewParams() const; + const DeskewParams& getDeskewParams() const; - void setDeskewParams(const DeskewParams& deskewParams); + void setDeskewParams(const DeskewParams& deskewParams); - const PageSplitParams& getPageSplitParams() const; + const PageSplitParams& getPageSplitParams() const; - void setPageSplitParams(const PageSplitParams& pageSplitParams); + void setPageSplitParams(const PageSplitParams& pageSplitParams); - const SelectContentParams& getSelectContentParams() const; + const SelectContentParams& getSelectContentParams() const; - void setSelectContentParams(const SelectContentParams& selectContentParams); + void setSelectContentParams(const SelectContentParams& selectContentParams); - const PageLayoutParams& getPageLayoutParams() const; + const PageLayoutParams& getPageLayoutParams() const; - void setPageLayoutParams(const PageLayoutParams& pageLayoutParams); + void setPageLayoutParams(const PageLayoutParams& pageLayoutParams); - const OutputParams& getOutputParams() const; + const OutputParams& getOutputParams() const; - void setOutputParams(const OutputParams& outputParams); + void setOutputParams(const OutputParams& outputParams); - Units getUnits() const; + Units getUnits() const; - void setUnits(Units units); + void setUnits(Units units); -private: - FixOrientationParams fixOrientationParams; - DeskewParams deskewParams; - PageSplitParams pageSplitParams; - SelectContentParams selectContentParams; - PageLayoutParams pageLayoutParams; - OutputParams outputParams; - Units units; + private: + FixOrientationParams m_fixOrientationParams; + DeskewParams m_deskewParams; + PageSplitParams m_pageSplitParams; + SelectContentParams m_selectContentParams; + PageLayoutParams m_pageLayoutParams; + OutputParams m_outputParams; + Units m_units; }; diff --git a/DefaultParamsDialog.cpp b/DefaultParamsDialog.cpp index 40461783b..789e5e7cd 100644 --- a/DefaultParamsDialog.cpp +++ b/DefaultParamsDialog.cpp @@ -1,1118 +1,1109 @@ -#include +#include "DefaultParamsDialog.h" #include -#include #include -#include +#include +#include #include -#include #include -#include -#include #include -#include "DefaultParamsDialog.h" -#include "Utils.h" -#include "UnitsProvider.h" +#include +#include +#include +#include #include "DefaultParamsProvider.h" +#include "UnitsProvider.h" +#include "Utils.h" using namespace page_split; using namespace output; using namespace page_layout; DefaultParamsDialog::DefaultParamsDialog(QWidget* parent) - : QDialog(parent), - leftRightLinkEnabled(true), - topBottomLinkEnabled(true), - ignoreMarginChanges(0), - currentUnits(MILLIMETRES), - ignoreProfileChanges(0) { - setupUi(this); - - layoutModeCB->addItem(tr("Auto"), MODE_AUTO); - layoutModeCB->addItem(tr("Manual"), MODE_MANUAL); - - colorModeSelector->addItem(tr("Black and White"), BLACK_AND_WHITE); - colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE); - colorModeSelector->addItem(tr("Mixed"), MIXED); - - fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND); - fillingColorBox->addItem(tr("White"), FILL_WHITE); - - thresholdMethodBox->addItem(tr("Otsu"), OTSU); - thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); - thresholdMethodBox->addItem(tr("Wolf"), WOLF); - - pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE); - pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE); - pictureShapeSelector->addItem(tr("Rectangular"), RECTANGULAR_SHAPE); - - dpiSelector->addItem("300", "300"); - dpiSelector->addItem("400", "400"); - dpiSelector->addItem("600", "600"); - customDpiItemIdx = dpiSelector->count(); - customDpiValue = "200"; - dpiSelector->addItem(tr("Custom"), customDpiValue); - - dewarpingModeCB->addItem(tr("Off"), OFF); - dewarpingModeCB->addItem(tr("Auto"), AUTO); - dewarpingModeCB->addItem(tr("Manual"), MANUAL); - dewarpingModeCB->addItem(tr("Marginal"), MARGINAL); - - reservedProfileNames.insert("Default"); - reservedProfileNames.insert("Source"); - reservedProfileNames.insert("Custom"); - - profileCB->addItem(tr("Default"), "Default"); - profileCB->addItem(tr("Source"), "Source"); - std::unique_ptr> profileList = profileManager.getProfileList(); - for (const QString& profileName : *profileList) { - if (!isProfileNameReserved(profileName)) { - profileCB->addItem(profileName, profileName); - } + : QDialog(parent), + m_leftRightLinkEnabled(true), + m_topBottomLinkEnabled(true), + m_ignoreMarginChanges(0), + m_currentUnits(MILLIMETRES), + m_ignoreProfileChanges(0) { + setupUi(this); + + layoutModeCB->addItem(tr("Auto"), MODE_AUTO); + layoutModeCB->addItem(tr("Manual"), MODE_MANUAL); + + colorModeSelector->addItem(tr("Black and White"), BLACK_AND_WHITE); + colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE); + colorModeSelector->addItem(tr("Mixed"), MIXED); + + fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND); + fillingColorBox->addItem(tr("White"), FILL_WHITE); + + thresholdMethodBox->addItem(tr("Otsu"), OTSU); + thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); + thresholdMethodBox->addItem(tr("Wolf"), WOLF); + + pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE); + pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE); + pictureShapeSelector->addItem(tr("Rectangular"), RECTANGULAR_SHAPE); + + dpiSelector->addItem("300", "300"); + dpiSelector->addItem("400", "400"); + dpiSelector->addItem("600", "600"); + m_customDpiItemIdx = dpiSelector->count(); + m_customDpiValue = "200"; + dpiSelector->addItem(tr("Custom"), m_customDpiValue); + + dewarpingModeCB->addItem(tr("Off"), OFF); + dewarpingModeCB->addItem(tr("Auto"), AUTO); + dewarpingModeCB->addItem(tr("Manual"), MANUAL); + dewarpingModeCB->addItem(tr("Marginal"), MARGINAL); + + m_reservedProfileNames.insert("Default"); + m_reservedProfileNames.insert("Source"); + m_reservedProfileNames.insert("Custom"); + + profileCB->addItem(tr("Default"), "Default"); + profileCB->addItem(tr("Source"), "Source"); + const std::list profileList = m_profileManager.getProfileList(); + for (const QString& profileName : profileList) { + if (!isProfileNameReserved(profileName)) { + profileCB->addItem(profileName, profileName); } - customProfileItemIdx = profileCB->count(); - profileCB->addItem(tr("Custom"), "Custom"); - - reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Default"))); - reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Source"))); - reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Custom"))); - - chainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-24.png"))); - brokenChainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-broken-24.png"))); - setLinkButtonLinked(topBottomLink, topBottomLinkEnabled); - setLinkButtonLinked(leftRightLink, leftRightLinkEnabled); - - Utils::mapSetValue(alignmentByButton, alignTopLeftBtn, Alignment(Alignment::TOP, Alignment::LEFT)); - Utils::mapSetValue(alignmentByButton, alignTopBtn, Alignment(Alignment::TOP, Alignment::HCENTER)); - Utils::mapSetValue(alignmentByButton, alignTopRightBtn, Alignment(Alignment::TOP, Alignment::RIGHT)); - Utils::mapSetValue(alignmentByButton, alignLeftBtn, Alignment(Alignment::VCENTER, Alignment::LEFT)); - Utils::mapSetValue(alignmentByButton, alignCenterBtn, Alignment(Alignment::VCENTER, Alignment::HCENTER)); - Utils::mapSetValue(alignmentByButton, alignRightBtn, Alignment(Alignment::VCENTER, Alignment::RIGHT)); - Utils::mapSetValue(alignmentByButton, alignBottomLeftBtn, Alignment(Alignment::BOTTOM, Alignment::LEFT)); - Utils::mapSetValue(alignmentByButton, alignBottomBtn, Alignment(Alignment::BOTTOM, Alignment::HCENTER)); - Utils::mapSetValue(alignmentByButton, alignBottomRightBtn, Alignment(Alignment::BOTTOM, Alignment::RIGHT)); - - alignmentButtonGroup = std::make_unique(this); - for (const auto& buttonAndAlignment : alignmentByButton) { - alignmentButtonGroup->addButton(buttonAndAlignment.first); - } - - darkerThresholdLink->setText(Utils::richTextForLink(darkerThresholdLink->text())); - lighterThresholdLink->setText(Utils::richTextForLink(lighterThresholdLink->text())); - thresholdSlider->setToolTip(QString::number(thresholdSlider->value())); - - thresholdSlider->setMinimum(-100); - thresholdSlider->setMaximum(100); - thresholLabel->setText(QString::number(thresholdSlider->value())); - - const int index = profileCB->findData(DefaultParamsProvider::getInstance()->getProfileName()); - if (index != -1) { - profileCB->setCurrentIndex(index); - } - profileChanged(profileCB->currentIndex()); - - connect(buttonBox, SIGNAL(accepted()), this, SLOT(commitChanges())); + } + m_customProfileItemIdx = profileCB->count(); + profileCB->addItem(tr("Custom"), "Custom"); + + m_reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Default"))); + m_reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Source"))); + m_reservedProfileNames.insert(profileCB->itemText(profileCB->findData("Custom"))); + + m_chainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-24.png"))); + m_brokenChainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-broken-24.png"))); + setLinkButtonLinked(topBottomLink, m_topBottomLinkEnabled); + setLinkButtonLinked(leftRightLink, m_leftRightLinkEnabled); + + Utils::mapSetValue(m_alignmentByButton, alignTopLeftBtn, Alignment(Alignment::TOP, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignTopBtn, Alignment(Alignment::TOP, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignTopRightBtn, Alignment(Alignment::TOP, Alignment::RIGHT)); + Utils::mapSetValue(m_alignmentByButton, alignLeftBtn, Alignment(Alignment::VCENTER, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignCenterBtn, Alignment(Alignment::VCENTER, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignRightBtn, Alignment(Alignment::VCENTER, Alignment::RIGHT)); + Utils::mapSetValue(m_alignmentByButton, alignBottomLeftBtn, Alignment(Alignment::BOTTOM, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignBottomBtn, Alignment(Alignment::BOTTOM, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignBottomRightBtn, Alignment(Alignment::BOTTOM, Alignment::RIGHT)); + + m_alignmentButtonGroup = std::make_unique(this); + for (const auto& buttonAndAlignment : m_alignmentByButton) { + m_alignmentButtonGroup->addButton(buttonAndAlignment.first); + } + + darkerThresholdLink->setText(Utils::richTextForLink(darkerThresholdLink->text())); + lighterThresholdLink->setText(Utils::richTextForLink(lighterThresholdLink->text())); + thresholdSlider->setToolTip(QString::number(thresholdSlider->value())); + + thresholdSlider->setMinimum(-100); + thresholdSlider->setMaximum(100); + thresholLabel->setText(QString::number(thresholdSlider->value())); + + depthPerceptionSlider->setMinimum(qRound(DepthPerception::minValue() * 10)); + depthPerceptionSlider->setMaximum(qRound(DepthPerception::maxValue() * 10)); + + despeckleSlider->setMinimum(qRound(1.0 * 10)); + despeckleSlider->setMaximum(qRound(3.0 * 10)); + + const int index = profileCB->findData(DefaultParamsProvider::getInstance()->getProfileName()); + if (index != -1) { + profileCB->setCurrentIndex(index); + } + profileChanged(profileCB->currentIndex()); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(commitChanges())); } void DefaultParamsDialog::updateFixOrientationDisplay(const DefaultParams::FixOrientationParams& params) { - orthogonalRotation = params.getImageRotation(); - setRotationPixmap(); + m_orthogonalRotation = params.getImageRotation(); + setRotationPixmap(); } void DefaultParamsDialog::updatePageSplitDisplay(const DefaultParams::PageSplitParams& params) { - LayoutType layoutType = params.getLayoutType(); - if (layoutType == AUTO_LAYOUT_TYPE) { - layoutModeCB->setCurrentIndex(layoutModeCB->findData(static_cast(MODE_AUTO))); - pageLayoutGroup->setEnabled(false); - } else { - layoutModeCB->setCurrentIndex(layoutModeCB->findData(static_cast(MODE_MANUAL))); - pageLayoutGroup->setEnabled(true); - } - - switch (layoutType) { - case AUTO_LAYOUT_TYPE: - // Uncheck all buttons. Can only be done - // by playing with exclusiveness. - twoPagesBtn->setChecked(true); - twoPagesBtn->setAutoExclusive(false); - twoPagesBtn->setChecked(false); - twoPagesBtn->setAutoExclusive(true); - break; - case SINGLE_PAGE_UNCUT: - singlePageUncutBtn->setChecked(true); - break; - case PAGE_PLUS_OFFCUT: - pagePlusOffcutBtn->setChecked(true); - break; - case TWO_PAGES: - twoPagesBtn->setChecked(true); - break; - } + LayoutType layoutType = params.getLayoutType(); + if (layoutType == AUTO_LAYOUT_TYPE) { + layoutModeCB->setCurrentIndex(layoutModeCB->findData(static_cast(MODE_AUTO))); + pageLayoutGroup->setEnabled(false); + } else { + layoutModeCB->setCurrentIndex(layoutModeCB->findData(static_cast(MODE_MANUAL))); + pageLayoutGroup->setEnabled(true); + } + + switch (layoutType) { + case AUTO_LAYOUT_TYPE: + // Uncheck all buttons. Can only be done + // by playing with exclusiveness. + twoPagesBtn->setChecked(true); + twoPagesBtn->setAutoExclusive(false); + twoPagesBtn->setChecked(false); + twoPagesBtn->setAutoExclusive(true); + break; + case SINGLE_PAGE_UNCUT: + singlePageUncutBtn->setChecked(true); + break; + case PAGE_PLUS_OFFCUT: + pagePlusOffcutBtn->setChecked(true); + break; + case TWO_PAGES: + twoPagesBtn->setChecked(true); + break; + } } void DefaultParamsDialog::updateDeskewDisplay(const DefaultParams::DeskewParams& params) { - AutoManualMode mode = params.getMode(); - if (mode == MODE_AUTO) { - deskewAutoBtn->setChecked(true); - } else { - deskewManualBtn->setChecked(true); - } - angleSpinBox->setEnabled(mode == MODE_MANUAL); - angleSpinBox->setValue(params.getDeskewAngleDeg()); + AutoManualMode mode = params.getMode(); + if (mode == MODE_AUTO) { + deskewAutoBtn->setChecked(true); + } else { + deskewManualBtn->setChecked(true); + } + angleSpinBox->setEnabled(mode == MODE_MANUAL); + angleSpinBox->setValue(params.getDeskewAngleDeg()); } void DefaultParamsDialog::updateSelectContentDisplay(const DefaultParams::SelectContentParams& params) { - if (params.isPageDetectEnabled()) { - pageDetectOptions->setEnabled(true); - AutoManualMode pageDetectMode = params.getPageDetectMode(); - fineTuneBtn->setEnabled(pageDetectMode == MODE_AUTO); - dimensionsWidget->setEnabled(pageDetectMode == MODE_MANUAL); - if (pageDetectMode == MODE_AUTO) { - pageDetectAutoBtn->setChecked(true); - } else { - pageDetectManualBtn->setChecked(true); - } - } else { - pageDetectOptions->setEnabled(false); - pageDetectDisableBtn->setChecked(true); - } - - if (params.isContentDetectEnabled()) { - contentDetectAutoBtn->setChecked(true); - } else { - contentDetectDisableBtn->setChecked(true); - } - - QSizeF pageRectSize = params.getPageRectSize(); - widthSpinBox->setValue(pageRectSize.width()); - heightSpinBox->setValue(pageRectSize.height()); - fineTuneBtn->setChecked(params.isFineTuneCorners()); + const AutoManualMode pageDetectMode = params.getPageDetectMode(); + pageDetectOptions->setEnabled(pageDetectMode != MODE_DISABLED); + fineTuneBtn->setEnabled(pageDetectMode == MODE_AUTO); + dimensionsWidget->setEnabled(pageDetectMode == MODE_MANUAL); + switch (pageDetectMode) { + case MODE_AUTO: + pageDetectAutoBtn->setChecked(true); + break; + case MODE_MANUAL: + pageDetectManualBtn->setChecked(true); + break; + case MODE_DISABLED: + pageDetectDisableBtn->setChecked(true); + break; + } + + if (params.isContentDetectEnabled()) { + contentDetectAutoBtn->setChecked(true); + } else { + contentDetectDisableBtn->setChecked(true); + } + + QSizeF pageRectSize = params.getPageRectSize(); + widthSpinBox->setValue(pageRectSize.width()); + heightSpinBox->setValue(pageRectSize.height()); + fineTuneBtn->setChecked(params.isFineTuneCorners()); } void DefaultParamsDialog::updatePageLayoutDisplay(const DefaultParams::PageLayoutParams& params) { - autoMargins->setChecked(params.isAutoMargins()); - marginsWidget->setEnabled(!params.isAutoMargins()); - - const Margins& margins = params.getHardMargins(); - topMarginSpinBox->setValue(margins.top()); - rightMarginSpinBox->setValue(margins.right()); - bottomMarginSpinBox->setValue(margins.bottom()); - leftMarginSpinBox->setValue(margins.left()); - - topBottomLinkEnabled = (margins.top() == margins.bottom()); - leftRightLinkEnabled = (margins.left() == margins.right()); - setLinkButtonLinked(topBottomLink, topBottomLinkEnabled); - setLinkButtonLinked(leftRightLink, leftRightLinkEnabled); - - const Alignment& alignment = params.getAlignment(); - if (alignment.isAuto()) { - alignmentMode->setCurrentIndex(0); - autoAlignSettingsGroup->setVisible(true); - autoVerticalAligningCB->setChecked(alignment.isAutoVertical()); - autoHorizontalAligningCB->setChecked(alignment.isAutoHorizontal()); - } else if (alignment.isOriginal()) { - alignmentMode->setCurrentIndex(2); - autoAlignSettingsGroup->setVisible(true); - autoVerticalAligningCB->setChecked(alignment.isAutoVertical()); - autoHorizontalAligningCB->setChecked(alignment.isAutoHorizontal()); - } else { - alignmentMode->setCurrentIndex(1); - autoAlignSettingsGroup->setVisible(false); - } - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); - - alignWithOthersCB->setChecked(!alignment.isNull()); - - for (const auto& kv : alignmentByButton) { - if (alignment.isAuto() || alignment.isOriginal()) { - if (!alignment.isAutoHorizontal() && (kv.second.vertical() == Alignment::VCENTER) - && (kv.second.horizontal() == alignment.horizontal())) { - kv.first->setChecked(true); - break; - } else if (!alignment.isAutoVertical() && (kv.second.horizontal() == Alignment::HCENTER) - && (kv.second.vertical() == alignment.vertical())) { - kv.first->setChecked(true); - break; - } - } else if (kv.second == alignment) { - kv.first->setChecked(true); - break; - } + autoMargins->setChecked(params.isAutoMargins()); + marginsWidget->setEnabled(!params.isAutoMargins()); + + const Margins& margins = params.getHardMargins(); + topMarginSpinBox->setValue(margins.top()); + rightMarginSpinBox->setValue(margins.right()); + bottomMarginSpinBox->setValue(margins.bottom()); + leftMarginSpinBox->setValue(margins.left()); + + m_topBottomLinkEnabled = (margins.top() == margins.bottom()); + m_leftRightLinkEnabled = (margins.left() == margins.right()); + setLinkButtonLinked(topBottomLink, m_topBottomLinkEnabled); + setLinkButtonLinked(leftRightLink, m_leftRightLinkEnabled); + + const Alignment& alignment = params.getAlignment(); + if (alignment.isAuto()) { + alignmentMode->setCurrentIndex(0); + autoAlignSettingsGroup->setVisible(true); + autoVerticalAligningCB->setChecked(alignment.isAutoVertical()); + autoHorizontalAligningCB->setChecked(alignment.isAutoHorizontal()); + } else if (alignment.isOriginal()) { + alignmentMode->setCurrentIndex(2); + autoAlignSettingsGroup->setVisible(true); + autoVerticalAligningCB->setChecked(alignment.isAutoVertical()); + autoHorizontalAligningCB->setChecked(alignment.isAutoHorizontal()); + } else { + alignmentMode->setCurrentIndex(1); + autoAlignSettingsGroup->setVisible(false); + } + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); + + alignWithOthersCB->setChecked(!alignment.isNull()); + + for (const auto& kv : m_alignmentByButton) { + if (alignment.isAuto() || alignment.isOriginal()) { + if (!alignment.isAutoHorizontal() && (kv.second.vertical() == Alignment::VCENTER) + && (kv.second.horizontal() == alignment.horizontal())) { + kv.first->setChecked(true); + break; + } else if (!alignment.isAutoVertical() && (kv.second.horizontal() == Alignment::HCENTER) + && (kv.second.vertical() == alignment.vertical())) { + kv.first->setChecked(true); + break; + } + } else if (kv.second == alignment) { + kv.first->setChecked(true); + break; } + } } void DefaultParamsDialog::updateOutputDisplay(const DefaultParams::OutputParams& params) { - const ColorParams& colorParams = params.getColorParams(); - colorModeSelector->setCurrentIndex(colorModeSelector->findData(colorParams.colorMode())); - - const ColorCommonOptions& colorCommonOptions = colorParams.colorCommonOptions(); - const BlackWhiteOptions& blackWhiteOptions = colorParams.blackWhiteOptions(); - cutMarginsCB->setChecked(colorCommonOptions.cutMargins()); - equalizeIlluminationCB->setChecked(blackWhiteOptions.normalizeIllumination()); - equalizeIlluminationColorCB->setChecked(colorCommonOptions.normalizeIllumination()); - savitzkyGolaySmoothingCB->setChecked(blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()); - morphologicalSmoothingCB->setChecked(blackWhiteOptions.isMorphologicalSmoothingEnabled()); - - fillingColorBox->setCurrentIndex(fillingColorBox->findData(colorCommonOptions.getFillingColor())); - - colorSegmentationCB->setChecked(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); - reduceNoiseSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getNoiseReduction()); - redAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getRedThresholdAdjustment()); - greenAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getGreenThresholdAdjustment()); - blueAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getBlueThresholdAdjustment()); - posterizeCB->setChecked(colorCommonOptions.getPosterizationOptions().isEnabled()); - posterizeLevelSB->setValue(colorCommonOptions.getPosterizationOptions().getLevel()); - posterizeNormalizationCB->setChecked(colorCommonOptions.getPosterizationOptions().isNormalizationEnabled()); - posterizeForceBwCB->setChecked(colorCommonOptions.getPosterizationOptions().isForceBlackAndWhite()); - - thresholdMethodBox->setCurrentIndex(thresholdMethodBox->findData(blackWhiteOptions.getBinarizationMethod())); - thresholdSlider->setValue(blackWhiteOptions.thresholdAdjustment()); - thresholLabel->setText(QString::number(thresholdSlider->value())); - sauvolaWindowSize->setValue(blackWhiteOptions.getWindowSize()); - wolfWindowSize->setValue(blackWhiteOptions.getWindowSize()); - sauvolaCoef->setValue(blackWhiteOptions.getSauvolaCoef()); - lowerBound->setValue(blackWhiteOptions.getWolfLowerBound()); - upperBound->setValue(blackWhiteOptions.getWolfUpperBound()); - wolfCoef->setValue(blackWhiteOptions.getWolfCoef()); - - const PictureShapeOptions& pictureShapeOptions = params.getPictureShapeOptions(); - pictureShapeSelector->setCurrentIndex(pictureShapeSelector->findData(pictureShapeOptions.getPictureShape())); - pictureShapeSensitivitySB->setValue(pictureShapeOptions.getSensitivity()); - higherSearchSensitivityCB->setChecked(pictureShapeOptions.isHigherSearchSensitivity()); - - int dpiIndex = dpiSelector->findData(QString::number(params.getDpi().vertical())); - if (dpiIndex != -1) { - dpiSelector->setCurrentIndex(dpiIndex); - } else { - dpiSelector->setCurrentIndex(customDpiItemIdx); - customDpiValue = QString::number(params.getDpi().vertical()); - } - - const SplittingOptions& splittingOptions = params.getSplittingOptions(); - splittingCB->setChecked(splittingOptions.isSplitOutput()); - switch (splittingOptions.getSplittingMode()) { - case BLACK_AND_WHITE_FOREGROUND: - bwForegroundRB->setChecked(true); - break; - case COLOR_FOREGROUND: - colorForegroundRB->setChecked(true); - break; - } - originalBackgroundCB->setChecked(splittingOptions.isOriginalBackground()); - - switch (params.getDespeckleLevel()) { - case DESPECKLE_OFF: - despeckleOffBtn->setChecked(true); - break; - case DESPECKLE_CAUTIOUS: - despeckleCautiousBtn->setChecked(true); - break; - case DESPECKLE_NORMAL: - despeckleNormalBtn->setChecked(true); - break; - case DESPECKLE_AGGRESSIVE: - despeckleAggressiveBtn->setChecked(true); - break; - } - - dewarpingModeCB->setCurrentIndex(dewarpingModeCB->findData(params.getDewarpingOptions().dewarpingMode())); - dewarpingPostDeskewCB->setChecked(params.getDewarpingOptions().needPostDeskew()); - depthPerceptionSlider->setValue(qRound(params.getDepthPerception().value() * 10)); - - // update the display - colorModeChanged(colorModeSelector->currentIndex()); - thresholdMethodChanged(thresholdMethodBox->currentIndex()); - pictureShapeChanged(pictureShapeSelector->currentIndex()); - equalizeIlluminationToggled(equalizeIlluminationCB->isChecked()); - splittingToggled(splittingCB->isChecked()); - dpiSelectionChanged(dpiSelector->currentIndex()); + const ColorParams& colorParams = params.getColorParams(); + colorModeSelector->setCurrentIndex(colorModeSelector->findData(colorParams.colorMode())); + + const ColorCommonOptions& colorCommonOptions = colorParams.colorCommonOptions(); + const BlackWhiteOptions& blackWhiteOptions = colorParams.blackWhiteOptions(); + fillMarginsCB->setChecked(colorCommonOptions.fillMargins()); + fillOffcutCB->setChecked(colorCommonOptions.fillOffcut()); + equalizeIlluminationCB->setChecked(blackWhiteOptions.normalizeIllumination()); + equalizeIlluminationColorCB->setChecked(colorCommonOptions.normalizeIllumination()); + savitzkyGolaySmoothingCB->setChecked(blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()); + morphologicalSmoothingCB->setChecked(blackWhiteOptions.isMorphologicalSmoothingEnabled()); + + fillingColorBox->setCurrentIndex(fillingColorBox->findData(colorCommonOptions.getFillingColor())); + + colorSegmentationCB->setChecked(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); + reduceNoiseSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getNoiseReduction()); + redAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getRedThresholdAdjustment()); + greenAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getGreenThresholdAdjustment()); + blueAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getBlueThresholdAdjustment()); + posterizeCB->setChecked(colorCommonOptions.getPosterizationOptions().isEnabled()); + posterizeLevelSB->setValue(colorCommonOptions.getPosterizationOptions().getLevel()); + posterizeNormalizationCB->setChecked(colorCommonOptions.getPosterizationOptions().isNormalizationEnabled()); + posterizeForceBwCB->setChecked(colorCommonOptions.getPosterizationOptions().isForceBlackAndWhite()); + + thresholdMethodBox->setCurrentIndex(thresholdMethodBox->findData(blackWhiteOptions.getBinarizationMethod())); + thresholdSlider->setValue(blackWhiteOptions.thresholdAdjustment()); + thresholLabel->setText(QString::number(thresholdSlider->value())); + sauvolaWindowSize->setValue(blackWhiteOptions.getWindowSize()); + wolfWindowSize->setValue(blackWhiteOptions.getWindowSize()); + sauvolaCoef->setValue(blackWhiteOptions.getSauvolaCoef()); + lowerBound->setValue(blackWhiteOptions.getWolfLowerBound()); + upperBound->setValue(blackWhiteOptions.getWolfUpperBound()); + wolfCoef->setValue(blackWhiteOptions.getWolfCoef()); + + const PictureShapeOptions& pictureShapeOptions = params.getPictureShapeOptions(); + pictureShapeSelector->setCurrentIndex(pictureShapeSelector->findData(pictureShapeOptions.getPictureShape())); + pictureShapeSensitivitySB->setValue(pictureShapeOptions.getSensitivity()); + higherSearchSensitivityCB->setChecked(pictureShapeOptions.isHigherSearchSensitivity()); + + int dpiIndex = dpiSelector->findData(QString::number(params.getDpi().vertical())); + if (dpiIndex != -1) { + dpiSelector->setCurrentIndex(dpiIndex); + } else { + dpiSelector->setCurrentIndex(m_customDpiItemIdx); + m_customDpiValue = QString::number(params.getDpi().vertical()); + } + + const SplittingOptions& splittingOptions = params.getSplittingOptions(); + splittingCB->setChecked(splittingOptions.isSplitOutput()); + switch (splittingOptions.getSplittingMode()) { + case BLACK_AND_WHITE_FOREGROUND: + bwForegroundRB->setChecked(true); + break; + case COLOR_FOREGROUND: + colorForegroundRB->setChecked(true); + break; + } + originalBackgroundCB->setChecked(splittingOptions.isOriginalBackgroundEnabled()); + + const double despeckleLevel = params.getDespeckleLevel(); + if (despeckleLevel != 0) { + despeckleCB->setChecked(true); + despeckleSlider->setValue(qRound(10 * despeckleLevel)); + } else { + despeckleCB->setChecked(false); + } + despeckleSlider->setToolTip(QString::number(0.1 * despeckleSlider->value())); + + dewarpingModeCB->setCurrentIndex(dewarpingModeCB->findData(params.getDewarpingOptions().dewarpingMode())); + dewarpingPostDeskewCB->setChecked(params.getDewarpingOptions().needPostDeskew()); + depthPerceptionSlider->setValue(qRound(params.getDepthPerception().value() * 10)); + + // update the display + colorModeChanged(colorModeSelector->currentIndex()); + thresholdMethodChanged(thresholdMethodBox->currentIndex()); + pictureShapeChanged(pictureShapeSelector->currentIndex()); + equalizeIlluminationToggled(equalizeIlluminationCB->isChecked()); + splittingToggled(splittingCB->isChecked()); + dpiSelectionChanged(dpiSelector->currentIndex()); + despeckleToggled(despeckleCB->isChecked()); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void DefaultParamsDialog::setupUiConnections() { - connect(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); - connect(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); - connect(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); - connect(layoutModeCB, SIGNAL(currentIndexChanged(int)), this, SLOT(layoutModeChanged(int))); - connect(deskewAutoBtn, SIGNAL(toggled(bool)), this, SLOT(deskewModeChanged(bool))); - connect(pageDetectAutoBtn, SIGNAL(pressed()), this, SLOT(pageDetectAutoToggled())); - connect(pageDetectManualBtn, SIGNAL(pressed()), this, SLOT(pageDetectManualToggled())); - connect(pageDetectDisableBtn, SIGNAL(pressed()), this, SLOT(pageDetectDisableToggled())); - connect(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); - connect(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); - connect(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled(bool))); - connect(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); - connect(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); - connect(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - connect(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - connect(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - connect(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - connect(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); - connect(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); - connect(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); - connect(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); - connect(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); - connect(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); - connect(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); - connect(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); - connect(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); - connect(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); - connect(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); - connect(dpiSelector, SIGNAL(activated(int)), this, SLOT(dpiSelectionChanged(int))); - connect(dpiSelector, SIGNAL(editTextChanged(const QString&)), this, SLOT(dpiEditTextChanged(const QString&))); - connect(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); - connect(profileCB, SIGNAL(currentIndexChanged(int)), this, SLOT(profileChanged(int))); - connect(profileSaveButton, SIGNAL(pressed()), this, SLOT(profileSavePressed())); - connect(profileDeleteButton, SIGNAL(pressed()), this, SLOT(profileDeletePressed())); - connect(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); - connect(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); - connect(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); - connect(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); + CONNECT(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); + CONNECT(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); + CONNECT(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); + CONNECT(layoutModeCB, SIGNAL(currentIndexChanged(int)), this, SLOT(layoutModeChanged(int))); + CONNECT(deskewAutoBtn, SIGNAL(toggled(bool)), this, SLOT(deskewModeChanged(bool))); + CONNECT(pageDetectAutoBtn, SIGNAL(pressed()), this, SLOT(pageDetectAutoToggled())); + CONNECT(pageDetectManualBtn, SIGNAL(pressed()), this, SLOT(pageDetectManualToggled())); + CONNECT(pageDetectDisableBtn, SIGNAL(pressed()), this, SLOT(pageDetectDisableToggled())); + CONNECT(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); + CONNECT(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); + CONNECT(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled(bool))); + CONNECT(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); + CONNECT(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); + CONNECT(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); + CONNECT(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); + CONNECT(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); + CONNECT(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); + CONNECT(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); + CONNECT(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); + CONNECT(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); + CONNECT(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); + CONNECT(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); + CONNECT(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); + CONNECT(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); + CONNECT(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); + CONNECT(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); + CONNECT(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); + CONNECT(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); + CONNECT(dpiSelector, SIGNAL(activated(int)), this, SLOT(dpiSelectionChanged(int))); + CONNECT(dpiSelector, SIGNAL(editTextChanged(const QString&)), this, SLOT(dpiEditTextChanged(const QString&))); + CONNECT(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); + CONNECT(profileCB, SIGNAL(currentIndexChanged(int)), this, SLOT(profileChanged(int))); + CONNECT(profileSaveButton, SIGNAL(pressed()), this, SLOT(profileSavePressed())); + CONNECT(profileDeleteButton, SIGNAL(pressed()), this, SLOT(profileDeletePressed())); + CONNECT(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); + CONNECT(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); + CONNECT(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); + CONNECT(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); + CONNECT(despeckleCB, SIGNAL(clicked(bool)), this, SLOT(despeckleToggled(bool))); + CONNECT(despeckleSlider, SIGNAL(valueChanged(int)), this, SLOT(despeckleSliderValueChanged(int))); } +#undef CONNECT + void DefaultParamsDialog::removeUiConnections() { - disconnect(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); - disconnect(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); - disconnect(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); - disconnect(layoutModeCB, SIGNAL(currentIndexChanged(int)), this, SLOT(layoutModeChanged(int))); - disconnect(deskewAutoBtn, SIGNAL(toggled(bool)), this, SLOT(deskewModeChanged(bool))); - disconnect(pageDetectAutoBtn, SIGNAL(pressed()), this, SLOT(pageDetectAutoToggled())); - disconnect(pageDetectManualBtn, SIGNAL(pressed()), this, SLOT(pageDetectManualToggled())); - disconnect(pageDetectDisableBtn, SIGNAL(pressed()), this, SLOT(pageDetectDisableToggled())); - disconnect(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); - disconnect(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); - disconnect(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled(bool))); - disconnect(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); - disconnect(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); - disconnect(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - disconnect(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - disconnect(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - disconnect(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - disconnect(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); - disconnect(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); - disconnect(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); - disconnect(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); - disconnect(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); - disconnect(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); - disconnect(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); - disconnect(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); - disconnect(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); - disconnect(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); - disconnect(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); - disconnect(dpiSelector, SIGNAL(activated(int)), this, SLOT(dpiSelectionChanged(int))); - disconnect(dpiSelector, SIGNAL(editTextChanged(const QString&)), this, SLOT(dpiEditTextChanged(const QString&))); - disconnect(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); - disconnect(profileCB, SIGNAL(currentIndexChanged(int)), this, SLOT(profileChanged(int))); - disconnect(profileSaveButton, SIGNAL(pressed()), this, SLOT(profileSavePressed())); - disconnect(profileDeleteButton, SIGNAL(pressed()), this, SLOT(profileDeletePressed())); - disconnect(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); - disconnect(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); - disconnect(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); - disconnect(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } void DefaultParamsDialog::rotateLeft() { - OrthogonalRotation rotation(orthogonalRotation); - rotation.prevClockwiseDirection(); - setRotation(rotation); + OrthogonalRotation rotation(m_orthogonalRotation); + rotation.prevClockwiseDirection(); + setRotation(rotation); } void DefaultParamsDialog::rotateRight() { - OrthogonalRotation rotation(orthogonalRotation); - rotation.nextClockwiseDirection(); - setRotation(rotation); + OrthogonalRotation rotation(m_orthogonalRotation); + rotation.nextClockwiseDirection(); + setRotation(rotation); } void DefaultParamsDialog::resetRotation() { - setRotation(OrthogonalRotation()); + setRotation(OrthogonalRotation()); } void DefaultParamsDialog::setRotation(const OrthogonalRotation& rotation) { - if (rotation == orthogonalRotation) { - return; - } + if (rotation == m_orthogonalRotation) { + return; + } - orthogonalRotation = rotation; - setRotationPixmap(); + m_orthogonalRotation = rotation; + setRotationPixmap(); } void DefaultParamsDialog::setRotationPixmap() { - const char* path = nullptr; - - switch (orthogonalRotation.toDegrees()) { - case 0: - path = ":/icons/big-up-arrow.png"; - break; - case 90: - path = ":/icons/big-right-arrow.png"; - break; - case 180: - path = ":/icons/big-down-arrow.png"; - break; - case 270: - path = ":/icons/big-left-arrow.png"; - break; - default: - assert(!"Unreachable"); - } - - rotationIndicator->setPixmap(QPixmap(path)); + const char* path = nullptr; + + switch (m_orthogonalRotation.toDegrees()) { + case 0: + path = ":/icons/big-up-arrow.png"; + break; + case 90: + path = ":/icons/big-right-arrow.png"; + break; + case 180: + path = ":/icons/big-down-arrow.png"; + break; + case 270: + path = ":/icons/big-left-arrow.png"; + break; + default: + assert(!"Unreachable"); + } + + rotationIndicator->setPixmap(QPixmap(path)); } void DefaultParamsDialog::layoutModeChanged(const int idx) { - const AutoManualMode mode = static_cast(layoutModeCB->itemData(idx).toInt()); - if (mode == MODE_AUTO) { - // Uncheck all buttons. Can only be done - // by playing with exclusiveness. - twoPagesBtn->setChecked(true); - twoPagesBtn->setAutoExclusive(false); - twoPagesBtn->setChecked(false); - twoPagesBtn->setAutoExclusive(true); - } else { - singlePageUncutBtn->setChecked(true); - } - pageLayoutGroup->setEnabled(mode == MODE_MANUAL); + const AutoManualMode mode = static_cast(layoutModeCB->itemData(idx).toInt()); + if (mode == MODE_AUTO) { + // Uncheck all buttons. Can only be done + // by playing with exclusiveness. + twoPagesBtn->setChecked(true); + twoPagesBtn->setAutoExclusive(false); + twoPagesBtn->setChecked(false); + twoPagesBtn->setAutoExclusive(true); + } else { + singlePageUncutBtn->setChecked(true); + } + pageLayoutGroup->setEnabled(mode == MODE_MANUAL); } void DefaultParamsDialog::deskewModeChanged(const bool auto_mode) { - angleSpinBox->setEnabled(!auto_mode); + angleSpinBox->setEnabled(!auto_mode); } void DefaultParamsDialog::pageDetectAutoToggled() { - pageDetectOptions->setEnabled(true); - fineTuneBtn->setEnabled(true); - dimensionsWidget->setEnabled(false); + pageDetectOptions->setEnabled(true); + fineTuneBtn->setEnabled(true); + dimensionsWidget->setEnabled(false); } void DefaultParamsDialog::pageDetectManualToggled() { - pageDetectOptions->setEnabled(true); - fineTuneBtn->setEnabled(false); - dimensionsWidget->setEnabled(true); + pageDetectOptions->setEnabled(true); + fineTuneBtn->setEnabled(false); + dimensionsWidget->setEnabled(true); } void DefaultParamsDialog::pageDetectDisableToggled() { - pageDetectOptions->setEnabled(false); + pageDetectOptions->setEnabled(false); } void DefaultParamsDialog::autoMarginsToggled(const bool checked) { - marginsWidget->setEnabled(!checked); + marginsWidget->setEnabled(!checked); } void DefaultParamsDialog::alignmentModeChanged(const int idx) { - autoAlignSettingsGroup->setVisible((idx == 0) || (idx == 2)); - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); + autoAlignSettingsGroup->setVisible((idx == 0) || (idx == 2)); + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); } void DefaultParamsDialog::alignWithOthersToggled(const bool) { - updateAlignmentButtonsEnabled(); + updateAlignmentButtonsEnabled(); } void DefaultParamsDialog::colorModeChanged(const int idx) { - const auto colorMode = static_cast(colorModeSelector->itemData(idx).toInt()); - bool threshold_options_visible = false; - bool picture_shape_visible = false; - bool splitting_options_visible = false; - switch (colorMode) { - case MIXED: - picture_shape_visible = true; - splitting_options_visible = true; - // fall into - case BLACK_AND_WHITE: - threshold_options_visible = true; - // fall into - case COLOR_GRAYSCALE: - break; - } - thresholdOptions->setEnabled(threshold_options_visible); - pictureShapeOptions->setEnabled(picture_shape_visible); - splittingOptions->setEnabled(splitting_options_visible); - - fillingOptions->setEnabled(colorMode != BLACK_AND_WHITE); - - equalizeIlluminationCB->setEnabled(colorMode != COLOR_GRAYSCALE); - equalizeIlluminationColorCB->setEnabled(colorMode != BLACK_AND_WHITE); - if ((colorMode == MIXED)) { - if (equalizeIlluminationColorCB->isChecked()) { - equalizeIlluminationColorCB->setChecked(equalizeIlluminationCB->isChecked()); - } - equalizeIlluminationColorCB->setEnabled(equalizeIlluminationCB->isChecked()); - } - savitzkyGolaySmoothingCB->setEnabled(colorMode != COLOR_GRAYSCALE); - morphologicalSmoothingCB->setEnabled(colorMode != COLOR_GRAYSCALE); - - colorSegmentationCB->setEnabled(threshold_options_visible); - segmenterOptionsWidget->setEnabled(threshold_options_visible); - segmenterOptionsWidget->setEnabled(colorSegmentationCB->isChecked()); - if (threshold_options_visible) { - posterizeCB->setEnabled(colorSegmentationCB->isChecked()); - posterizeOptionsWidget->setEnabled(colorSegmentationCB->isChecked() && posterizeCB->isChecked()); - } else { - posterizeCB->setEnabled(true); - posterizeOptionsWidget->setEnabled(posterizeCB->isChecked()); + const auto colorMode = static_cast(colorModeSelector->itemData(idx).toInt()); + bool threshold_options_visible = false; + bool picture_shape_visible = false; + bool splitting_options_visible = false; + switch (colorMode) { + case MIXED: + picture_shape_visible = true; + splitting_options_visible = true; + // fall into + case BLACK_AND_WHITE: + threshold_options_visible = true; + // fall into + case COLOR_GRAYSCALE: + break; + } + thresholdOptions->setEnabled(threshold_options_visible); + pictureShapeOptions->setEnabled(picture_shape_visible); + splittingOptions->setEnabled(splitting_options_visible); + + fillingOptions->setEnabled(colorMode != BLACK_AND_WHITE); + + equalizeIlluminationCB->setEnabled(colorMode != COLOR_GRAYSCALE); + equalizeIlluminationColorCB->setEnabled(colorMode != BLACK_AND_WHITE); + if ((colorMode == MIXED)) { + if (equalizeIlluminationColorCB->isChecked()) { + equalizeIlluminationColorCB->setChecked(equalizeIlluminationCB->isChecked()); } + equalizeIlluminationColorCB->setEnabled(equalizeIlluminationCB->isChecked()); + } + savitzkyGolaySmoothingCB->setEnabled(colorMode != COLOR_GRAYSCALE); + morphologicalSmoothingCB->setEnabled(colorMode != COLOR_GRAYSCALE); + + colorSegmentationCB->setEnabled(threshold_options_visible); + segmenterOptionsWidget->setEnabled(threshold_options_visible); + segmenterOptionsWidget->setEnabled(colorSegmentationCB->isChecked()); + if (threshold_options_visible) { + posterizeCB->setEnabled(colorSegmentationCB->isChecked()); + posterizeOptionsWidget->setEnabled(colorSegmentationCB->isChecked() && posterizeCB->isChecked()); + } else { + posterizeCB->setEnabled(true); + posterizeOptionsWidget->setEnabled(posterizeCB->isChecked()); + } } void DefaultParamsDialog::thresholdMethodChanged(const int idx) { - binarizationOptions->setCurrentIndex(idx); + binarizationOptions->setCurrentIndex(idx); } void DefaultParamsDialog::pictureShapeChanged(const int idx) { - const auto shapeMode = static_cast(pictureShapeSelector->itemData(idx).toInt()); - pictureShapeSensitivityOptions->setEnabled(shapeMode == RECTANGULAR_SHAPE); - higherSearchSensitivityCB->setEnabled(shapeMode != OFF_SHAPE); + const auto shapeMode = static_cast(pictureShapeSelector->itemData(idx).toInt()); + pictureShapeSensitivityOptions->setEnabled(shapeMode == RECTANGULAR_SHAPE); + higherSearchSensitivityCB->setEnabled(shapeMode != OFF_SHAPE); } void DefaultParamsDialog::equalizeIlluminationToggled(const bool checked) { - const auto colorMode = static_cast(colorModeSelector->currentData().toInt()); - if (colorMode == MIXED) { - if (equalizeIlluminationColorCB->isChecked()) { - equalizeIlluminationColorCB->setChecked(checked); - } - equalizeIlluminationColorCB->setEnabled(checked); + const auto colorMode = static_cast(colorModeSelector->currentData().toInt()); + if (colorMode == MIXED) { + if (equalizeIlluminationColorCB->isChecked()) { + equalizeIlluminationColorCB->setChecked(checked); } + equalizeIlluminationColorCB->setEnabled(checked); + } } void DefaultParamsDialog::splittingToggled(const bool checked) { - bwForegroundRB->setEnabled(checked); - colorForegroundRB->setEnabled(checked); - originalBackgroundCB->setEnabled(checked && bwForegroundRB->isChecked()); + bwForegroundRB->setEnabled(checked); + colorForegroundRB->setEnabled(checked); + originalBackgroundCB->setEnabled(checked && bwForegroundRB->isChecked()); } void DefaultParamsDialog::bwForegroundToggled(bool checked) { - if (!checked) { - return; - } - originalBackgroundCB->setEnabled(checked); + if (!checked) { + return; + } + originalBackgroundCB->setEnabled(checked); } void DefaultParamsDialog::colorForegroundToggled(bool checked) { - if (!checked) { - return; - } - originalBackgroundCB->setEnabled(!checked); + if (!checked) { + return; + } + originalBackgroundCB->setEnabled(!checked); } void DefaultParamsDialog::loadParams(const DefaultParams& params) { - removeUiConnections(); + removeUiConnections(); - // must be done before updating the displays in order to set the precise of the spin boxes - updateUnits(params.getUnits()); + // must be done before updating the displays in order to set the precise of the spin boxes + updateUnits(params.getUnits()); - updateFixOrientationDisplay(params.getFixOrientationParams()); - updatePageSplitDisplay(params.getPageSplitParams()); - updateDeskewDisplay(params.getDeskewParams()); - updateSelectContentDisplay(params.getSelectContentParams()); - updatePageLayoutDisplay(params.getPageLayoutParams()); - updateOutputDisplay(params.getOutputParams()); + updateFixOrientationDisplay(params.getFixOrientationParams()); + updatePageSplitDisplay(params.getPageSplitParams()); + updateDeskewDisplay(params.getDeskewParams()); + updateSelectContentDisplay(params.getSelectContentParams()); + updatePageLayoutDisplay(params.getPageLayoutParams()); + updateOutputDisplay(params.getOutputParams()); - setupUiConnections(); + setupUiConnections(); } std::unique_ptr DefaultParamsDialog::buildParams() const { - DefaultParams::FixOrientationParams fixOrientationParams(orthogonalRotation); - - LayoutType layoutType; - if (layoutModeCB->currentData() == MODE_AUTO) { - layoutType = AUTO_LAYOUT_TYPE; - } else if (singlePageUncutBtn->isChecked()) { - layoutType = SINGLE_PAGE_UNCUT; - } else if (pagePlusOffcutBtn->isChecked()) { - layoutType = PAGE_PLUS_OFFCUT; - } else { - layoutType = TWO_PAGES; - } - DefaultParams::PageSplitParams pageSplitParams(layoutType); - - DefaultParams::DeskewParams deskewParams(angleSpinBox->value(), - deskewAutoBtn->isChecked() ? MODE_AUTO : MODE_MANUAL); - - DefaultParams::SelectContentParams selectContentParams( - QSizeF(widthSpinBox->value(), heightSpinBox->value()), - pageDetectManualBtn->isChecked() ? MODE_MANUAL : MODE_AUTO, !contentDetectDisableBtn->isChecked(), - !pageDetectDisableBtn->isChecked(), fineTuneBtn->isChecked()); - - Alignment alignment; - switch (alignmentMode->currentIndex()) { - case 0: - if (autoVerticalAligningCB->isChecked()) { - alignment.setVertical(Alignment::VAUTO); - } else { - alignment.setVertical(alignmentByButton.at(getCheckedAlignmentButton()).vertical()); - } - if (autoHorizontalAligningCB->isChecked()) { - alignment.setHorizontal(Alignment::HAUTO); - } else { - alignment.setHorizontal(alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); - } - break; - case 1: - alignment = alignmentByButton.at(getCheckedAlignmentButton()); - break; - case 2: - if (autoVerticalAligningCB->isChecked()) { - alignment.setVertical(Alignment::VORIGINAL); - } else { - alignment.setVertical(alignmentByButton.at(getCheckedAlignmentButton()).vertical()); - } - if (autoHorizontalAligningCB->isChecked()) { - alignment.setHorizontal(Alignment::HORIGINAL); - } else { - alignment.setHorizontal(alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); - } - break; - default: - break; - } - alignment.setNull(!alignWithOthersCB->isChecked()); - DefaultParams::PageLayoutParams pageLayoutParams(Margins(leftMarginSpinBox->value(), topMarginSpinBox->value(), - rightMarginSpinBox->value(), bottomMarginSpinBox->value()), - alignment, autoMargins->isChecked()); - - const int dpi = (dpiSelector->currentIndex() != customDpiItemIdx) ? dpiSelector->currentText().toInt() - : customDpiValue.toInt(); - ColorParams colorParams; - colorParams.setColorMode(static_cast(colorModeSelector->currentData().toInt())); - - ColorCommonOptions colorCommonOptions; - colorCommonOptions.setFillingColor(static_cast(fillingColorBox->currentData().toInt())); - colorCommonOptions.setCutMargins(cutMarginsCB->isChecked()); - colorCommonOptions.setNormalizeIllumination(equalizeIlluminationColorCB->isChecked()); - ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); - posterizationOptions.setEnabled(posterizeCB->isChecked()); - posterizationOptions.setLevel(posterizeLevelSB->value()); - posterizationOptions.setNormalizationEnabled(posterizeNormalizationCB->isChecked()); - posterizationOptions.setForceBlackAndWhite(posterizeForceBwCB->isChecked()); - colorCommonOptions.setPosterizationOptions(posterizationOptions); - colorParams.setColorCommonOptions(colorCommonOptions); - - BlackWhiteOptions blackWhiteOptions; - blackWhiteOptions.setNormalizeIllumination(equalizeIlluminationCB->isChecked()); - blackWhiteOptions.setSavitzkyGolaySmoothingEnabled(savitzkyGolaySmoothingCB->isChecked()); - blackWhiteOptions.setMorphologicalSmoothingEnabled(morphologicalSmoothingCB->isChecked()); - BinarizationMethod binarizationMethod = static_cast(thresholdMethodBox->currentData().toInt()); - blackWhiteOptions.setBinarizationMethod(binarizationMethod); - blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value()); - blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value()); - if (binarizationMethod == SAUVOLA) { - blackWhiteOptions.setWindowSize(sauvolaWindowSize->value()); - } else if (binarizationMethod == WOLF) { - blackWhiteOptions.setWindowSize(wolfWindowSize->value()); - } - blackWhiteOptions.setWolfCoef(wolfCoef->value()); - blackWhiteOptions.setWolfLowerBound(upperBound->value()); - blackWhiteOptions.setWolfLowerBound(lowerBound->value()); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setEnabled(colorSegmentationCB->isChecked()); - segmenterOptions.setNoiseReduction(reduceNoiseSB->value()); - segmenterOptions.setRedThresholdAdjustment(redAdjustmentSB->value()); - segmenterOptions.setGreenThresholdAdjustment(greenAdjustmentSB->value()); - segmenterOptions.setBlueThresholdAdjustment(blueAdjustmentSB->value()); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - colorParams.setBlackWhiteOptions(blackWhiteOptions); - - SplittingOptions splittingOptions; - splittingOptions.setSplitOutput(splittingCB->isChecked()); - splittingOptions.setSplittingMode(bwForegroundRB->isChecked() ? BLACK_AND_WHITE_FOREGROUND : COLOR_FOREGROUND); - splittingOptions.setOriginalBackground(originalBackgroundCB->isChecked()); - - PictureShapeOptions pictureShapeOptions; - pictureShapeOptions.setPictureShape(static_cast(pictureShapeSelector->currentData().toInt())); - pictureShapeOptions.setSensitivity(pictureShapeSensitivitySB->value()); - pictureShapeOptions.setHigherSearchSensitivity(higherSearchSensitivityCB->isChecked()); - - DewarpingOptions dewarpingOptions; - dewarpingOptions.setDewarpingMode(static_cast(dewarpingModeCB->currentData().toInt())); - dewarpingOptions.setPostDeskew(dewarpingPostDeskewCB->isChecked()); - - DespeckleLevel despeckleLevel; - if (despeckleAggressiveBtn->isChecked()) { - despeckleLevel = DESPECKLE_AGGRESSIVE; - } else if (despeckleNormalBtn->isChecked()) { - despeckleLevel = DESPECKLE_NORMAL; - } else if (despeckleCautiousBtn->isChecked()) { - despeckleLevel = DESPECKLE_CAUTIOUS; - } else { - despeckleLevel = DESPECKLE_OFF; - } - - DefaultParams::OutputParams outputParams(Dpi(dpi, dpi), colorParams, splittingOptions, pictureShapeOptions, - DepthPerception(0.1 * depthPerceptionSlider->value()), dewarpingOptions, - despeckleLevel); - - std::unique_ptr defaultParams = std::make_unique( - fixOrientationParams, deskewParams, pageSplitParams, selectContentParams, pageLayoutParams, outputParams); - defaultParams->setUnits(currentUnits); - - return defaultParams; + DefaultParams::FixOrientationParams fixOrientationParams(m_orthogonalRotation); + + LayoutType layoutType; + if (layoutModeCB->currentData() == MODE_AUTO) { + layoutType = AUTO_LAYOUT_TYPE; + } else if (singlePageUncutBtn->isChecked()) { + layoutType = SINGLE_PAGE_UNCUT; + } else if (pagePlusOffcutBtn->isChecked()) { + layoutType = PAGE_PLUS_OFFCUT; + } else { + layoutType = TWO_PAGES; + } + DefaultParams::PageSplitParams pageSplitParams(layoutType); + + DefaultParams::DeskewParams deskewParams(angleSpinBox->value(), deskewAutoBtn->isChecked() ? MODE_AUTO : MODE_MANUAL); + + const AutoManualMode pageBoxMode + = pageDetectDisableBtn->isChecked() ? MODE_DISABLED : pageDetectManualBtn->isChecked() ? MODE_MANUAL : MODE_AUTO; + DefaultParams::SelectContentParams selectContentParams(QSizeF(widthSpinBox->value(), heightSpinBox->value()), + !contentDetectDisableBtn->isChecked(), pageBoxMode, + fineTuneBtn->isChecked()); + + Alignment alignment; + switch (alignmentMode->currentIndex()) { + case 0: + if (autoVerticalAligningCB->isChecked()) { + alignment.setVertical(Alignment::VAUTO); + } else { + alignment.setVertical(m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()); + } + if (autoHorizontalAligningCB->isChecked()) { + alignment.setHorizontal(Alignment::HAUTO); + } else { + alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); + } + break; + case 1: + alignment = m_alignmentByButton.at(getCheckedAlignmentButton()); + break; + case 2: + if (autoVerticalAligningCB->isChecked()) { + alignment.setVertical(Alignment::VORIGINAL); + } else { + alignment.setVertical(m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()); + } + if (autoHorizontalAligningCB->isChecked()) { + alignment.setHorizontal(Alignment::HORIGINAL); + } else { + alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); + } + break; + default: + break; + } + alignment.setNull(!alignWithOthersCB->isChecked()); + DefaultParams::PageLayoutParams pageLayoutParams(Margins(leftMarginSpinBox->value(), topMarginSpinBox->value(), + rightMarginSpinBox->value(), bottomMarginSpinBox->value()), + alignment, autoMargins->isChecked()); + + const int dpi = (dpiSelector->currentIndex() != m_customDpiItemIdx) ? dpiSelector->currentText().toInt() + : m_customDpiValue.toInt(); + ColorParams colorParams; + colorParams.setColorMode(static_cast(colorModeSelector->currentData().toInt())); + + ColorCommonOptions colorCommonOptions; + colorCommonOptions.setFillingColor(static_cast(fillingColorBox->currentData().toInt())); + colorCommonOptions.setFillMargins(fillMarginsCB->isChecked()); + colorCommonOptions.setFillOffcut(fillOffcutCB->isChecked()); + colorCommonOptions.setNormalizeIllumination(equalizeIlluminationColorCB->isChecked()); + ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); + posterizationOptions.setEnabled(posterizeCB->isChecked()); + posterizationOptions.setLevel(posterizeLevelSB->value()); + posterizationOptions.setNormalizationEnabled(posterizeNormalizationCB->isChecked()); + posterizationOptions.setForceBlackAndWhite(posterizeForceBwCB->isChecked()); + colorCommonOptions.setPosterizationOptions(posterizationOptions); + colorParams.setColorCommonOptions(colorCommonOptions); + + BlackWhiteOptions blackWhiteOptions; + blackWhiteOptions.setNormalizeIllumination(equalizeIlluminationCB->isChecked()); + blackWhiteOptions.setSavitzkyGolaySmoothingEnabled(savitzkyGolaySmoothingCB->isChecked()); + blackWhiteOptions.setMorphologicalSmoothingEnabled(morphologicalSmoothingCB->isChecked()); + BinarizationMethod binarizationMethod = static_cast(thresholdMethodBox->currentData().toInt()); + blackWhiteOptions.setBinarizationMethod(binarizationMethod); + blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value()); + blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value()); + if (binarizationMethod == SAUVOLA) { + blackWhiteOptions.setWindowSize(sauvolaWindowSize->value()); + } else if (binarizationMethod == WOLF) { + blackWhiteOptions.setWindowSize(wolfWindowSize->value()); + } + blackWhiteOptions.setWolfCoef(wolfCoef->value()); + blackWhiteOptions.setWolfLowerBound(upperBound->value()); + blackWhiteOptions.setWolfLowerBound(lowerBound->value()); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setEnabled(colorSegmentationCB->isChecked()); + segmenterOptions.setNoiseReduction(reduceNoiseSB->value()); + segmenterOptions.setRedThresholdAdjustment(redAdjustmentSB->value()); + segmenterOptions.setGreenThresholdAdjustment(greenAdjustmentSB->value()); + segmenterOptions.setBlueThresholdAdjustment(blueAdjustmentSB->value()); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + colorParams.setBlackWhiteOptions(blackWhiteOptions); + + SplittingOptions splittingOptions; + splittingOptions.setSplitOutput(splittingCB->isChecked()); + splittingOptions.setSplittingMode(bwForegroundRB->isChecked() ? BLACK_AND_WHITE_FOREGROUND : COLOR_FOREGROUND); + splittingOptions.setOriginalBackgroundEnabled(originalBackgroundCB->isChecked()); + + PictureShapeOptions pictureShapeOptions; + pictureShapeOptions.setPictureShape(static_cast(pictureShapeSelector->currentData().toInt())); + pictureShapeOptions.setSensitivity(pictureShapeSensitivitySB->value()); + pictureShapeOptions.setHigherSearchSensitivity(higherSearchSensitivityCB->isChecked()); + + DewarpingOptions dewarpingOptions; + dewarpingOptions.setDewarpingMode(static_cast(dewarpingModeCB->currentData().toInt())); + dewarpingOptions.setPostDeskew(dewarpingPostDeskewCB->isChecked()); + + double despeckleLevel; + if (despeckleCB->isChecked()) { + despeckleLevel = 0.1 * despeckleSlider->value(); + } else { + despeckleLevel = 0; + } + + DefaultParams::OutputParams outputParams(Dpi(dpi, dpi), colorParams, splittingOptions, pictureShapeOptions, + DepthPerception(0.1 * depthPerceptionSlider->value()), dewarpingOptions, + despeckleLevel); + + std::unique_ptr defaultParams = std::make_unique( + fixOrientationParams, deskewParams, pageSplitParams, selectContentParams, pageLayoutParams, outputParams); + defaultParams->setUnits(m_currentUnits); + + return defaultParams; } void DefaultParamsDialog::updateUnits(const Units units) { - currentUnits = units; - unitsLabel->setText(unitsToLocalizedString(units)); - - { - int decimals; - double step; - switch (units) { - case PIXELS: - case MILLIMETRES: - decimals = 1; - step = 1.0; - break; - default: - decimals = 2; - step = 0.01; - break; - } - - widthSpinBox->setDecimals(decimals); - widthSpinBox->setSingleStep(step); - heightSpinBox->setDecimals(decimals); - heightSpinBox->setSingleStep(step); + m_currentUnits = units; + unitsLabel->setText(unitsToLocalizedString(units)); + + { + int decimals; + double step; + switch (units) { + case PIXELS: + case MILLIMETRES: + decimals = 1; + step = 1.0; + break; + default: + decimals = 2; + step = 0.01; + break; } - { - int decimals; - double step; - switch (units) { - case PIXELS: - case MILLIMETRES: - decimals = 1; - step = 1.0; - break; - default: - decimals = 2; - step = 0.01; - break; - } - - topMarginSpinBox->setDecimals(decimals); - topMarginSpinBox->setSingleStep(step); - bottomMarginSpinBox->setDecimals(decimals); - bottomMarginSpinBox->setSingleStep(step); - leftMarginSpinBox->setDecimals(decimals); - leftMarginSpinBox->setSingleStep(step); - rightMarginSpinBox->setDecimals(decimals); - rightMarginSpinBox->setSingleStep(step); + widthSpinBox->setDecimals(decimals); + widthSpinBox->setSingleStep(step); + heightSpinBox->setDecimals(decimals); + heightSpinBox->setSingleStep(step); + } + + { + int decimals; + double step; + switch (units) { + case PIXELS: + case MILLIMETRES: + decimals = 1; + step = 1.0; + break; + default: + decimals = 2; + step = 0.01; + break; } + + topMarginSpinBox->setDecimals(decimals); + topMarginSpinBox->setSingleStep(step); + bottomMarginSpinBox->setDecimals(decimals); + bottomMarginSpinBox->setSingleStep(step); + leftMarginSpinBox->setDecimals(decimals); + leftMarginSpinBox->setSingleStep(step); + rightMarginSpinBox->setDecimals(decimals); + rightMarginSpinBox->setSingleStep(step); + } } void DefaultParamsDialog::setLinkButtonLinked(QToolButton* button, bool linked) { - button->setIcon(linked ? chainIcon : brokenChainIcon); + button->setIcon(linked ? m_chainIcon : m_brokenChainIcon); } void DefaultParamsDialog::topBottomLinkClicked() { - topBottomLinkEnabled = !topBottomLinkEnabled; - setLinkButtonLinked(topBottomLink, topBottomLinkEnabled); - if (topBottomLinkEnabled && (topMarginSpinBox->value() != bottomMarginSpinBox->value())) { - const double new_margin = std::min(topMarginSpinBox->value(), bottomMarginSpinBox->value()); - topMarginSpinBox->setValue(new_margin); - bottomMarginSpinBox->setValue(new_margin); - } + m_topBottomLinkEnabled = !m_topBottomLinkEnabled; + setLinkButtonLinked(topBottomLink, m_topBottomLinkEnabled); + if (m_topBottomLinkEnabled && (topMarginSpinBox->value() != bottomMarginSpinBox->value())) { + const double new_margin = std::min(topMarginSpinBox->value(), bottomMarginSpinBox->value()); + topMarginSpinBox->setValue(new_margin); + bottomMarginSpinBox->setValue(new_margin); + } } void DefaultParamsDialog::leftRightLinkClicked() { - leftRightLinkEnabled = !leftRightLinkEnabled; - setLinkButtonLinked(leftRightLink, leftRightLinkEnabled); - if (leftRightLinkEnabled && (leftMarginSpinBox->value() != rightMarginSpinBox->value())) { - const double new_margin = std::min(leftMarginSpinBox->value(), rightMarginSpinBox->value()); - leftMarginSpinBox->setValue(new_margin); - rightMarginSpinBox->setValue(new_margin); - } + m_leftRightLinkEnabled = !m_leftRightLinkEnabled; + setLinkButtonLinked(leftRightLink, m_leftRightLinkEnabled); + if (m_leftRightLinkEnabled && (leftMarginSpinBox->value() != rightMarginSpinBox->value())) { + const double new_margin = std::min(leftMarginSpinBox->value(), rightMarginSpinBox->value()); + leftMarginSpinBox->setValue(new_margin); + rightMarginSpinBox->setValue(new_margin); + } } void DefaultParamsDialog::horMarginsChanged(double val) { - if (ignoreMarginChanges) { - return; - } - if (leftRightLinkEnabled) { - const ScopedIncDec scopeGuard(ignoreMarginChanges); - leftMarginSpinBox->setValue(val); - rightMarginSpinBox->setValue(val); - } + if (m_ignoreMarginChanges) { + return; + } + if (m_leftRightLinkEnabled) { + const ScopedIncDec scopeGuard(m_ignoreMarginChanges); + leftMarginSpinBox->setValue(val); + rightMarginSpinBox->setValue(val); + } } void DefaultParamsDialog::vertMarginsChanged(double val) { - if (ignoreMarginChanges) { - return; - } - if (topBottomLinkEnabled) { - const ScopedIncDec scopeGuard(ignoreMarginChanges); - topMarginSpinBox->setValue(val); - bottomMarginSpinBox->setValue(val); - } + if (m_ignoreMarginChanges) { + return; + } + if (m_topBottomLinkEnabled) { + const ScopedIncDec scopeGuard(m_ignoreMarginChanges); + topMarginSpinBox->setValue(val); + bottomMarginSpinBox->setValue(val); + } } void DefaultParamsDialog::thresholdSliderValueChanged(int value) { - const QString tooltip_text(QString::number(value)); - thresholdSlider->setToolTip(tooltip_text); - - thresholLabel->setText(QString::number(value)); - - // Show the tooltip immediately. - const QPoint center(thresholdSlider->rect().center()); - QPoint tooltip_pos(thresholdSlider->mapFromGlobal(QCursor::pos())); - tooltip_pos.setY(center.y()); - tooltip_pos.setX(qBound(0, tooltip_pos.x(), thresholdSlider->width())); - tooltip_pos = thresholdSlider->mapToGlobal(tooltip_pos); - QToolTip::showText(tooltip_pos, tooltip_text, thresholdSlider); + const QString tooltip_text(QString::number(value)); + thresholdSlider->setToolTip(tooltip_text); + + thresholLabel->setText(QString::number(value)); + + // Show the tooltip immediately. + const QPoint center(thresholdSlider->rect().center()); + QPoint tooltip_pos(thresholdSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), thresholdSlider->width())); + tooltip_pos = thresholdSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, thresholdSlider); } void DefaultParamsDialog::setLighterThreshold() { - thresholdSlider->setValue(thresholdSlider->value() - 1); - thresholLabel->setText(QString::number(thresholdSlider->value())); + thresholdSlider->setValue(thresholdSlider->value() - 1); + thresholLabel->setText(QString::number(thresholdSlider->value())); } void DefaultParamsDialog::setDarkerThreshold() { - thresholdSlider->setValue(thresholdSlider->value() + 1); - thresholLabel->setText(QString::number(thresholdSlider->value())); + thresholdSlider->setValue(thresholdSlider->value() + 1); + thresholLabel->setText(QString::number(thresholdSlider->value())); } void DefaultParamsDialog::setNeutralThreshold() { - thresholdSlider->setValue(0); - thresholLabel->setText(QString::number(thresholdSlider->value())); + thresholdSlider->setValue(0); + thresholLabel->setText(QString::number(thresholdSlider->value())); } void DefaultParamsDialog::dpiSelectionChanged(int index) { - dpiSelector->setEditable(index == customDpiItemIdx); - if (index == customDpiItemIdx) { - dpiSelector->setEditText(customDpiValue); - dpiSelector->lineEdit()->selectAll(); - // It looks like we need to set a new validator - // every time we make the combo box editable. - dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); - } + dpiSelector->setEditable(index == m_customDpiItemIdx); + if (index == m_customDpiItemIdx) { + dpiSelector->setEditText(m_customDpiValue); + dpiSelector->lineEdit()->selectAll(); + // It looks like we need to set a new validator + // every time we make the combo box editable. + dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); + } } void DefaultParamsDialog::dpiEditTextChanged(const QString& text) { - if (dpiSelector->currentIndex() == customDpiItemIdx) { - customDpiValue = text; - } + if (dpiSelector->currentIndex() == m_customDpiItemIdx) { + m_customDpiValue = text; + } } void DefaultParamsDialog::depthPerceptionChangedSlot(const int val) { - const QString tooltip_text(QString::number(0.1 * val)); - depthPerceptionSlider->setToolTip(tooltip_text); - - // Show the tooltip immediately. - const QPoint center(depthPerceptionSlider->rect().center()); - QPoint tooltip_pos(depthPerceptionSlider->mapFromGlobal(QCursor::pos())); - tooltip_pos.setY(center.y()); - tooltip_pos.setX(qBound(0, tooltip_pos.x(), depthPerceptionSlider->width())); - tooltip_pos = depthPerceptionSlider->mapToGlobal(tooltip_pos); - QToolTip::showText(tooltip_pos, tooltip_text, depthPerceptionSlider); + const QString tooltip_text(QString::number(0.1 * val)); + depthPerceptionSlider->setToolTip(tooltip_text); + + // Show the tooltip immediately. + const QPoint center(depthPerceptionSlider->rect().center()); + QPoint tooltip_pos(depthPerceptionSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), depthPerceptionSlider->width())); + tooltip_pos = depthPerceptionSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, depthPerceptionSlider); } void DefaultParamsDialog::profileChanged(const int index) { - if (ignoreProfileChanges) { - return; - } - - profileCB->setEditable(index == customProfileItemIdx); - if (index == customProfileItemIdx) { - profileCB->setEditText(profileCB->currentText()); - profileCB->lineEdit()->selectAll(); - profileCB->setFocus(); - profileSaveButton->setEnabled(true); - profileDeleteButton->setEnabled(false); - setTabWidgetsEnabled(true); - - if (DefaultParamsProvider::getInstance()->getProfileName() == "Custom") { - loadParams(DefaultParamsProvider::getInstance()->getParams()); - } else { - updateUnits(UnitsProvider::getInstance()->getUnits()); - } - } else if (profileCB->currentData().toString() == "Default") { - profileSaveButton->setEnabled(false); - profileDeleteButton->setEnabled(false); - setTabWidgetsEnabled(false); - - std::unique_ptr defaultProfile = profileManager.createDefaultProfile(); - loadParams(*defaultProfile); - } else if (profileCB->currentData().toString() == "Source") { - profileSaveButton->setEnabled(false); - profileDeleteButton->setEnabled(false); - setTabWidgetsEnabled(false); - - std::unique_ptr sourceProfile = profileManager.createSourceProfile(); - loadParams(*sourceProfile); + if (m_ignoreProfileChanges) { + return; + } + + profileCB->setEditable(index == m_customProfileItemIdx); + if (index == m_customProfileItemIdx) { + profileCB->setEditText(profileCB->currentText()); + profileCB->lineEdit()->selectAll(); + profileCB->setFocus(); + profileSaveButton->setEnabled(true); + profileDeleteButton->setEnabled(false); + setTabWidgetsEnabled(true); + + if (DefaultParamsProvider::getInstance()->getProfileName() == "Custom") { + loadParams(DefaultParamsProvider::getInstance()->getParams()); } else { - std::unique_ptr profile = profileManager.readProfile(profileCB->itemData(index).toString()); - if (profile != nullptr) { - profileSaveButton->setEnabled(true); - profileDeleteButton->setEnabled(true); - setTabWidgetsEnabled(true); - - loadParams(*profile); - } else { - QMessageBox::critical(this, tr("Error"), tr("Error loading the profile.")); - profileCB->setCurrentIndex(0); - profileCB->removeItem(index); - customProfileItemIdx--; - - profileSaveButton->setEnabled(false); - profileDeleteButton->setEnabled(false); - setTabWidgetsEnabled(false); - - std::unique_ptr defaultProfile = profileManager.createDefaultProfile(); - loadParams(*defaultProfile); - } + updateUnits(UnitsProvider::getInstance()->getUnits()); } -} + } else if (profileCB->currentData().toString() == "Default") { + profileSaveButton->setEnabled(false); + profileDeleteButton->setEnabled(false); + setTabWidgetsEnabled(false); + + std::unique_ptr defaultProfile = m_profileManager.createDefaultProfile(); + loadParams(*defaultProfile); + } else if (profileCB->currentData().toString() == "Source") { + profileSaveButton->setEnabled(false); + profileDeleteButton->setEnabled(false); + setTabWidgetsEnabled(false); + + std::unique_ptr sourceProfile = m_profileManager.createSourceProfile(); + loadParams(*sourceProfile); + } else { + std::unique_ptr profile = m_profileManager.readProfile(profileCB->itemData(index).toString()); + if (profile != nullptr) { + profileSaveButton->setEnabled(true); + profileDeleteButton->setEnabled(true); + setTabWidgetsEnabled(true); + + loadParams(*profile); + } else { + QMessageBox::critical(this, tr("Error"), tr("Error loading the profile.")); + profileCB->setCurrentIndex(0); + profileCB->removeItem(index); + m_customProfileItemIdx--; -void DefaultParamsDialog::profileSavePressed() { - const ScopedIncDec scopeGuard(ignoreProfileChanges); + profileSaveButton->setEnabled(false); + profileDeleteButton->setEnabled(false); + setTabWidgetsEnabled(false); - if (isProfileNameReserved(profileCB->currentText())) { - QMessageBox::information(this, tr("Error"), - tr("The name conflicts with a default profile name. Please enter a different name.")); - return; + std::unique_ptr defaultProfile = m_profileManager.createDefaultProfile(); + loadParams(*defaultProfile); } + } +} - if (profileManager.writeProfile(*buildParams(), profileCB->currentText())) { - if (profileCB->currentIndex() == customProfileItemIdx) { - const QString profileName = profileCB->currentText(); - profileCB->setItemData(profileCB->currentIndex(), profileName); - profileCB->setItemText(profileCB->currentIndex(), profileName); - customProfileItemIdx = profileCB->count(); - profileCB->addItem(tr("Custom"), "Custom"); - - profileCB->setEditable(false); - profileDeleteButton->setEnabled(true); - } - } else { - QMessageBox::critical(this, tr("Error"), tr("Error saving the profile.")); +void DefaultParamsDialog::profileSavePressed() { + const ScopedIncDec scopeGuard(m_ignoreProfileChanges); + + if (isProfileNameReserved(profileCB->currentText())) { + QMessageBox::information(this, tr("Error"), + tr("The name conflicts with a default profile name. Please enter a different name.")); + return; + } + + if (m_profileManager.writeProfile(*buildParams(), profileCB->currentText())) { + if (profileCB->currentIndex() == m_customProfileItemIdx) { + const QString profileName = profileCB->currentText(); + profileCB->setItemData(profileCB->currentIndex(), profileName); + profileCB->setItemText(profileCB->currentIndex(), profileName); + m_customProfileItemIdx = profileCB->count(); + profileCB->addItem(tr("Custom"), "Custom"); + + profileCB->setEditable(false); + profileDeleteButton->setEnabled(true); } + } else { + QMessageBox::critical(this, tr("Error"), tr("Error saving the profile.")); + } } void DefaultParamsDialog::profileDeletePressed() { - if (profileManager.deleteProfile(profileCB->currentText())) { - { - const ScopedIncDec scopeGuard(ignoreProfileChanges); - - const int deletedProfileIndex = profileCB->currentIndex(); - profileCB->setCurrentIndex(customProfileItemIdx--); - profileCB->removeItem(deletedProfileIndex); - } - profileChanged(customProfileItemIdx); - } else { - QMessageBox::critical(this, tr("Error"), tr("Error deleting the profile.")); + if (m_profileManager.deleteProfile(profileCB->currentText())) { + { + const ScopedIncDec scopeGuard(m_ignoreProfileChanges); + + const int deletedProfileIndex = profileCB->currentIndex(); + profileCB->setCurrentIndex(m_customProfileItemIdx--); + profileCB->removeItem(deletedProfileIndex); } + profileChanged(m_customProfileItemIdx); + } else { + QMessageBox::critical(this, tr("Error"), tr("Error deleting the profile.")); + } } bool DefaultParamsDialog::isProfileNameReserved(const QString& name) { - return reservedProfileNames.find(name) != reservedProfileNames.end(); + return m_reservedProfileNames.find(name) != m_reservedProfileNames.end(); } void DefaultParamsDialog::commitChanges() { - const QString profile = profileCB->currentData().toString(); - std::unique_ptr params; - if (profile == "Default") { - params = profileManager.createDefaultProfile(); - } else if (profile == "Source") { - params = profileManager.createSourceProfile(); - } else { - params = buildParams(); - } - DefaultParamsProvider::getInstance()->setParams(std::move(params), profile); - QSettings().setValue("settings/current_profile", profile); + const QString profile = profileCB->currentData().toString(); + std::unique_ptr params; + if (profile == "Default") { + params = m_profileManager.createDefaultProfile(); + } else if (profile == "Source") { + params = m_profileManager.createSourceProfile(); + } else { + params = buildParams(); + } + DefaultParamsProvider::getInstance()->setParams(std::move(params), profile); + QSettings().setValue("settings/current_profile", profile); } void DefaultParamsDialog::setTabWidgetsEnabled(const bool enabled) { - for (int i = 0; i < tabWidget->count(); i++) { - QWidget* widget = tabWidget->widget(i); - widget->setEnabled(enabled); - } + for (int i = 0; i < tabWidget->count(); i++) { + QWidget* widget = tabWidget->widget(i); + widget->setEnabled(enabled); + } } void DefaultParamsDialog::colorSegmentationToggled(bool checked) { - segmenterOptionsWidget->setEnabled(checked); - if ((colorModeSelector->currentData() == BLACK_AND_WHITE) || (colorModeSelector->currentData() == MIXED)) { - posterizeCB->setEnabled(checked); - posterizeOptionsWidget->setEnabled(checked && posterizeCB->isChecked()); - } + segmenterOptionsWidget->setEnabled(checked); + if ((colorModeSelector->currentData() == BLACK_AND_WHITE) || (colorModeSelector->currentData() == MIXED)) { + posterizeCB->setEnabled(checked); + posterizeOptionsWidget->setEnabled(checked && posterizeCB->isChecked()); + } } void DefaultParamsDialog::posterizeToggled(bool checked) { - posterizeOptionsWidget->setEnabled(checked); + posterizeOptionsWidget->setEnabled(checked); } void DefaultParamsDialog::updateAlignmentButtonsEnabled() { - bool enableHorizontalButtons; - bool enableVerticalButtons; - if ((alignmentMode->currentIndex() == 0) || (alignmentMode->currentIndex() == 2)) { - enableHorizontalButtons = !autoHorizontalAligningCB->isChecked() ? alignWithOthersCB->isChecked() : false; - enableVerticalButtons = !autoVerticalAligningCB->isChecked() ? alignWithOthersCB->isChecked() : false; - } else { - enableHorizontalButtons = enableVerticalButtons = alignWithOthersCB->isChecked(); - } - - alignTopLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignTopBtn->setEnabled(enableVerticalButtons); - alignTopRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignLeftBtn->setEnabled(enableHorizontalButtons); - alignCenterBtn->setEnabled(enableHorizontalButtons || enableVerticalButtons); - alignRightBtn->setEnabled(enableHorizontalButtons); - alignBottomLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignBottomBtn->setEnabled(enableVerticalButtons); - alignBottomRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + bool enableHorizontalButtons; + bool enableVerticalButtons; + if ((alignmentMode->currentIndex() == 0) || (alignmentMode->currentIndex() == 2)) { + enableHorizontalButtons = !autoHorizontalAligningCB->isChecked() ? alignWithOthersCB->isChecked() : false; + enableVerticalButtons = !autoVerticalAligningCB->isChecked() ? alignWithOthersCB->isChecked() : false; + } else { + enableHorizontalButtons = enableVerticalButtons = alignWithOthersCB->isChecked(); + } + + alignTopLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignTopBtn->setEnabled(enableVerticalButtons); + alignTopRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignLeftBtn->setEnabled(enableHorizontalButtons); + alignCenterBtn->setEnabled(enableHorizontalButtons || enableVerticalButtons); + alignRightBtn->setEnabled(enableHorizontalButtons); + alignBottomLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignBottomBtn->setEnabled(enableVerticalButtons); + alignBottomRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); } void DefaultParamsDialog::updateAutoModeButtons() { - if ((alignmentMode->currentIndex() == 0) || (alignmentMode->currentIndex() == 2)) { - if (autoVerticalAligningCB->isChecked() && !autoHorizontalAligningCB->isChecked()) { - autoVerticalAligningCB->setEnabled(false); - } else if (autoHorizontalAligningCB->isChecked() && !autoVerticalAligningCB->isChecked()) { - autoHorizontalAligningCB->setEnabled(false); - } else { - autoVerticalAligningCB->setEnabled(true); - autoHorizontalAligningCB->setEnabled(true); - } - } - + if ((alignmentMode->currentIndex() == 0) || (alignmentMode->currentIndex() == 2)) { if (autoVerticalAligningCB->isChecked() && !autoHorizontalAligningCB->isChecked()) { - switch (alignmentByButton.at(getCheckedAlignmentButton()).horizontal()) { - case Alignment::LEFT: - alignLeftBtn->setChecked(true); - break; - case Alignment::RIGHT: - alignRightBtn->setChecked(true); - break; - default: - alignCenterBtn->setChecked(true); - break; - } + autoVerticalAligningCB->setEnabled(false); } else if (autoHorizontalAligningCB->isChecked() && !autoVerticalAligningCB->isChecked()) { - switch (alignmentByButton.at(getCheckedAlignmentButton()).vertical()) { - case Alignment::TOP: - alignTopBtn->setChecked(true); - break; - case Alignment::BOTTOM: - alignBottomBtn->setChecked(true); - break; - default: - alignCenterBtn->setChecked(true); - break; - } + autoHorizontalAligningCB->setEnabled(false); + } else { + autoVerticalAligningCB->setEnabled(true); + autoHorizontalAligningCB->setEnabled(true); } + } + + if (autoVerticalAligningCB->isChecked() && !autoHorizontalAligningCB->isChecked()) { + switch (m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()) { + case Alignment::LEFT: + alignLeftBtn->setChecked(true); + break; + case Alignment::RIGHT: + alignRightBtn->setChecked(true); + break; + default: + alignCenterBtn->setChecked(true); + break; + } + } else if (autoHorizontalAligningCB->isChecked() && !autoVerticalAligningCB->isChecked()) { + switch (m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()) { + case Alignment::TOP: + alignTopBtn->setChecked(true); + break; + case Alignment::BOTTOM: + alignBottomBtn->setChecked(true); + break; + default: + alignCenterBtn->setChecked(true); + break; + } + } } QToolButton* DefaultParamsDialog::getCheckedAlignmentButton() const { - auto* checkedButton = dynamic_cast(alignmentButtonGroup->checkedButton()); - if (!checkedButton) { - checkedButton = alignCenterBtn; - } + auto* checkedButton = dynamic_cast(m_alignmentButtonGroup->checkedButton()); + if (!checkedButton) { + checkedButton = alignCenterBtn; + } - return checkedButton; + return checkedButton; } void DefaultParamsDialog::autoHorizontalAligningToggled(bool) { - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); } void DefaultParamsDialog::autoVerticalAligningToggled(bool) { - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); +} + +void DefaultParamsDialog::despeckleToggled(bool checked) { + despeckleSlider->setEnabled(checked); +} + +void DefaultParamsDialog::despeckleSliderValueChanged(int value) { + const double new_value = 0.1 * value; + + const QString tooltip_text(QString::number(new_value)); + despeckleSlider->setToolTip(tooltip_text); + + // Show the tooltip immediately. + const QPoint center(despeckleSlider->rect().center()); + QPoint tooltip_pos(despeckleSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), despeckleSlider->width())); + tooltip_pos = despeckleSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, despeckleSlider); } diff --git a/DefaultParamsDialog.h b/DefaultParamsDialog.h index d9ec86edb..f90022e4a 100644 --- a/DefaultParamsDialog.h +++ b/DefaultParamsDialog.h @@ -2,152 +2,159 @@ #ifndef SCANTAILOR_DEFAULTPARAMSDIALOG_H #define SCANTAILOR_DEFAULTPARAMSDIALOG_H -#include -#include +#include +#include +#include #include -#include "ui_DefaultParamsDialog.h" -#include "OrthogonalRotation.h" -#include "DefaultParamsProfileManager.h" +#include #include "DefaultParams.h" +#include "DefaultParamsProfileManager.h" +#include "OrthogonalRotation.h" +#include "ui_DefaultParamsDialog.h" class DefaultParamsDialog : public QDialog, private Ui::DefaultParamsDialog { - Q_OBJECT -private: - QIcon chainIcon; - QIcon brokenChainIcon; - bool leftRightLinkEnabled; - bool topBottomLinkEnabled; - int ignoreMarginChanges; - OrthogonalRotation orthogonalRotation; - std::unordered_map alignmentByButton; - std::unique_ptr alignmentButtonGroup; - DefaultParamsProfileManager profileManager; - int customDpiItemIdx; - QString customDpiValue; - int customProfileItemIdx; - Units currentUnits; - std::set reservedProfileNames; - int ignoreProfileChanges; + Q_OBJECT + public: + explicit DefaultParamsDialog(QWidget* parent = nullptr); + + ~DefaultParamsDialog() override = default; + + private slots: + + void rotateLeft(); -public: - explicit DefaultParamsDialog(QWidget* parent = nullptr); + void rotateRight(); - ~DefaultParamsDialog() override = default; + void resetRotation(); -private slots: + void layoutModeChanged(int idx); - void rotateLeft(); + void deskewModeChanged(bool auto_mode); - void rotateRight(); + void pageDetectAutoToggled(); - void resetRotation(); + void pageDetectManualToggled(); - void layoutModeChanged(int idx); + void pageDetectDisableToggled(); - void deskewModeChanged(bool auto_mode); + void autoMarginsToggled(bool checked); - void pageDetectAutoToggled(); + void alignmentModeChanged(int idx); - void pageDetectManualToggled(); + void alignWithOthersToggled(bool); - void pageDetectDisableToggled(); + void autoHorizontalAligningToggled(bool); - void autoMarginsToggled(bool checked); + void autoVerticalAligningToggled(bool); - void alignmentModeChanged(int idx); + void topBottomLinkClicked(); - void alignWithOthersToggled(bool); + void leftRightLinkClicked(); - void autoHorizontalAligningToggled(bool); + void horMarginsChanged(double val); - void autoVerticalAligningToggled(bool); + void vertMarginsChanged(double val); - void topBottomLinkClicked(); + void colorModeChanged(int idx); - void leftRightLinkClicked(); + void thresholdMethodChanged(int idx); - void horMarginsChanged(double val); + void pictureShapeChanged(int idx); - void vertMarginsChanged(double val); + void equalizeIlluminationToggled(bool checked); - void colorModeChanged(int idx); + void splittingToggled(bool checked); - void thresholdMethodChanged(int idx); + void bwForegroundToggled(bool checked); - void pictureShapeChanged(int idx); + void colorForegroundToggled(bool checked); - void equalizeIlluminationToggled(bool checked); + void thresholdSliderValueChanged(int value); - void splittingToggled(bool checked); + void colorSegmentationToggled(bool checked); - void bwForegroundToggled(bool checked); + void posterizeToggled(bool checked); - void colorForegroundToggled(bool checked); + void setLighterThreshold(); - void thresholdSliderValueChanged(int value); + void setDarkerThreshold(); - void colorSegmentationToggled(bool checked); + void setNeutralThreshold(); - void posterizeToggled(bool checked); + void dpiSelectionChanged(int index); - void setLighterThreshold(); + void dpiEditTextChanged(const QString& text); - void setDarkerThreshold(); + void depthPerceptionChangedSlot(int val); - void setNeutralThreshold(); + void despeckleToggled(bool checked); - void dpiSelectionChanged(int index); + void despeckleSliderValueChanged(int value); - void dpiEditTextChanged(const QString& text); + void profileChanged(int index); - void depthPerceptionChangedSlot(int val); + void profileSavePressed(); - void profileChanged(int index); + void profileDeletePressed(); - void profileSavePressed(); + void commitChanges(); - void profileDeletePressed(); + private: + void updateFixOrientationDisplay(const DefaultParams::FixOrientationParams& params); - void commitChanges(); + void updatePageSplitDisplay(const DefaultParams::PageSplitParams& params); -private: - void updateFixOrientationDisplay(const DefaultParams::FixOrientationParams& params); + void updateDeskewDisplay(const DefaultParams::DeskewParams& params); - void updatePageSplitDisplay(const DefaultParams::PageSplitParams& params); + void updateSelectContentDisplay(const DefaultParams::SelectContentParams& params); - void updateDeskewDisplay(const DefaultParams::DeskewParams& params); + void updatePageLayoutDisplay(const DefaultParams::PageLayoutParams& params); - void updateSelectContentDisplay(const DefaultParams::SelectContentParams& params); + void updateOutputDisplay(const DefaultParams::OutputParams& params); - void updatePageLayoutDisplay(const DefaultParams::PageLayoutParams& params); + void updateUnits(Units units); - void updateOutputDisplay(const DefaultParams::OutputParams& params); + void setupUiConnections(); - void updateUnits(Units units); + void removeUiConnections(); - void setupUiConnections(); + void setRotation(const OrthogonalRotation& rotation); - void removeUiConnections(); + void setRotationPixmap(); - void setRotation(const OrthogonalRotation& rotation); + void updateAlignmentButtonsEnabled(); - void setRotationPixmap(); + void updateAutoModeButtons(); - void updateAlignmentButtonsEnabled(); + QToolButton* getCheckedAlignmentButton() const; - void updateAutoModeButtons(); + void setLinkButtonLinked(QToolButton* button, bool linked); - QToolButton* getCheckedAlignmentButton() const; + void loadParams(const DefaultParams& params); - void setLinkButtonLinked(QToolButton* button, bool linked); + std::unique_ptr buildParams() const; - void loadParams(const DefaultParams& params); + bool isProfileNameReserved(const QString& name); - std::unique_ptr buildParams() const; + void setTabWidgetsEnabled(bool enabled); - bool isProfileNameReserved(const QString& name); + QIcon m_chainIcon; + QIcon m_brokenChainIcon; + bool m_leftRightLinkEnabled; + bool m_topBottomLinkEnabled; + int m_ignoreMarginChanges; + OrthogonalRotation m_orthogonalRotation; + std::unordered_map m_alignmentByButton; + std::unique_ptr m_alignmentButtonGroup; + DefaultParamsProfileManager m_profileManager; + int m_customDpiItemIdx; + QString m_customDpiValue; + int m_customProfileItemIdx; + Units m_currentUnits; + std::set m_reservedProfileNames; + int m_ignoreProfileChanges; - void setTabWidgetsEnabled(bool enabled); + std::list m_connectionList; }; diff --git a/DefaultParamsProfileManager.cpp b/DefaultParamsProfileManager.cpp index 429524f4b..0efe21a5a 100644 --- a/DefaultParamsProfileManager.cpp +++ b/DefaultParamsProfileManager.cpp @@ -1,7 +1,9 @@ +#include "DefaultParamsProfileManager.h" +#include #include #include -#include "DefaultParamsProfileManager.h" +#include "Application.h" #include "DefaultParams.h" #include "version.h" @@ -9,128 +11,133 @@ using namespace page_split; using namespace output; using namespace page_layout; -DefaultParamsProfileManager::DefaultParamsProfileManager() : path(qApp->applicationDirPath() + "/config/profiles") { +DefaultParamsProfileManager::DefaultParamsProfileManager() { + auto* app = dynamic_cast(qApp); + if (app->isPortableVersion()) { + m_path = app->getPortableConfigPath() + "/profiles"; + } else { + m_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/profiles"; + } } -DefaultParamsProfileManager::DefaultParamsProfileManager(const QString& path) : path(path) { -} +DefaultParamsProfileManager::DefaultParamsProfileManager(const QString& path) : m_path(path) {} -std::unique_ptr> DefaultParamsProfileManager::getProfileList() const { - auto profileList = std::make_unique>(); - - QDir dir(path); - if (dir.exists()) { - QList fileInfoList = dir.entryInfoList(); - for (const QFileInfo& fileInfo : fileInfoList) { - if (fileInfo.isFile() && ((fileInfo.suffix() == "stp") || (fileInfo.suffix() == "xml"))) { - profileList->push_back(fileInfo.baseName()); - } - } +std::list DefaultParamsProfileManager::getProfileList() const { + std::list profileList; + + QDir dir(m_path); + if (dir.exists()) { + QList fileInfoList = dir.entryInfoList(); + for (const QFileInfo& fileInfo : fileInfoList) { + if (fileInfo.isFile() && ((fileInfo.suffix() == "stp") || (fileInfo.suffix() == "xml"))) { + profileList.push_back(fileInfo.baseName()); + } } + } - return profileList; + return profileList; } std::unique_ptr DefaultParamsProfileManager::readProfile(const QString& name) const { - QDir dir(path); - QFileInfo profile(dir.absoluteFilePath(name + ".stp")); + QDir dir(m_path); + QFileInfo profile(dir.absoluteFilePath(name + ".stp")); + if (!profile.exists()) { + profile = dir.absoluteFilePath(name + ".xml"); if (!profile.exists()) { - profile = dir.absoluteFilePath(name + ".xml"); - if (!profile.exists()) { - return nullptr; - } + return nullptr; } + } - QFile profileFile(profile.filePath()); - if (!profileFile.open(QIODevice::ReadOnly)) { - return nullptr; - } + QFile profileFile(profile.filePath()); + if (!profileFile.open(QIODevice::ReadOnly)) { + return nullptr; + } - QDomDocument doc; - if (!doc.setContent(&profileFile)) { - return nullptr; - } + QDomDocument doc; + if (!doc.setContent(&profileFile)) { + return nullptr; + } - profileFile.close(); + profileFile.close(); - const QDomElement profileElement(doc.documentElement()); - const QString version = profileElement.attribute("version"); - if (version.isNull() || (version.toInt() != PROJECT_VERSION)) { - return nullptr; - } - const QDomElement defaultParamsElement(profileElement.namedItem("default-params").toElement()); + const QDomElement profileElement(doc.documentElement()); + const QString version = profileElement.attribute("version"); + if (version.isNull() || (version.toInt() != PROJECT_VERSION)) { + return nullptr; + } + const QDomElement defaultParamsElement(profileElement.namedItem("default-params").toElement()); - return std::make_unique(defaultParamsElement); + return std::make_unique(defaultParamsElement); } bool DefaultParamsProfileManager::writeProfile(const DefaultParams& params, const QString& name) const { - QDomDocument doc; - QDomElement rootElement(doc.createElement("profile")); - doc.appendChild(rootElement); - rootElement.setAttribute("version", PROJECT_VERSION); - rootElement.appendChild(params.toXml(doc, "default-params")); - - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - - QFile file(dir.absoluteFilePath(name + ".stp")); - if (file.open(QIODevice::WriteOnly)) { - QTextStream textStream(&file); - doc.save(textStream, 2); - return true; - } - - return false; + QDomDocument doc; + QDomElement rootElement(doc.createElement("profile")); + doc.appendChild(rootElement); + rootElement.setAttribute("version", PROJECT_VERSION); + rootElement.appendChild(params.toXml(doc, "default-params")); + + QDir dir(m_path); + if (!dir.exists()) { + dir.mkpath("."); + } + + QFile file(dir.absoluteFilePath(name + ".stp")); + if (file.open(QIODevice::WriteOnly)) { + QTextStream textStream(&file); + doc.save(textStream, 2); + return true; + } + + return false; } std::unique_ptr DefaultParamsProfileManager::createDefaultProfile() const { - return std::make_unique(); + return std::make_unique(); } std::unique_ptr DefaultParamsProfileManager::createSourceProfile() const { - DefaultParams::DeskewParams deskewParams; - deskewParams.setMode(MODE_MANUAL); + DefaultParams::DeskewParams deskewParams; + deskewParams.setMode(MODE_MANUAL); - DefaultParams::PageSplitParams pageSplitParams; - pageSplitParams.setLayoutType(SINGLE_PAGE_UNCUT); + DefaultParams::PageSplitParams pageSplitParams; + pageSplitParams.setLayoutType(SINGLE_PAGE_UNCUT); - DefaultParams::SelectContentParams selectContentParams; - selectContentParams.setContentDetectEnabled(false); + DefaultParams::SelectContentParams selectContentParams; + selectContentParams.setContentDetectEnabled(false); - DefaultParams::PageLayoutParams pageLayoutParams; - pageLayoutParams.setHardMargins(Margins(0, 0, 0, 0)); + DefaultParams::PageLayoutParams pageLayoutParams; + pageLayoutParams.setHardMargins(Margins(0, 0, 0, 0)); - Alignment alignment; - alignment.setNull(true); - pageLayoutParams.setAlignment(alignment); + Alignment alignment; + alignment.setNull(true); + pageLayoutParams.setAlignment(alignment); - DefaultParams::OutputParams outputParams; + DefaultParams::OutputParams outputParams; - ColorParams colorParams; - colorParams.setColorMode(COLOR_GRAYSCALE); + ColorParams colorParams; + colorParams.setColorMode(COLOR_GRAYSCALE); - ColorCommonOptions colorCommonOptions; - colorCommonOptions.setCutMargins(false); - colorParams.setColorCommonOptions(colorCommonOptions); + ColorCommonOptions colorCommonOptions; + colorCommonOptions.setFillMargins(false); + colorParams.setColorCommonOptions(colorCommonOptions); - outputParams.setColorParams(colorParams); + outputParams.setColorParams(colorParams); - return std::make_unique(DefaultParams::FixOrientationParams(), deskewParams, pageSplitParams, - selectContentParams, pageLayoutParams, outputParams); + return std::make_unique(DefaultParams::FixOrientationParams(), deskewParams, pageSplitParams, + selectContentParams, pageLayoutParams, outputParams); } bool DefaultParamsProfileManager::deleteProfile(const QString& name) const { - QDir dir(path); - QFileInfo profile(dir.absoluteFilePath(name + ".stp")); + QDir dir(m_path); + QFileInfo profile(dir.absoluteFilePath(name + ".stp")); + if (!profile.exists()) { + profile = dir.absoluteFilePath(name + ".xml"); if (!profile.exists()) { - profile = dir.absoluteFilePath(name + ".xml"); - if (!profile.exists()) { - return false; - } + return false; } + } - QFile profileFile(profile.filePath()); - return profileFile.remove(); + QFile profileFile(profile.filePath()); + return profileFile.remove(); } diff --git a/DefaultParamsProfileManager.h b/DefaultParamsProfileManager.h index 816505718..85ba68152 100644 --- a/DefaultParamsProfileManager.h +++ b/DefaultParamsProfileManager.h @@ -3,31 +3,34 @@ #define SCANTAILOR_DEFAULTPARAMSPROFILEMANAGER_H +#include #include +#include #include class DefaultParams; class DefaultParamsProfileManager { -private: - QString path; + DECLARE_NON_COPYABLE(DefaultParamsProfileManager) + public: + DefaultParamsProfileManager(); -public: - DefaultParamsProfileManager(); + explicit DefaultParamsProfileManager(const QString& path); - explicit DefaultParamsProfileManager(const QString& path); + std::list getProfileList() const; - std::unique_ptr> getProfileList() const; + std::unique_ptr readProfile(const QString& name) const; - std::unique_ptr readProfile(const QString& name) const; + bool writeProfile(const DefaultParams& params, const QString& name) const; - bool writeProfile(const DefaultParams& params, const QString& name) const; + bool deleteProfile(const QString& name) const; - bool deleteProfile(const QString& name) const; + std::unique_ptr createDefaultProfile() const; - std::unique_ptr createDefaultProfile() const; + std::unique_ptr createSourceProfile() const; - std::unique_ptr createSourceProfile() const; + private: + QString m_path; }; diff --git a/DefaultParamsProvider.cpp b/DefaultParamsProvider.cpp index e7b7486e3..4d93c73f9 100644 --- a/DefaultParamsProvider.cpp +++ b/DefaultParamsProvider.cpp @@ -1,59 +1,59 @@ +#include "DefaultParamsProvider.h" #include #include -#include "DefaultParamsProvider.h" -#include "DefaultParamsProfileManager.h" #include "DefaultParams.h" +#include "DefaultParamsProfileManager.h" -std::unique_ptr DefaultParamsProvider::instance = nullptr; +std::unique_ptr DefaultParamsProvider::m_instance = nullptr; DefaultParamsProvider::DefaultParamsProvider() { - QSettings settings; - DefaultParamsProfileManager defaultParamsProfileManager; - - const QString profile = settings.value("settings/current_profile", "Default").toString(); - if (profile == "Default") { - this->params = defaultParamsProfileManager.createDefaultProfile(); - this->profileName = profile; - } else if (profile == "Source") { - this->params = defaultParamsProfileManager.createSourceProfile(); - this->profileName = profile; + QSettings settings; + DefaultParamsProfileManager defaultParamsProfileManager; + + const QString profile = settings.value("settings/current_profile", "Default").toString(); + if (profile == "Default") { + m_params = defaultParamsProfileManager.createDefaultProfile(); + m_profileName = profile; + } else if (profile == "Source") { + m_params = defaultParamsProfileManager.createSourceProfile(); + m_profileName = profile; + } else { + std::unique_ptr params = defaultParamsProfileManager.readProfile(profile); + if (params != nullptr) { + m_params = std::move(params); + m_profileName = profile; } else { - std::unique_ptr params = defaultParamsProfileManager.readProfile(profile); - if (params != nullptr) { - this->params = std::move(params); - this->profileName = profile; - } else { - this->params = defaultParamsProfileManager.createDefaultProfile(); - this->profileName = "Default"; - settings.setValue("settings/current_profile", "Default"); - } + m_params = defaultParamsProfileManager.createDefaultProfile(); + m_profileName = "Default"; + settings.setValue("settings/current_profile", "Default"); } + } } DefaultParamsProvider* DefaultParamsProvider::getInstance() { - if (instance == nullptr) { - instance.reset(new DefaultParamsProvider()); - } + if (m_instance == nullptr) { + m_instance.reset(new DefaultParamsProvider()); + } - return instance.get(); + return m_instance.get(); } const QString& DefaultParamsProvider::getProfileName() const { - return profileName; + return m_profileName; } DefaultParams DefaultParamsProvider::getParams() const { - assert(params != nullptr); + assert(m_params != nullptr); - return *params; + return *m_params; } void DefaultParamsProvider::setParams(std::unique_ptr params, const QString& name) { - if (params == nullptr) { - return; - } + if (params == nullptr) { + return; + } - this->params = std::move(params); - this->profileName = name; + m_params = std::move(params); + m_profileName = name; } diff --git a/DefaultParamsProvider.h b/DefaultParamsProvider.h index af43f073a..e839b7303 100644 --- a/DefaultParamsProvider.h +++ b/DefaultParamsProvider.h @@ -2,29 +2,31 @@ #ifndef SCANTAILOR_DEFAULTPARAMSPROVIDER_H #define SCANTAILOR_DEFAULTPARAMSPROVIDER_H - +#include #include #include class DefaultParams; class DefaultParamsProvider { -private: - static std::unique_ptr instance; + DECLARE_NON_COPYABLE(DefaultParamsProvider) + private: + DefaultParamsProvider(); - QString profileName; - std::unique_ptr params; + public: + static DefaultParamsProvider* getInstance(); - DefaultParamsProvider(); + const QString& getProfileName() const; -public: - static DefaultParamsProvider* getInstance(); + DefaultParams getParams() const; - const QString& getProfileName() const; + void setParams(std::unique_ptr params, const QString& name); - DefaultParams getParams() const; + private: + static std::unique_ptr m_instance; - void setParams(std::unique_ptr params, const QString& name); + QString m_profileName; + std::unique_ptr m_params; }; diff --git a/Despeckle.cpp b/Despeckle.cpp index e5506a6b6..38992cbc6 100644 --- a/Despeckle.cpp +++ b/Despeckle.cpp @@ -17,15 +17,16 @@ */ #include "Despeckle.h" -#include "TaskStatus.h" +#include +#include +#include +#include #include "DebugImages.h" #include "Dpi.h" #include "FastQueue.h" +#include "TaskStatus.h" #include "imageproc/BinaryImage.h" #include "imageproc/ConnectivityMap.h" -#include -#include -#include /** * \file @@ -58,97 +59,99 @@ const int VERTICAL_SCALE = 2; const int VERTICAL_SCALE_SQ = VERTICAL_SCALE * VERTICAL_SCALE; struct Settings { - /** - * When multiplied by the number of pixels in a connected component, - * gives the minimum size (in terms of the number of pixels) of a connected - * component we may attach it to. - */ - double minRelativeParentWeight; - - /** - * When multiplied by the number of pixels in a connected component, - * gives the maximum squared distance to another connected component - * we may attach it to. - */ - uint32_t pixelsToSqDist; - - /** - * Defines the minimum width or height in pixels that will guarantee - * the object won't be removed. - */ - int bigObjectThreshold; - - static Settings get(Despeckle::Level level, const Dpi& dpi); + /** + * When multiplied by the number of pixels in a connected component, + * gives the minimum size (in terms of the number of pixels) of a connected + * component we may attach it to. + */ + double minRelativeParentWeight; + + /** + * When multiplied by the number of pixels in a connected component, + * gives the maximum squared distance to another connected component + * we may attach it to. + */ + uint32_t pixelsToSqDist; + + /** + * Defines the minimum width or height in pixels that will guarantee + * the object won't be removed. + */ + int bigObjectThreshold; + + static Settings get(Despeckle::Level level, const Dpi& dpi); + + static Settings get(double level, const Dpi& dpi); }; Settings Settings::get(const Despeckle::Level level, const Dpi& dpi) { - Settings settings{}; - - const int min_dpi = std::min(dpi.horizontal(), dpi.vertical()); - const double dpi_factor = min_dpi / 300.0; - // To silence compiler's warnings. - settings.minRelativeParentWeight = 0; - settings.pixelsToSqDist = 0; - settings.bigObjectThreshold = 0; - - switch (level) { - case Despeckle::CAUTIOUS: - settings.minRelativeParentWeight = 0.125 * dpi_factor; - settings.pixelsToSqDist = static_cast(10.0 * 10.0); - settings.bigObjectThreshold = qRound(7 * dpi_factor); - break; - case Despeckle::NORMAL: - settings.minRelativeParentWeight = 0.175 * dpi_factor; - settings.pixelsToSqDist = static_cast(6.5 * 6.5); - settings.bigObjectThreshold = qRound(12 * dpi_factor); - break; - case Despeckle::AGGRESSIVE: - settings.minRelativeParentWeight = 0.225 * dpi_factor; - settings.pixelsToSqDist = static_cast(3.5 * 3.5); - settings.bigObjectThreshold = qRound(17 * dpi_factor); - break; - } + Settings settings{}; + + const int min_dpi = std::min(dpi.horizontal(), dpi.vertical()); + const double dpi_factor = min_dpi / 300.0; + // To silence compiler's warnings. + settings.minRelativeParentWeight = 0; + settings.pixelsToSqDist = 0; + settings.bigObjectThreshold = 0; + + switch (level) { + case Despeckle::CAUTIOUS: + settings.minRelativeParentWeight = 0.125 * dpi_factor; + settings.pixelsToSqDist = static_cast(std::pow(10.0, 2)); + settings.bigObjectThreshold = qRound(7 * dpi_factor); + break; + case Despeckle::NORMAL: + settings.minRelativeParentWeight = 0.175 * dpi_factor; + settings.pixelsToSqDist = static_cast(std::pow(6.5, 2)); + settings.bigObjectThreshold = qRound(12 * dpi_factor); + break; + case Despeckle::AGGRESSIVE: + settings.minRelativeParentWeight = 0.225 * dpi_factor; + settings.pixelsToSqDist = static_cast(std::pow(3.5, 2)); + settings.bigObjectThreshold = qRound(17 * dpi_factor); + break; + } + + return settings; +} + +Settings Settings::get(const double level, const Dpi& dpi) { + Settings settings{}; - return settings; + const int min_dpi = std::min(dpi.horizontal(), dpi.vertical()); + const double dpi_factor = min_dpi / 300.0; + + settings.minRelativeParentWeight = (0.05 * level + 0.075) * dpi_factor; + settings.pixelsToSqDist = static_cast(std::pow(0.25 * std::pow(level, 2) - 4.25 * level + 14, 2)); + settings.bigObjectThreshold = qRound((5 * level + 2) * dpi_factor); + + return settings; } struct Component { - static const uint32_t ANCHORED_TO_BIG = uint32_t(1) << 31; - static const uint32_t ANCHORED_TO_SMALL = uint32_t(1) << 30; - static const uint32_t TAG_MASK = ANCHORED_TO_BIG | ANCHORED_TO_SMALL; + static const uint32_t ANCHORED_TO_BIG = uint32_t(1) << 31; + static const uint32_t ANCHORED_TO_SMALL = uint32_t(1) << 30; + static const uint32_t TAG_MASK = ANCHORED_TO_BIG | ANCHORED_TO_SMALL; - /** - * Lower 30 bits: the number of pixels in the connected component. - * Higher 2 bits: tags. - */ - uint32_t num_pixels; + /** + * Lower 30 bits: the number of pixels in the connected component. + * Higher 2 bits: tags. + */ + uint32_t num_pixels; - Component() : num_pixels(0) { - } + Component() : num_pixels(0) {} - const uint32_t anchoredToBig() const { - return num_pixels & ANCHORED_TO_BIG; - } + const uint32_t anchoredToBig() const { return num_pixels & ANCHORED_TO_BIG; } - void setAnchoredToBig() { - num_pixels |= ANCHORED_TO_BIG; - } + void setAnchoredToBig() { num_pixels |= ANCHORED_TO_BIG; } - const uint32_t anchoredToSmall() const { - return num_pixels & ANCHORED_TO_SMALL; - } + const uint32_t anchoredToSmall() const { return num_pixels & ANCHORED_TO_SMALL; } - void setAnchoredToSmall() { - num_pixels |= ANCHORED_TO_SMALL; - } + void setAnchoredToSmall() { num_pixels |= ANCHORED_TO_SMALL; } - const bool anchoredToSmallButNotBig() const { - return (num_pixels & TAG_MASK) == ANCHORED_TO_SMALL; - } + const bool anchoredToSmallButNotBig() const { return (num_pixels & TAG_MASK) == ANCHORED_TO_SMALL; } - void clearTags() { - num_pixels &= ~TAG_MASK; - } + void clearTags() { num_pixels &= ~TAG_MASK; } }; const uint32_t Component::ANCHORED_TO_BIG; @@ -156,141 +159,132 @@ const uint32_t Component::ANCHORED_TO_SMALL; const uint32_t Component::TAG_MASK; struct BoundingBox { - int top; - int left; - int bottom; - int right; - - BoundingBox() { - top = left = std::numeric_limits::max(); - bottom = right = std::numeric_limits::min(); - } - - int width() const { - return right - left + 1; - } - - int height() const { - return bottom - top + 1; - } - - void extend(int x, int y) { - top = std::min(top, y); - left = std::min(left, x); - bottom = std::max(bottom, y); - right = std::max(right, x); - } + int top; + int left; + int bottom; + int right; + + BoundingBox() { + top = left = std::numeric_limits::max(); + bottom = right = std::numeric_limits::min(); + } + + int width() const { return right - left + 1; } + + int height() const { return bottom - top + 1; } + + void extend(int x, int y) { + top = std::min(top, y); + left = std::min(left, x); + bottom = std::max(bottom, y); + right = std::max(right, x); + } }; struct Vector { - int16_t x; - int16_t y; + int16_t x; + int16_t y; }; union Distance { - Vector vec; - uint32_t raw; + Vector vec; + uint32_t raw; - static Distance zero() { - Distance dist{}; - dist.raw = 0; + static Distance zero() { + Distance dist{}; + dist.raw = 0; - return dist; - } + return dist; + } - static Distance special() { - Distance dist{}; - dist.vec.x = dist.vec.y = std::numeric_limits::max(); + static Distance special() { + Distance dist{}; + dist.vec.x = dist.vec.y = std::numeric_limits::max(); - return dist; - } + return dist; + } - bool operator==(const Distance& other) const { - return raw == other.raw; - } + bool operator==(const Distance& other) const { return raw == other.raw; } - bool operator!=(const Distance& other) const { - return raw != other.raw; - } + bool operator!=(const Distance& other) const { return raw != other.raw; } - void reset(int x) { - vec.x = static_cast(std::numeric_limits::max() - x); - vec.y = 0; - } + void reset(int x) { + vec.x = static_cast(std::numeric_limits::max() - x); + vec.y = 0; + } - uint32_t sqdist() const { - const int x = vec.x; - const int y = vec.y; + uint32_t sqdist() const { + const int x = vec.x; + const int y = vec.y; - return static_cast(x * x + VERTICAL_SCALE_SQ * y * y); - } + return static_cast(x * x + VERTICAL_SCALE_SQ * y * y); + } }; /** * \brief A bidirectional association between two connected components. */ struct Connection { - uint32_t lesser_label; - uint32_t greater_label; - - Connection(uint32_t lbl1, uint32_t lbl2) { - if (lbl1 < lbl2) { - lesser_label = lbl1; - greater_label = lbl2; - } else { - lesser_label = lbl2; - greater_label = lbl1; - } - } + uint32_t lesser_label; + uint32_t greater_label; - bool operator<(const Connection& rhs) const { - if (lesser_label < rhs.lesser_label) { - return true; - } else if (lesser_label > rhs.lesser_label) { - return false; - } else { - return greater_label < rhs.greater_label; - } + Connection(uint32_t lbl1, uint32_t lbl2) { + if (lbl1 < lbl2) { + lesser_label = lbl1; + greater_label = lbl2; + } else { + lesser_label = lbl2; + greater_label = lbl1; } + } - bool operator==(const Connection& other) const { - return (lesser_label == other.lesser_label) && (greater_label == other.greater_label); + bool operator<(const Connection& rhs) const { + if (lesser_label < rhs.lesser_label) { + return true; + } else if (lesser_label > rhs.lesser_label) { + return false; + } else { + return greater_label < rhs.greater_label; } + } - struct hash { - std::size_t operator()(const Connection& connection) const noexcept { - return std::hash()(connection.lesser_label) ^ std::hash()(connection.greater_label << 1); - } - }; + bool operator==(const Connection& other) const { + return (lesser_label == other.lesser_label) && (greater_label == other.greater_label); + } + + struct hash { + std::size_t operator()(const Connection& connection) const noexcept { + return std::hash()(connection.lesser_label) ^ std::hash()(connection.greater_label << 1); + } + }; }; /** * \brief A directional assiciation between two connected components. */ struct TargetSourceConn { - uint32_t target; + uint32_t target; - /**< The label of the target connected component. */ - uint32_t source; + /**< The label of the target connected component. */ + uint32_t source; - /**< The label of the source connected component. */ + /**< The label of the source connected component. */ - TargetSourceConn(uint32_t tgt, uint32_t src) : target(tgt), source(src) { - } + TargetSourceConn(uint32_t tgt, uint32_t src) : target(tgt), source(src) {} - /** - * The ordering is by target then source. It's designed to be able - * to quickly locate all associations involving a specific target. - */ - bool operator<(const TargetSourceConn& rhs) const { - if (target < rhs.target) { - return true; - } else if (target > rhs.target) { - return false; - } else { - return source < rhs.source; - } + /** + * The ordering is by target then source. It's designed to be able + * to quickly locate all associations involving a specific target. + */ + bool operator<(const TargetSourceConn& rhs) const { + if (target < rhs.target) { + return true; + } else if (target > rhs.target) { + return false; + } else { + return source < rhs.source; } + } }; /** @@ -301,15 +295,15 @@ void updateDistance(std::unordered_map& uint32_t label1, uint32_t label2, uint32_t sqdist) { - typedef std::unordered_map Connections; - - const Connection conn(label1, label2); - auto it(conns.find(conn)); - if (it == conns.end()) { - conns.insert(Connections::value_type(conn, sqdist)); - } else if (sqdist < it->second) { - it->second = sqdist; - } + typedef std::unordered_map Connections; + + const Connection conn(label1, label2); + auto it(conns.find(conn)); + if (it == conns.end()) { + conns.insert(Connections::value_type(conn, sqdist)); + } else if (sqdist < it->second) { + it->second = sqdist; + } } /** @@ -317,21 +311,21 @@ void updateDistance(std::unordered_map& * or none of the above. */ void tagSourceComponent(Component& source, const Component& target, uint32_t sqdist, const Settings& settings) { - if (source.anchoredToBig()) { - // No point in setting ANCHORED_TO_SMALL. - return; - } - - if (sqdist > source.num_pixels * settings.pixelsToSqDist) { - // Too far. - return; - } - - if (target.num_pixels >= settings.minRelativeParentWeight * source.num_pixels) { - source.setAnchoredToBig(); - } else { - source.setAnchoredToSmall(); - } + if (source.anchoredToBig()) { + // No point in setting ANCHORED_TO_SMALL. + return; + } + + if (sqdist > source.num_pixels * settings.pixelsToSqDist) { + // Too far. + return; + } + + if (target.num_pixels >= settings.minRelativeParentWeight * source.num_pixels) { + source.setAnchoredToBig(); + } else { + source.setAnchoredToSmall(); + } } /** @@ -340,297 +334,297 @@ void tagSourceComponent(Component& source, const Component& target, uint32_t sqd * being attached, provided that the one it's attached to is also preserved. */ bool canBeAttachedTo(const Component& comp, const Component& target, uint32_t sqdist, const Settings& settings) { - if (sqdist <= comp.num_pixels * settings.pixelsToSqDist) { - if (target.num_pixels >= comp.num_pixels * settings.minRelativeParentWeight) { - return true; - } + if (sqdist <= comp.num_pixels * settings.pixelsToSqDist) { + if (target.num_pixels >= comp.num_pixels * settings.minRelativeParentWeight) { + return true; } + } - return false; + return false; } void voronoi(ConnectivityMap& cmap, std::vector& dist) { - const int width = cmap.size().width() + 2; - const int height = cmap.size().height() + 2; - - assert(dist.empty()); - dist.resize(width * height, Distance::zero()); - - std::vector sqdists(width * 2, 0); - uint32_t* prev_sqdist_line = &sqdists[0]; - uint32_t* this_sqdist_line = &sqdists[width]; - - Distance* dist_line = &dist[0]; - uint32_t* cmap_line = cmap.paddedData(); - + const int width = cmap.size().width() + 2; + const int height = cmap.size().height() + 2; + + assert(dist.empty()); + dist.resize(width * height, Distance::zero()); + + std::vector sqdists(width * 2, 0); + uint32_t* prev_sqdist_line = &sqdists[0]; + uint32_t* this_sqdist_line = &sqdists[width]; + + Distance* dist_line = &dist[0]; + uint32_t* cmap_line = cmap.paddedData(); + + dist_line[0].reset(0); + prev_sqdist_line[0] = dist_line[0].sqdist(); + for (int x = 1; x < width; ++x) { + dist_line[x].vec.x = static_cast(dist_line[x - 1].vec.x - 1); + prev_sqdist_line[x] = prev_sqdist_line[x - 1] - (int(dist_line[x - 1].vec.x) << 1) + 1; + } + + // Top to bottom scan. + for (int y = 1; y < height; ++y) { + dist_line += width; + cmap_line += width; dist_line[0].reset(0); - prev_sqdist_line[0] = dist_line[0].sqdist(); - for (int x = 1; x < width; ++x) { - dist_line[x].vec.x = static_cast(dist_line[x - 1].vec.x - 1); - prev_sqdist_line[x] = prev_sqdist_line[x - 1] - (int(dist_line[x - 1].vec.x) << 1) + 1; - } + dist_line[width - 1].reset(width - 1); + this_sqdist_line[0] = dist_line[0].sqdist(); + this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); + // Left to right scan. + for (int x = 1; x < width - 1; ++x) { + if (cmap_line[x]) { + this_sqdist_line[x] = 0; + assert(dist_line[x] == Distance::zero()); + continue; + } + + // Propagate from left. + Distance left_dist = dist_line[x - 1]; + uint32_t sqdist_left = this_sqdist_line[x - 1]; + sqdist_left += 1 - (int(left_dist.vec.x) << 1); + // Propagate from top. + Distance top_dist = dist_line[x - width]; + uint32_t sqdist_top = prev_sqdist_line[x]; + sqdist_top += VERTICAL_SCALE_SQ - 2 * VERTICAL_SCALE_SQ * int(top_dist.vec.y); + + if (sqdist_left < sqdist_top) { + this_sqdist_line[x] = sqdist_left; + --left_dist.vec.x; + dist_line[x] = left_dist; + cmap_line[x] = cmap_line[x - 1]; + } else { + this_sqdist_line[x] = sqdist_top; + --top_dist.vec.y; + dist_line[x] = top_dist; + cmap_line[x] = cmap_line[x - width]; + } + } + + // Right to left scan. + for (int x = width - 2; x >= 1; --x) { + // Propagate from right. + Distance right_dist = dist_line[x + 1]; + uint32_t sqdist_right = this_sqdist_line[x + 1]; + sqdist_right += 1 + (int(right_dist.vec.x) << 1); + + if (sqdist_right < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_right; + ++right_dist.vec.x; + dist_line[x] = right_dist; + cmap_line[x] = cmap_line[x + 1]; + } + } + + std::swap(this_sqdist_line, prev_sqdist_line); + } + + // Bottom to top scan. + for (int y = height - 2; y >= 1; --y) { + dist_line -= width; + cmap_line -= width; + dist_line[0].reset(0); + dist_line[width - 1].reset(width - 1); + this_sqdist_line[0] = dist_line[0].sqdist(); + this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); + // Right to left scan. + for (int x = width - 2; x >= 1; --x) { + // Propagate from right. + Distance right_dist = dist_line[x + 1]; + uint32_t sqdist_right = this_sqdist_line[x + 1]; + sqdist_right += 1 + (int(right_dist.vec.x) << 1); + // Propagate from bottom. + Distance bottom_dist = dist_line[x + width]; + uint32_t sqdist_bottom = prev_sqdist_line[x]; + sqdist_bottom += VERTICAL_SCALE_SQ + 2 * VERTICAL_SCALE_SQ * int(bottom_dist.vec.y); + + this_sqdist_line[x] = dist_line[x].sqdist(); + + if (sqdist_right < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_right; + ++right_dist.vec.x; + dist_line[x] = right_dist; + assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); + cmap_line[x] = cmap_line[x + 1]; + } + if (sqdist_bottom < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_bottom; + ++bottom_dist.vec.y; + dist_line[x] = bottom_dist; + assert(cmap_line[x] == 0 || cmap_line[x + width] != 0); + cmap_line[x] = cmap_line[x + width]; + } + } + // Left to right scan. + for (int x = 1; x < width - 1; ++x) { + // Propagate from left. + Distance left_dist = dist_line[x - 1]; + uint32_t sqdist_left = this_sqdist_line[x - 1]; + sqdist_left += 1 - (int(left_dist.vec.x) << 1); + + if (sqdist_left < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_left; + --left_dist.vec.x; + dist_line[x] = left_dist; + assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); + cmap_line[x] = cmap_line[x - 1]; + } + } + + std::swap(this_sqdist_line, prev_sqdist_line); + } +} // voronoi - // Top to bottom scan. - for (int y = 1; y < height; ++y) { - dist_line += width; - cmap_line += width; - dist_line[0].reset(0); - dist_line[width - 1].reset(width - 1); - this_sqdist_line[0] = dist_line[0].sqdist(); - this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); - // Left to right scan. - for (int x = 1; x < width - 1; ++x) { - if (cmap_line[x]) { - this_sqdist_line[x] = 0; - assert(dist_line[x] == Distance::zero()); - continue; - } - - // Propagate from left. - Distance left_dist = dist_line[x - 1]; - uint32_t sqdist_left = this_sqdist_line[x - 1]; - sqdist_left += 1 - (int(left_dist.vec.x) << 1); - // Propagate from top. - Distance top_dist = dist_line[x - width]; - uint32_t sqdist_top = prev_sqdist_line[x]; - sqdist_top += VERTICAL_SCALE_SQ - 2 * VERTICAL_SCALE_SQ * int(top_dist.vec.y); - - if (sqdist_left < sqdist_top) { - this_sqdist_line[x] = sqdist_left; - --left_dist.vec.x; - dist_line[x] = left_dist; - cmap_line[x] = cmap_line[x - 1]; - } else { - this_sqdist_line[x] = sqdist_top; - --top_dist.vec.y; - dist_line[x] = top_dist; - cmap_line[x] = cmap_line[x - width]; - } +void voronoiSpecial(ConnectivityMap& cmap, std::vector& dist, const Distance special_distance) { + const int width = cmap.size().width() + 2; + const int height = cmap.size().height() + 2; + + std::vector sqdists(width * 2, 0); + uint32_t* prev_sqdist_line = &sqdists[0]; + uint32_t* this_sqdist_line = &sqdists[width]; + + Distance* dist_line = &dist[0]; + uint32_t* cmap_line = cmap.paddedData(); + + dist_line[0].reset(0); + prev_sqdist_line[0] = dist_line[0].sqdist(); + for (int x = 1; x < width; ++x) { + dist_line[x].vec.x = static_cast(dist_line[x - 1].vec.x - 1); + prev_sqdist_line[x] = prev_sqdist_line[x - 1] - (int(dist_line[x - 1].vec.x) << 1) + 1; + } + + // Top to bottom scan. + for (int y = 1; y < height - 1; ++y) { + dist_line += width; + cmap_line += width; + dist_line[0].reset(0); + dist_line[width - 1].reset(width - 1); + this_sqdist_line[0] = dist_line[0].sqdist(); + this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); + // Left to right scan. + for (int x = 1; x < width - 1; ++x) { + if (dist_line[x] == special_distance) { + continue; + } + + this_sqdist_line[x] = dist_line[x].sqdist(); + // Propagate from left. + Distance left_dist = dist_line[x - 1]; + if (left_dist != special_distance) { + uint32_t sqdist_left = this_sqdist_line[x - 1]; + sqdist_left += 1 - (int(left_dist.vec.x) << 1); + if (sqdist_left < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_left; + --left_dist.vec.x; + dist_line[x] = left_dist; + assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); + cmap_line[x] = cmap_line[x - 1]; } - - // Right to left scan. - for (int x = width - 2; x >= 1; --x) { - // Propagate from right. - Distance right_dist = dist_line[x + 1]; - uint32_t sqdist_right = this_sqdist_line[x + 1]; - sqdist_right += 1 + (int(right_dist.vec.x) << 1); - - if (sqdist_right < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_right; - ++right_dist.vec.x; - dist_line[x] = right_dist; - cmap_line[x] = cmap_line[x + 1]; - } + } + // Propagate from top. + Distance top_dist = dist_line[x - width]; + if (top_dist != special_distance) { + uint32_t sqdist_top = prev_sqdist_line[x]; + sqdist_top += VERTICAL_SCALE_SQ - 2 * VERTICAL_SCALE_SQ * int(top_dist.vec.y); + if (sqdist_top < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_top; + --top_dist.vec.y; + dist_line[x] = top_dist; + assert(cmap_line[x] == 0 || cmap_line[x - width] != 0); + cmap_line[x] = cmap_line[x - width]; } - - std::swap(this_sqdist_line, prev_sqdist_line); - } - - // Bottom to top scan. - for (int y = height - 2; y >= 1; --y) { - dist_line -= width; - cmap_line -= width; - dist_line[0].reset(0); - dist_line[width - 1].reset(width - 1); - this_sqdist_line[0] = dist_line[0].sqdist(); - this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); - // Right to left scan. - for (int x = width - 2; x >= 1; --x) { - // Propagate from right. - Distance right_dist = dist_line[x + 1]; - uint32_t sqdist_right = this_sqdist_line[x + 1]; - sqdist_right += 1 + (int(right_dist.vec.x) << 1); - // Propagate from bottom. - Distance bottom_dist = dist_line[x + width]; - uint32_t sqdist_bottom = prev_sqdist_line[x]; - sqdist_bottom += VERTICAL_SCALE_SQ + 2 * VERTICAL_SCALE_SQ * int(bottom_dist.vec.y); - - this_sqdist_line[x] = dist_line[x].sqdist(); - - if (sqdist_right < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_right; - ++right_dist.vec.x; - dist_line[x] = right_dist; - assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); - cmap_line[x] = cmap_line[x + 1]; - } - if (sqdist_bottom < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_bottom; - ++bottom_dist.vec.y; - dist_line[x] = bottom_dist; - assert(cmap_line[x] == 0 || cmap_line[x + width] != 0); - cmap_line[x] = cmap_line[x + width]; - } + } + } + + // Right to left scan. + for (int x = width - 2; x >= 1; --x) { + if (dist_line[x] == special_distance) { + continue; + } + // Propagate from right. + Distance right_dist = dist_line[x + 1]; + if (right_dist != special_distance) { + uint32_t sqdist_right = this_sqdist_line[x + 1]; + sqdist_right += 1 + (int(right_dist.vec.x) << 1); + if (sqdist_right < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_right; + ++right_dist.vec.x; + dist_line[x] = right_dist; + assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); + cmap_line[x] = cmap_line[x + 1]; } - // Left to right scan. - for (int x = 1; x < width - 1; ++x) { - // Propagate from left. - Distance left_dist = dist_line[x - 1]; - uint32_t sqdist_left = this_sqdist_line[x - 1]; - sqdist_left += 1 - (int(left_dist.vec.x) << 1); - - if (sqdist_left < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_left; - --left_dist.vec.x; - dist_line[x] = left_dist; - assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); - cmap_line[x] = cmap_line[x - 1]; - } - } - - std::swap(this_sqdist_line, prev_sqdist_line); + } } -} // voronoi - -void voronoiSpecial(ConnectivityMap& cmap, std::vector& dist, const Distance special_distance) { - const int width = cmap.size().width() + 2; - const int height = cmap.size().height() + 2; - - std::vector sqdists(width * 2, 0); - uint32_t* prev_sqdist_line = &sqdists[0]; - uint32_t* this_sqdist_line = &sqdists[width]; - Distance* dist_line = &dist[0]; - uint32_t* cmap_line = cmap.paddedData(); + std::swap(this_sqdist_line, prev_sqdist_line); + } + // Bottom to top scan. + for (int y = height - 2; y >= 1; --y) { + dist_line -= width; + cmap_line -= width; dist_line[0].reset(0); - prev_sqdist_line[0] = dist_line[0].sqdist(); - for (int x = 1; x < width; ++x) { - dist_line[x].vec.x = static_cast(dist_line[x - 1].vec.x - 1); - prev_sqdist_line[x] = prev_sqdist_line[x - 1] - (int(dist_line[x - 1].vec.x) << 1) + 1; - } - - // Top to bottom scan. - for (int y = 1; y < height - 1; ++y) { - dist_line += width; - cmap_line += width; - dist_line[0].reset(0); - dist_line[width - 1].reset(width - 1); - this_sqdist_line[0] = dist_line[0].sqdist(); - this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); - // Left to right scan. - for (int x = 1; x < width - 1; ++x) { - if (dist_line[x] == special_distance) { - continue; - } - - this_sqdist_line[x] = dist_line[x].sqdist(); - // Propagate from left. - Distance left_dist = dist_line[x - 1]; - if (left_dist != special_distance) { - uint32_t sqdist_left = this_sqdist_line[x - 1]; - sqdist_left += 1 - (int(left_dist.vec.x) << 1); - if (sqdist_left < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_left; - --left_dist.vec.x; - dist_line[x] = left_dist; - assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); - cmap_line[x] = cmap_line[x - 1]; - } - } - // Propagate from top. - Distance top_dist = dist_line[x - width]; - if (top_dist != special_distance) { - uint32_t sqdist_top = prev_sqdist_line[x]; - sqdist_top += VERTICAL_SCALE_SQ - 2 * VERTICAL_SCALE_SQ * int(top_dist.vec.y); - if (sqdist_top < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_top; - --top_dist.vec.y; - dist_line[x] = top_dist; - assert(cmap_line[x] == 0 || cmap_line[x - width] != 0); - cmap_line[x] = cmap_line[x - width]; - } - } + dist_line[width - 1].reset(width - 1); + this_sqdist_line[0] = dist_line[0].sqdist(); + this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); + // Right to left scan. + for (int x = width - 2; x >= 1; --x) { + if (dist_line[x] == special_distance) { + continue; + } + + this_sqdist_line[x] = dist_line[x].sqdist(); + // Propagate from right. + Distance right_dist = dist_line[x + 1]; + if (right_dist != special_distance) { + uint32_t sqdist_right = this_sqdist_line[x + 1]; + sqdist_right += 1 + (int(right_dist.vec.x) << 1); + if (sqdist_right < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_right; + ++right_dist.vec.x; + dist_line[x] = right_dist; + assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); + cmap_line[x] = cmap_line[x + 1]; } - - // Right to left scan. - for (int x = width - 2; x >= 1; --x) { - if (dist_line[x] == special_distance) { - continue; - } - // Propagate from right. - Distance right_dist = dist_line[x + 1]; - if (right_dist != special_distance) { - uint32_t sqdist_right = this_sqdist_line[x + 1]; - sqdist_right += 1 + (int(right_dist.vec.x) << 1); - if (sqdist_right < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_right; - ++right_dist.vec.x; - dist_line[x] = right_dist; - assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); - cmap_line[x] = cmap_line[x + 1]; - } - } + } + // Propagate from bottom. + Distance bottom_dist = dist_line[x + width]; + if (bottom_dist != special_distance) { + uint32_t sqdist_bottom = prev_sqdist_line[x]; + sqdist_bottom += VERTICAL_SCALE_SQ + 2 * VERTICAL_SCALE_SQ * int(bottom_dist.vec.y); + if (sqdist_bottom < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_bottom; + ++bottom_dist.vec.y; + dist_line[x] = bottom_dist; + assert(cmap_line[x] == 0 || cmap_line[x + width] != 0); + cmap_line[x] = cmap_line[x + width]; } - - std::swap(this_sqdist_line, prev_sqdist_line); - } - - // Bottom to top scan. - for (int y = height - 2; y >= 1; --y) { - dist_line -= width; - cmap_line -= width; - dist_line[0].reset(0); - dist_line[width - 1].reset(width - 1); - this_sqdist_line[0] = dist_line[0].sqdist(); - this_sqdist_line[width - 1] = dist_line[width - 1].sqdist(); - // Right to left scan. - for (int x = width - 2; x >= 1; --x) { - if (dist_line[x] == special_distance) { - continue; - } - - this_sqdist_line[x] = dist_line[x].sqdist(); - // Propagate from right. - Distance right_dist = dist_line[x + 1]; - if (right_dist != special_distance) { - uint32_t sqdist_right = this_sqdist_line[x + 1]; - sqdist_right += 1 + (int(right_dist.vec.x) << 1); - if (sqdist_right < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_right; - ++right_dist.vec.x; - dist_line[x] = right_dist; - assert(cmap_line[x] == 0 || cmap_line[x + 1] != 0); - cmap_line[x] = cmap_line[x + 1]; - } - } - // Propagate from bottom. - Distance bottom_dist = dist_line[x + width]; - if (bottom_dist != special_distance) { - uint32_t sqdist_bottom = prev_sqdist_line[x]; - sqdist_bottom += VERTICAL_SCALE_SQ + 2 * VERTICAL_SCALE_SQ * int(bottom_dist.vec.y); - if (sqdist_bottom < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_bottom; - ++bottom_dist.vec.y; - dist_line[x] = bottom_dist; - assert(cmap_line[x] == 0 || cmap_line[x + width] != 0); - cmap_line[x] = cmap_line[x + width]; - } - } - } - - // Left to right scan. - for (int x = 1; x < width - 1; ++x) { - if (dist_line[x] == special_distance) { - continue; - } - // Propagate from left. - Distance left_dist = dist_line[x - 1]; - if (left_dist != special_distance) { - uint32_t sqdist_left = this_sqdist_line[x - 1]; - sqdist_left += 1 - (int(left_dist.vec.x) << 1); - if (sqdist_left < this_sqdist_line[x]) { - this_sqdist_line[x] = sqdist_left; - --left_dist.vec.x; - dist_line[x] = left_dist; - assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); - cmap_line[x] = cmap_line[x - 1]; - } - } + } + } + + // Left to right scan. + for (int x = 1; x < width - 1; ++x) { + if (dist_line[x] == special_distance) { + continue; + } + // Propagate from left. + Distance left_dist = dist_line[x - 1]; + if (left_dist != special_distance) { + uint32_t sqdist_left = this_sqdist_line[x - 1]; + sqdist_left += 1 - (int(left_dist.vec.x) << 1); + if (sqdist_left < this_sqdist_line[x]) { + this_sqdist_line[x] = sqdist_left; + --left_dist.vec.x; + dist_line[x] = left_dist; + assert(cmap_line[x] == 0 || cmap_line[x - 1] != 0); + cmap_line[x] = cmap_line[x - 1]; } - - std::swap(this_sqdist_line, prev_sqdist_line); + } } + + std::swap(this_sqdist_line, prev_sqdist_line); + } } // voronoiSpecial /** @@ -640,304 +634,332 @@ void voronoiSpecial(ConnectivityMap& cmap, std::vector& dist, const Di void voronoiDistances(const ConnectivityMap& cmap, const std::vector& distance_matrix, std::unordered_map& conns) { - const int width = cmap.size().width(); - const int height = cmap.size().height(); - - const int offsets[] = {-cmap.stride(), -1, 1, cmap.stride()}; - - const uint32_t* const cmap_data = cmap.data(); - const Distance* const distance_data = &distance_matrix[0] + width + 3; - for (int y = 0, offset = 0; y < height; ++y, offset += 2) { - for (int x = 0; x < width; ++x, ++offset) { - const uint32_t label = cmap_data[offset]; - assert(label != 0); - - const int x1 = x + distance_data[offset].vec.x; - const int y1 = y + distance_data[offset].vec.y; - - for (int i : offsets) { - const int nbh_offset = offset + i; - const uint32_t nbh_label = cmap_data[nbh_offset]; - if ((nbh_label == 0) || (nbh_label == label)) { - // label 0 can be encountered in - // padding lines. - continue; - } - - const int x2 = x + distance_data[nbh_offset].vec.x; - const int y2 = y + distance_data[nbh_offset].vec.y; - const int dx = x1 - x2; - const int dy = y1 - y2; - const uint32_t sqdist = dx * dx + dy * dy; - - updateDistance(conns, label, nbh_label, sqdist); - } + const int width = cmap.size().width(); + const int height = cmap.size().height(); + + const int offsets[] = {-cmap.stride(), -1, 1, cmap.stride()}; + + const uint32_t* const cmap_data = cmap.data(); + const Distance* const distance_data = &distance_matrix[0] + width + 3; + for (int y = 0, offset = 0; y < height; ++y, offset += 2) { + for (int x = 0; x < width; ++x, ++offset) { + const uint32_t label = cmap_data[offset]; + assert(label != 0); + + const int x1 = x + distance_data[offset].vec.x; + const int y1 = y + distance_data[offset].vec.y; + + for (int i : offsets) { + const int nbh_offset = offset + i; + const uint32_t nbh_label = cmap_data[nbh_offset]; + if ((nbh_label == 0) || (nbh_label == label)) { + // label 0 can be encountered in + // padding lines. + continue; } - } -} // voronoiDistances -} // namespace -BinaryImage Despeckle::despeckle(const BinaryImage& src, - const Dpi& dpi, - const Level level, - const TaskStatus& status, - DebugImages* const dbg) { - BinaryImage dst(src); - despeckleInPlace(dst, dpi, level, status, dbg); - - return dst; -} - -void Despeckle::despeckleInPlace(BinaryImage& image, - const Dpi& dpi, - const Level level, - const TaskStatus& status, - DebugImages* const dbg) { - const Settings settings(Settings::get(level, dpi)); + const int x2 = x + distance_data[nbh_offset].vec.x; + const int y2 = y + distance_data[nbh_offset].vec.y; + const int dx = x1 - x2; + const int dy = y1 - y2; + const uint32_t sqdist = dx * dx + dy * dy; - ConnectivityMap cmap(image, CONN8); - if (cmap.maxLabel() == 0) { - // Completely white image? - return; + updateDistance(conns, label, nbh_label, sqdist); + } } + } +} // voronoiDistances +void despeckleImpl(BinaryImage& image, + const Dpi& dpi, + const Settings& settings, + const TaskStatus& status, + DebugImages* const dbg) { + ConnectivityMap cmap(image, CONN8); + if (cmap.maxLabel() == 0) { + // Completely white image? + return; + } + + status.throwIfCancelled(); + + std::vector components(cmap.maxLabel() + 1); + std::vector bounding_boxes(cmap.maxLabel() + 1); + + const int width = image.width(); + const int height = image.height(); + + uint32_t* const cmap_data = cmap.data(); + + // Count the number of pixels and a bounding rect of each component. + uint32_t* cmap_line = cmap_data; + const int cmap_stride = cmap.stride(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = cmap_line[x]; + ++components[label].num_pixels; + bounding_boxes[label].extend(x, y); + } + cmap_line += cmap_stride; + } + + status.throwIfCancelled(); + + // Unify big components into one. + std::vector remapping_table(components.size()); + uint32_t unified_big_component = 0; + uint32_t next_avail_component = 1; + for (uint32_t label = 1; label <= cmap.maxLabel(); ++label) { + if ((bounding_boxes[label].width() < settings.bigObjectThreshold) + && (bounding_boxes[label].height() < settings.bigObjectThreshold)) { + components[next_avail_component] = components[label]; + remapping_table[label] = next_avail_component; + ++next_avail_component; + } else { + if (unified_big_component == 0) { + unified_big_component = next_avail_component; + ++next_avail_component; + components[unified_big_component] = components[label]; + // Set num_pixels to a large value so that canBeAttachedTo() + // always allows attaching to any such component. + components[unified_big_component].num_pixels = width * height; + } + remapping_table[label] = unified_big_component; + } + } + components.resize(next_avail_component); + std::vector().swap(bounding_boxes); // We don't need them any more. + status.throwIfCancelled(); + + const uint32_t max_label = next_avail_component - 1; + // Remapping individual pixels. + cmap_line = cmap_data; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + cmap_line[x] = remapping_table[cmap_line[x]]; + } + cmap_line += cmap_stride; + } + if (dbg) { + dbg->add(cmap.visualized(), "big_components_unified"); + } + + status.throwIfCancelled(); + // Build a Voronoi diagram. + std::vector distance_matrix; + voronoi(cmap, distance_matrix); + if (dbg) { + dbg->add(cmap.visualized(), "voronoi"); + } + + status.throwIfCancelled(); + + Distance* const distance_data = &distance_matrix[0] + width + 3; + + // Now build a bidirectional map of distances between neighboring + // connected components. + + typedef std::unordered_map Connections; // conn -> sqdist + Connections conns; + + voronoiDistances(cmap, distance_matrix, conns); + + status.throwIfCancelled(); + + // Tag connected components with ANCHORED_TO_BIG or ANCHORED_TO_SMALL. + for (const Connections::value_type& pair : conns) { + const Connection conn(pair.first); + const uint32_t sqdist = pair.second; + Component& comp1 = components[conn.lesser_label]; + Component& comp2 = components[conn.greater_label]; + tagSourceComponent(comp1, comp2, sqdist, settings); + tagSourceComponent(comp2, comp1, sqdist, settings); + } + + // Prevent it from growing when we compute the Voronoi diagram + // the second time. + components[unified_big_component].setAnchoredToBig(); + + bool have_anchored_to_small_but_not_big = false; + for (const Component& comp : components) { + have_anchored_to_small_but_not_big = comp.anchoredToSmallButNotBig(); + } + + if (have_anchored_to_small_but_not_big) { status.throwIfCancelled(); - std::vector components(cmap.maxLabel() + 1); - std::vector bounding_boxes(cmap.maxLabel() + 1); - - const int width = image.width(); - const int height = image.height(); + // Give such components a second chance. Maybe they do have + // big neighbors, but Voronoi regions from a smaller ones + // block the path to the bigger ones. - uint32_t* const cmap_data = cmap.data(); - - // Count the number of pixels and a bounding rect of each component. - uint32_t* cmap_line = cmap_data; - const int cmap_stride = cmap.stride(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = cmap_line[x]; - ++components[label].num_pixels; - bounding_boxes[label].extend(x, y); + const Distance zero_distance(Distance::zero()); + const Distance special_distance(Distance::special()); + for (int y = 0, offset = 0; y < height; ++y, offset += 2) { + for (int x = 0; x < width; ++x, ++offset) { + const uint32_t label = cmap_data[offset]; + assert(label != 0); + + const Component& comp = components[label]; + if (!comp.anchoredToSmallButNotBig()) { + if (distance_data[offset] == zero_distance) { + // Prevent this region from growing + // and from being taken over by another + // by another region. + distance_data[offset] = special_distance; + } else { + // Allow this region to be taken over by others. + // Note: x + 1 here is equivalent to x + // in voronoi() or voronoiSpecial(). + distance_data[offset].reset(x + 1); + } } - cmap_line += cmap_stride; + } } status.throwIfCancelled(); - // Unify big components into one. - std::vector remapping_table(components.size()); - uint32_t unified_big_component = 0; - uint32_t next_avail_component = 1; - for (uint32_t label = 1; label <= cmap.maxLabel(); ++label) { - if ((bounding_boxes[label].width() < settings.bigObjectThreshold) - && (bounding_boxes[label].height() < settings.bigObjectThreshold)) { - components[next_avail_component] = components[label]; - remapping_table[label] = next_avail_component; - ++next_avail_component; - } else { - if (unified_big_component == 0) { - unified_big_component = next_avail_component; - ++next_avail_component; - components[unified_big_component] = components[label]; - // Set num_pixels to a large value so that canBeAttachedTo() - // always allows attaching to any such component. - components[unified_big_component].num_pixels = width * height; - } - remapping_table[label] = unified_big_component; - } - } - components.resize(next_avail_component); - std::vector().swap(bounding_boxes); // We don't need them any more. - status.throwIfCancelled(); - - const uint32_t max_label = next_avail_component - 1; - // Remapping individual pixels. - cmap_line = cmap_data; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - cmap_line[x] = remapping_table[cmap_line[x]]; - } - cmap_line += cmap_stride; - } + // Calculate the Voronoi diagram again, but this time + // treat pixels with a special distance in such a way + // to prevent them from spreading but also preventing + // them from being overwritten. + voronoiSpecial(cmap, distance_matrix, special_distance); if (dbg) { - dbg->add(cmap.visualized(), "big_components_unified"); + dbg->add(cmap.visualized(), "voronoi_special"); } status.throwIfCancelled(); - // Build a Voronoi diagram. - std::vector distance_matrix; - voronoi(cmap, distance_matrix); - if (dbg) { - dbg->add(cmap.visualized(), "voronoi"); - } - - status.throwIfCancelled(); - - Distance* const distance_data = &distance_matrix[0] + width + 3; - - // Now build a bidirectional map of distances between neighboring - // connected components. - - typedef std::unordered_map Connections; // conn -> sqdist - Connections conns; + // We've got new connections. Add them to the map. voronoiDistances(cmap, distance_matrix, conns); + } + + status.throwIfCancelled(); + + // Clear the distance matrix. + std::vector().swap(distance_matrix); + + // Remove tags from components. + for (Component& comp : components) { + comp.clearTags(); + } + // Build a directional connection map and only include + // good connections, that is those with a small enough + // distance. + // While at it, clear the bidirectional connection map. + std::vector target_source; + while (!conns.empty()) { + const auto it(conns.begin()); + const uint32_t label1 = it->first.lesser_label; + const uint32_t label2 = it->first.greater_label; + const uint32_t sqdist = it->second; + const Component& comp1 = components[label1]; + const Component& comp2 = components[label2]; + if (canBeAttachedTo(comp1, comp2, sqdist, settings)) { + target_source.emplace_back(label2, label1); + } + if (canBeAttachedTo(comp2, comp1, sqdist, settings)) { + target_source.emplace_back(label1, label2); + } + conns.erase(it); + } + + std::sort(target_source.begin(), target_source.end()); + + status.throwIfCancelled(); + + // Create an index for quick access to a group of connections + // with a specified target. + std::vector target_source_idx; + const size_t num_target_sources = target_source.size(); + uint32_t prev_label = uint32_t(0) - 1; + for (size_t i = 0; i < num_target_sources; ++i) { + const TargetSourceConn& conn = target_source[i]; + assert(conn.target != 0); + for (; prev_label != conn.target; ++prev_label) { + target_source_idx.push_back(i); + } + assert(target_source_idx.size() - 1 == conn.target); + } + for (auto label = static_cast(target_source_idx.size()); label <= max_label; ++label) { + target_source_idx.push_back(num_target_sources); + } + // Labels of components that are to be retained. + FastQueue ok_labels; + ok_labels.push(unified_big_component); + + while (!ok_labels.empty()) { + const uint32_t label = ok_labels.front(); + ok_labels.pop(); + + Component& comp = components[label]; + if (comp.anchoredToBig()) { + continue; + } + + comp.setAnchoredToBig(); + + size_t idx = target_source_idx[label]; + while (idx < num_target_sources && target_source[idx].target == label) { + ok_labels.push(target_source[idx].source); + ++idx; + } + } + + status.throwIfCancelled(); + // Remove unmarked components from the binary image. + const uint32_t msb = uint32_t(1) << 31; + uint32_t* image_line = image.data(); + const int image_stride = image.wordsPerLine(); + cmap_line = cmap_data; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (!components[cmap_line[x]].anchoredToBig()) { + image_line[x >> 5] &= ~(msb >> (x & 31)); + } + } + image_line += image_stride; + cmap_line += cmap_stride; + } +} +} // namespace - status.throwIfCancelled(); - - // Tag connected components with ANCHORED_TO_BIG or ANCHORED_TO_SMALL. - for (const Connections::value_type& pair : conns) { - const Connection conn(pair.first); - const uint32_t sqdist = pair.second; - Component& comp1 = components[conn.lesser_label]; - Component& comp2 = components[conn.greater_label]; - tagSourceComponent(comp1, comp2, sqdist, settings); - tagSourceComponent(comp2, comp1, sqdist, settings); - } - - // Prevent it from growing when we compute the Voronoi diagram - // the second time. - components[unified_big_component].setAnchoredToBig(); - - bool have_anchored_to_small_but_not_big = false; - for (const Component& comp : components) { - have_anchored_to_small_but_not_big = comp.anchoredToSmallButNotBig(); - } - - if (have_anchored_to_small_but_not_big) { - status.throwIfCancelled(); - - // Give such components a second chance. Maybe they do have - // big neighbors, but Voronoi regions from a smaller ones - // block the path to the bigger ones. - - const Distance zero_distance(Distance::zero()); - const Distance special_distance(Distance::special()); - for (int y = 0, offset = 0; y < height; ++y, offset += 2) { - for (int x = 0; x < width; ++x, ++offset) { - const uint32_t label = cmap_data[offset]; - assert(label != 0); - - const Component& comp = components[label]; - if (!comp.anchoredToSmallButNotBig()) { - if (distance_data[offset] == zero_distance) { - // Prevent this region from growing - // and from being taken over by another - // by another region. - distance_data[offset] = special_distance; - } else { - // Allow this region to be taken over by others. - // Note: x + 1 here is equivalent to x - // in voronoi() or voronoiSpecial(). - distance_data[offset].reset(x + 1); - } - } - } - } - - status.throwIfCancelled(); - - // Calculate the Voronoi diagram again, but this time - // treat pixels with a special distance in such a way - // to prevent them from spreading but also preventing - // them from being overwritten. - voronoiSpecial(cmap, distance_matrix, special_distance); - if (dbg) { - dbg->add(cmap.visualized(), "voronoi_special"); - } - - status.throwIfCancelled(); - - // We've got new connections. Add them to the map. - voronoiDistances(cmap, distance_matrix, conns); - } - - status.throwIfCancelled(); - - // Clear the distance matrix. - std::vector().swap(distance_matrix); - - // Remove tags from components. - for (Component& comp : components) { - comp.clearTags(); - } - // Build a directional connection map and only include - // good connections, that is those with a small enough - // distance. - // While at it, clear the bidirectional connection map. - std::vector target_source; - while (!conns.empty()) { - const auto it(conns.begin()); - const uint32_t label1 = it->first.lesser_label; - const uint32_t label2 = it->first.greater_label; - const uint32_t sqdist = it->second; - const Component& comp1 = components[label1]; - const Component& comp2 = components[label2]; - if (canBeAttachedTo(comp1, comp2, sqdist, settings)) { - target_source.emplace_back(label2, label1); - } - if (canBeAttachedTo(comp2, comp1, sqdist, settings)) { - target_source.emplace_back(label1, label2); - } - conns.erase(it); - } - - std::sort(target_source.begin(), target_source.end()); - - status.throwIfCancelled(); - - // Create an index for quick access to a group of connections - // with a specified target. - std::vector target_source_idx; - const size_t num_target_sources = target_source.size(); - uint32_t prev_label = uint32_t(0) - 1; - for (size_t i = 0; i < num_target_sources; ++i) { - const TargetSourceConn& conn = target_source[i]; - assert(conn.target != 0); - for (; prev_label != conn.target; ++prev_label) { - target_source_idx.push_back(i); - } - assert(target_source_idx.size() - 1 == conn.target); - } - for (auto label = static_cast(target_source_idx.size()); label <= max_label; ++label) { - target_source_idx.push_back(num_target_sources); - } - // Labels of components that are to be retained. - FastQueue ok_labels; - ok_labels.push(unified_big_component); +BinaryImage Despeckle::despeckle(const BinaryImage& src, + const Dpi& dpi, + const Level level, + const TaskStatus& status, + DebugImages* const dbg) { + BinaryImage dst(src); + despeckleInPlace(dst, dpi, level, status, dbg); - while (!ok_labels.empty()) { - const uint32_t label = ok_labels.front(); - ok_labels.pop(); + return dst; +} - Component& comp = components[label]; - if (comp.anchoredToBig()) { - continue; - } +void Despeckle::despeckleInPlace(BinaryImage& image, + const Dpi& dpi, + const Level level, + const TaskStatus& status, + DebugImages* const dbg) { + const Settings settings = Settings::get(level, dpi); + despeckleImpl(image, dpi, settings, status, dbg); +} - comp.setAnchoredToBig(); +imageproc::BinaryImage Despeckle::despeckle(const imageproc::BinaryImage& src, + const Dpi& dpi, + const double level, + const TaskStatus& status, + DebugImages* dbg) { + BinaryImage dst(src); + despeckleInPlace(dst, dpi, level, status, dbg); - size_t idx = target_source_idx[label]; - while (idx < num_target_sources && target_source[idx].target == label) { - ok_labels.push(target_source[idx].source); - ++idx; - } - } + return dst; +} - status.throwIfCancelled(); - // Remove unmarked components from the binary image. - const uint32_t msb = uint32_t(1) << 31; - uint32_t* image_line = image.data(); - const int image_stride = image.wordsPerLine(); - cmap_line = cmap_data; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (!components[cmap_line[x]].anchoredToBig()) { - image_line[x >> 5] &= ~(msb >> (x & 31)); - } - } - image_line += image_stride; - cmap_line += cmap_stride; - } -} // Despeckle::despeckleInPlace +void Despeckle::despeckleInPlace(imageproc::BinaryImage& image, + const Dpi& dpi, + const double level, + const TaskStatus& status, + DebugImages* dbg) { + const Settings settings = Settings::get(level, dpi); + despeckleImpl(image, dpi, settings, status, dbg); +} +// Despeckle::despeckleInPlace diff --git a/Despeckle.h b/Despeckle.h index c656bc80b..f23c0492d 100644 --- a/Despeckle.h +++ b/Despeckle.h @@ -28,33 +28,45 @@ class BinaryImage; } class Despeckle { -public: - enum Level { CAUTIOUS, NORMAL, AGGRESSIVE }; - - /** - * \brief Removes small speckles from a binary image. - * - * \param src The image to despeckle. Must not be null. - * \param dpi DPI of \p src. - * \param level Despeckling aggressiveness. - * \param dbg An optional sink for debugging images. - * \param status For asynchronous task cancellation. - * \return The despeckled image. - */ - static imageproc::BinaryImage despeckle(const imageproc::BinaryImage& src, - const Dpi& dpi, - Level level, - const TaskStatus& status, - DebugImages* dbg = nullptr); - - /** - * \brief A slightly faster, in-place version of despeckle(). - */ - static void despeckleInPlace(imageproc::BinaryImage& image, - const Dpi& dpi, - Level level, - const TaskStatus& status, - DebugImages* dbg = nullptr); + public: + enum Level { CAUTIOUS, NORMAL, AGGRESSIVE }; + + /** + * \brief Removes small speckles from a binary image. + * + * \param src The image to despeckle. Must not be null. + * \param dpi DPI of \p src. + * \param level Despeckling aggressiveness. + * \param dbg An optional sink for debugging images. + * \param status For asynchronous task cancellation. + * \return The despeckled image. + */ + static imageproc::BinaryImage despeckle(const imageproc::BinaryImage& src, + const Dpi& dpi, + Level level, + const TaskStatus& status, + DebugImages* dbg = nullptr); + + static imageproc::BinaryImage despeckle(const imageproc::BinaryImage& src, + const Dpi& dpi, + double level, + const TaskStatus& status, + DebugImages* dbg = nullptr); + + /** + * \brief A slightly faster, in-place version of despeckle(). + */ + static void despeckleInPlace(imageproc::BinaryImage& image, + const Dpi& dpi, + Level level, + const TaskStatus& status, + DebugImages* dbg = nullptr); + + static void despeckleInPlace(imageproc::BinaryImage& image, + const Dpi& dpi, + double level, + const TaskStatus& status, + DebugImages* dbg = nullptr); }; diff --git a/DeviationProvider.h b/DeviationProvider.h index c2fe18557..427af982b 100644 --- a/DeviationProvider.h +++ b/DeviationProvider.h @@ -2,143 +2,144 @@ #ifndef SCANTAILOR_DEVIATION_H #define SCANTAILOR_DEVIATION_H -#include -#include +#include #include +#include +#include -template> +template > class DeviationProvider { -private: - std::function computeValueByKey; - std::unordered_map keyValueMap; + DECLARE_NON_COPYABLE(DeviationProvider) + public: + DeviationProvider() = default; - // Cached values. - mutable bool needUpdate = false; - mutable double meanValue = 0.0; - mutable double standardDeviation = 0.0; + explicit DeviationProvider(const std::function& computeValueByKey); -public: - DeviationProvider() = default; + bool isDeviant(const K& key, double coefficient = 1.0, double threshold = 0.0) const; - explicit DeviationProvider(const std::function& computeValueByKey); + double getDeviationValue(const K& key) const; - bool isDeviant(const K& key, double coefficient = 1.0, double threshold = 0.0) const; + void addOrUpdate(const K& key); - double getDeviationValue(const K& key) const; + void addOrUpdate(const K& key, double value); - void addOrUpdate(const K& key); + void remove(const K& key); - void addOrUpdate(const K& key, double value); + void clear(); - void remove(const K& key); + void setComputeValueByKey(const std::function& computeValueByKey); - void clear(); + protected: + void update() const; - void setComputeValueByKey(const std::function& computeValueByKey); + private: + std::function m_computeValueByKey; + std::unordered_map m_keyValueMap; -protected: - void update() const; + // Cached values. + mutable bool m_needUpdate = false; + mutable double m_meanValue = 0.0; + mutable double m_standardDeviation = 0.0; }; -template +template DeviationProvider::DeviationProvider(const std::function& computeValueByKey) - : computeValueByKey(computeValueByKey) { -} + : m_computeValueByKey(computeValueByKey) {} -template +template bool DeviationProvider::isDeviant(const K& key, const double coefficient, const double threshold) const { - if (keyValueMap.find(key) == keyValueMap.end()) { - return false; - } - if (keyValueMap.size() < 3) { - return false; - } + if (m_keyValueMap.find(key) == m_keyValueMap.end()) { + return false; + } + if (m_keyValueMap.size() < 3) { + return false; + } - update(); + update(); - return (std::abs(keyValueMap.at(key) - meanValue) > std::max((coefficient * standardDeviation), threshold)); + return (std::abs(m_keyValueMap.at(key) - m_meanValue) > std::max((coefficient * m_standardDeviation), threshold)); } -template +template double DeviationProvider::getDeviationValue(const K& key) const { - if (keyValueMap.find(key) == keyValueMap.end()) { - return .0; - } - if (keyValueMap.size() < 2) { - return .0; - } + if (m_keyValueMap.find(key) == m_keyValueMap.end()) { + return .0; + } + if (m_keyValueMap.size() < 2) { + return .0; + } - update(); + update(); - return std::abs(keyValueMap.at(key) - meanValue); + return std::abs(m_keyValueMap.at(key) - m_meanValue); } -template +template void DeviationProvider::addOrUpdate(const K& key) { - needUpdate = true; + m_needUpdate = true; - keyValueMap[key] = computeValueByKey(key); + m_keyValueMap[key] = m_computeValueByKey(key); } -template +template void DeviationProvider::addOrUpdate(const K& key, const double value) { - needUpdate = true; + m_needUpdate = true; - keyValueMap[key] = value; + m_keyValueMap[key] = value; } -template +template void DeviationProvider::remove(const K& key) { - needUpdate = true; + m_needUpdate = true; - if (keyValueMap.find(key) == keyValueMap.end()) { - return; - } + if (m_keyValueMap.find(key) == m_keyValueMap.end()) { + return; + } - keyValueMap.erase(key); + m_keyValueMap.erase(key); } -template +template void DeviationProvider::update() const { - if (!needUpdate) { - return; - } - if (keyValueMap.size() < 2) { - return; - } - - { - double sum = .0; - for (const std::pair& keyAndValue : keyValueMap) { - sum += keyAndValue.second; - } - meanValue = sum / keyValueMap.size(); + if (!m_needUpdate) { + return; + } + if (m_keyValueMap.size() < 2) { + return; + } + + { + double sum = .0; + for (const std::pair& keyAndValue : m_keyValueMap) { + sum += keyAndValue.second; } + m_meanValue = sum / m_keyValueMap.size(); + } - { - double differencesSum = .0; - for (const std::pair& keyAndValue : keyValueMap) { - differencesSum += std::pow(keyAndValue.second - meanValue, 2); - } - standardDeviation = std::sqrt(differencesSum / (keyValueMap.size() - 1)); + { + double differencesSum = .0; + for (const std::pair& keyAndValue : m_keyValueMap) { + differencesSum += std::pow(keyAndValue.second - m_meanValue, 2); } + m_standardDeviation = std::sqrt(differencesSum / (m_keyValueMap.size() - 1)); + } - needUpdate = false; + m_needUpdate = false; } -template +template void DeviationProvider::setComputeValueByKey(const std::function& computeValueByKey) { - this->computeValueByKey = std::move(computeValueByKey); + this->m_computeValueByKey = std::move(computeValueByKey); } -template +template void DeviationProvider::clear() { - keyValueMap.clear(); + m_keyValueMap.clear(); - needUpdate = false; - meanValue = 0.0; - standardDeviation = 0.0; + m_needUpdate = false; + m_meanValue = 0.0; + m_standardDeviation = 0.0; } diff --git a/Dpi.cpp b/Dpi.cpp index 49dad306d..28b75e34b 100644 --- a/Dpi.cpp +++ b/Dpi.cpp @@ -22,21 +22,19 @@ using namespace imageproc; -Dpi::Dpi(const QSize size) : m_xDpi(size.width()), m_yDpi(size.height()) { -} +Dpi::Dpi(const QSize size) : m_xDpi(size.width()), m_yDpi(size.height()) {} Dpi::Dpi(const Dpm dpm) - : m_xDpi(qRound(dpm.horizontal() * constants::DPM2DPI)), m_yDpi(qRound(dpm.vertical() * constants::DPM2DPI)) { -} + : m_xDpi(qRound(dpm.horizontal() * constants::DPM2DPI)), m_yDpi(qRound(dpm.vertical() * constants::DPM2DPI)) {} QSize Dpi::toSize() const { - if (isNull()) { - return QSize(); - } else { - return QSize(m_xDpi, m_yDpi); - } + if (isNull()) { + return QSize(); + } else { + return QSize(m_xDpi, m_yDpi); + } } bool Dpi::operator==(const Dpi& other) const { - return m_xDpi == other.m_xDpi && m_yDpi == other.m_yDpi; + return m_xDpi == other.m_xDpi && m_yDpi == other.m_yDpi; } diff --git a/Dpi.h b/Dpi.h index 25c8cb00a..346dbef53 100644 --- a/Dpi.h +++ b/Dpi.h @@ -27,40 +27,30 @@ class Dpm; * \brief Dots per inch (horizontal and vertical). */ class Dpi { -public: - Dpi() : m_xDpi(0), m_yDpi(0) { - } + public: + Dpi() : m_xDpi(0), m_yDpi(0) {} - Dpi(int horizontal, int vertical) : m_xDpi(horizontal), m_yDpi(vertical) { - } + Dpi(int horizontal, int vertical) : m_xDpi(horizontal), m_yDpi(vertical) {} - Dpi(Dpm dpm); + Dpi(Dpm dpm); - explicit Dpi(QSize size); + explicit Dpi(QSize size); - int horizontal() const { - return m_xDpi; - } + int horizontal() const { return m_xDpi; } - int vertical() const { - return m_yDpi; - } + int vertical() const { return m_yDpi; } - QSize toSize() const; + QSize toSize() const; - bool isNull() const { - return m_xDpi <= 1 || m_yDpi <= 1; - } + bool isNull() const { return m_xDpi <= 1 || m_yDpi <= 1; } - bool operator==(const Dpi& other) const; + bool operator==(const Dpi& other) const; - bool operator!=(const Dpi& other) const { - return !(*this == other); - } + bool operator!=(const Dpi& other) const { return !(*this == other); } -private: - int m_xDpi; - int m_yDpi; + private: + int m_xDpi; + int m_yDpi; }; diff --git a/Dpm.cpp b/Dpm.cpp index 50e4f8771..75287858a 100644 --- a/Dpm.cpp +++ b/Dpm.cpp @@ -17,34 +17,31 @@ */ #include "Dpm.h" +#include #include "Dpi.h" #include "imageproc/Constants.h" -#include using namespace imageproc; -Dpm::Dpm(const QSize size) : m_xDpm(size.width()), m_yDpm(size.height()) { -} +Dpm::Dpm(const QSize size) : m_xDpm(size.width()), m_yDpm(size.height()) {} Dpm::Dpm(const Dpi dpi) - : m_xDpm(qRound(dpi.horizontal() * constants::DPI2DPM)), m_yDpm(qRound(dpi.vertical() * constants::DPI2DPM)) { -} + : m_xDpm(qRound(dpi.horizontal() * constants::DPI2DPM)), m_yDpm(qRound(dpi.vertical() * constants::DPI2DPM)) {} -Dpm::Dpm(const QImage& image) : m_xDpm(image.dotsPerMeterX()), m_yDpm(image.dotsPerMeterY()) { -} +Dpm::Dpm(const QImage& image) : m_xDpm(image.dotsPerMeterX()), m_yDpm(image.dotsPerMeterY()) {} bool Dpm::isNull() const { - return Dpi(*this).isNull(); + return Dpi(*this).isNull(); } QSize Dpm::toSize() const { - if (isNull()) { - return QSize(); - } else { - return QSize(m_xDpm, m_yDpm); - } + if (isNull()) { + return QSize(); + } else { + return QSize(m_xDpm, m_yDpm); + } } bool Dpm::operator==(const Dpm& other) const { - return m_xDpm == other.m_xDpm && m_yDpm == other.m_yDpm; + return m_xDpm == other.m_xDpm && m_yDpm == other.m_yDpm; } diff --git a/Dpm.h b/Dpm.h index 291980c56..e51e2fd91 100644 --- a/Dpm.h +++ b/Dpm.h @@ -28,41 +28,33 @@ class QImage; * \brief Dots per meter (horizontal and vertical). */ class Dpm { - // Member-wise copying is OK. -public: - Dpm() : m_xDpm(0), m_yDpm(0) { - } + // Member-wise copying is OK. + public: + Dpm() : m_xDpm(0), m_yDpm(0) {} - Dpm(int horizontal, int vertical) : m_xDpm(horizontal), m_yDpm(vertical) { - } + Dpm(int horizontal, int vertical) : m_xDpm(horizontal), m_yDpm(vertical) {} - Dpm(Dpi dpi); + Dpm(Dpi dpi); - explicit Dpm(QSize size); + explicit Dpm(QSize size); - explicit Dpm(const QImage& image); + explicit Dpm(const QImage& image); - int horizontal() const { - return m_xDpm; - } + int horizontal() const { return m_xDpm; } - int vertical() const { - return m_yDpm; - } + int vertical() const { return m_yDpm; } - QSize toSize() const; + QSize toSize() const; - bool isNull() const; + bool isNull() const; - bool operator==(const Dpm& other) const; + bool operator==(const Dpm& other) const; - bool operator!=(const Dpm& other) const { - return !(*this == other); - } + bool operator!=(const Dpm& other) const { return !(*this == other); } -private: - int m_xDpm; - int m_yDpm; + private: + int m_xDpm; + int m_yDpm; }; diff --git a/EmptyTaskStatus.h b/EmptyTaskStatus.h new file mode 100644 index 000000000..bff7a1a13 --- /dev/null +++ b/EmptyTaskStatus.h @@ -0,0 +1,14 @@ +#ifndef SCANTAILOR_ADVANCED_EMPTYTASKSTATUS_H +#define SCANTAILOR_ADVANCED_EMPTYTASKSTATUS_H + +#include "TaskStatus.h" + +class EmptyTaskStatus : public TaskStatus { + void cancel() override {} + + bool isCancelled() const override { return false; } + + void throwIfCancelled() const override {} +}; + +#endif // SCANTAILOR_ADVANCED_EMPTYTASKSTATUS_H diff --git a/ErrorWidget.cpp b/ErrorWidget.cpp index fb76cc44c..7b751096d 100644 --- a/ErrorWidget.cpp +++ b/ErrorWidget.cpp @@ -17,13 +17,15 @@ */ #include "ErrorWidget.h" +#include +#include ErrorWidget::ErrorWidget(const QString& text, Qt::TextFormat fmt) { - setupUi(this); - textLabel->setTextFormat(fmt); - textLabel->setText(text); - QIcon icon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning)); - imageLabel->setPixmap(icon.pixmap(48, 48)); + setupUi(this); + textLabel->setTextFormat(fmt); + textLabel->setText(text); + QIcon icon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning)); + imageLabel->setPixmap(icon.pixmap(48, 48)); - connect(textLabel, SIGNAL(linkActivated(const QString&)), SLOT(linkActivated(const QString&))); + connect(textLabel, SIGNAL(linkActivated(const QString&)), SLOT(linkActivated(const QString&))); } diff --git a/ErrorWidget.h b/ErrorWidget.h index 335eb6881..efb0ed81b 100644 --- a/ErrorWidget.h +++ b/ErrorWidget.h @@ -19,24 +19,23 @@ #ifndef ERRORWIDGET_H_ #define ERRORWIDGET_H_ -#include "ui_ErrorWidget.h" #include #include +#include "ui_ErrorWidget.h" class QString; class ErrorWidget : public QWidget, private Ui::ErrorWidget { - Q_OBJECT -public: - explicit ErrorWidget(const QString& text, Qt::TextFormat fmt = Qt::AutoText); + Q_OBJECT + public: + explicit ErrorWidget(const QString& text, Qt::TextFormat fmt = Qt::AutoText); -private slots: + private slots: - /** - * \see QLabel::linkActivated() - */ - virtual void linkActivated(const QString& link) { - } + /** + * \see QLabel::linkActivated() + */ + virtual void linkActivated(const QString& link) {} }; diff --git a/EstimateBackground.cpp b/EstimateBackground.cpp index 47d3b8d26..ab3ca5985 100644 --- a/EstimateBackground.cpp +++ b/EstimateBackground.cpp @@ -17,33 +17,31 @@ */ #include "EstimateBackground.h" +#include +#include +#include +#include +#include "DebugImages.h" #include "ImageTransformation.h" #include "TaskStatus.h" -#include "DebugImages.h" -#include "imageproc/GrayImage.h" #include "imageproc/BinaryImage.h" #include "imageproc/BitOps.h" -#include "imageproc/Transform.h" -#include "imageproc/Scale.h" -#include "imageproc/Morphology.h" #include "imageproc/Connectivity.h" +#include "imageproc/GrayImage.h" +#include "imageproc/GrayRasterOp.h" +#include "imageproc/Morphology.h" +#include "imageproc/PolygonRasterizer.h" #include "imageproc/PolynomialLine.h" #include "imageproc/PolynomialSurface.h" -#include "imageproc/PolygonRasterizer.h" -#include "imageproc/GrayRasterOp.h" #include "imageproc/RasterOpGeneric.h" +#include "imageproc/Scale.h" #include "imageproc/SeedFill.h" -#include -#include -#include -#include +#include "imageproc/Transform.h" using namespace imageproc; struct AbsoluteDifference { - static uint8_t transform(uint8_t src, uint8_t dst) { - return static_cast(std::abs(int(src) - int(dst))); - } + static uint8_t transform(uint8_t src, uint8_t dst) { return static_cast(std::abs(int(src) - int(dst))); } }; /** @@ -51,254 +49,254 @@ struct AbsoluteDifference { * at top and bottom, except here colors may only spread vertically. */ static void seedFillTopBottomInPlace(GrayImage& image) { - uint8_t* const data = image.data(); - const int stride = image.stride(); - - const int width = image.width(); - const int height = image.height(); + uint8_t* const data = image.data(); + const int stride = image.stride(); - std::vector seed_line(height, 0xff); + const int width = image.width(); + const int height = image.height(); - for (int x = 0; x < width; ++x) { - uint8_t* p = data + x; - - uint8_t prev = 0; // black - for (int y = 0; y < height; ++y) { - seed_line[y] = prev = std::max(*p, prev); - p += stride; - } - - prev = 0; // black - for (int y = height - 1; y >= 0; --y) { - p -= stride; - *p = prev = std::max(*p, std::min(seed_line[y], prev)); - } - } -} - -static void morphologicalPreprocessingInPlace(GrayImage& image, DebugImages* dbg) { - using namespace boost::lambda; - - // We do morphological preprocessing with one of two methods. The first - // one is good for cases when the dark area is in the middle of the image, - // touching at least one of the vertical edges and not touching the horizontal one. - // The second method is good for pages that have pictures (partly) in the - // shadow of the spine. Most of the other cases can be handled by any of these - // two methods. + std::vector seed_line(height, 0xff); - GrayImage method1(createFramedImage(image.size())); - seedFillGrayInPlace(method1, image, CONN8); + for (int x = 0; x < width; ++x) { + uint8_t* p = data + x; - // This will get rid of the remnants of letters. Note that since we know we - // are working with at most 300x300 px images, we can just hardcode the size. - method1 = openGray(method1, QSize(1, 20), 0x00); - if (dbg) { - dbg->add(method1, "preproc_method1"); - } - - seedFillTopBottomInPlace(image); - if (dbg) { - dbg->add(image, "preproc_method2"); + uint8_t prev = 0; // black + for (int y = 0; y < height; ++y) { + seed_line[y] = prev = std::max(*p, prev); + p += stride; } - // Now let's estimate, which of the methods is better for this case. - - // Take the difference between two methods. - GrayImage diff(image); - rasterOpGeneric(diff.data(), diff.stride(), diff.size(), method1.data(), method1.stride(), _1 -= _2); - if (dbg) { - dbg->add(diff, "raw_diff"); + prev = 0; // black + for (int y = height - 1; y >= 0; --y) { + p -= stride; + *p = prev = std::max(*p, std::min(seed_line[y], prev)); } + } +} - // Approximate the difference using a polynomial function. - // If it fits well into our data set, we consider the difference - // to be caused by a shadow rather than a picture, and use method1. - GrayImage approximated(PolynomialSurface(3, 3, diff).render(diff.size())); +static void morphologicalPreprocessingInPlace(GrayImage& image, DebugImages* dbg) { + using namespace boost::lambda; + + // We do morphological preprocessing with one of two methods. The first + // one is good for cases when the dark area is in the middle of the image, + // touching at least one of the vertical edges and not touching the horizontal one. + // The second method is good for pages that have pictures (partly) in the + // shadow of the spine. Most of the other cases can be handled by any of these + // two methods. + + GrayImage method1(createFramedImage(image.size())); + seedFillGrayInPlace(method1, image, CONN8); + + // This will get rid of the remnants of letters. Note that since we know we + // are working with at most 300x300 px images, we can just hardcode the size. + method1 = openGray(method1, QSize(1, 20), 0x00); + if (dbg) { + dbg->add(method1, "preproc_method1"); + } + + seedFillTopBottomInPlace(image); + if (dbg) { + dbg->add(image, "preproc_method2"); + } + + // Now let's estimate, which of the methods is better for this case. + + // Take the difference between two methods. + GrayImage diff(image); + rasterOpGeneric(diff.data(), diff.stride(), diff.size(), method1.data(), method1.stride(), _1 -= _2); + if (dbg) { + dbg->add(diff, "raw_diff"); + } + + // Approximate the difference using a polynomial function. + // If it fits well into our data set, we consider the difference + // to be caused by a shadow rather than a picture, and use method1. + GrayImage approximated(PolynomialSurface(3, 3, diff).render(diff.size())); + if (dbg) { + dbg->add(approximated, "approx_diff"); + } + // Now let's take the difference between the original difference + // and approximated difference. + rasterOpGeneric(diff.data(), diff.stride(), diff.size(), approximated.data(), approximated.stride(), + if_then_else(_1 > _2, _1 -= _2, _1 = _2 - _1)); + approximated = GrayImage(); // save memory. + if (dbg) { + dbg->add(diff, "raw_vs_approx_diff"); + } + + // Our final decision is like this: + // If we have at least 1% of pixels that are greater than 10, + // we consider that we have a picture rather than a shadow, + // and use method2. + + int sum = 0; + GrayscaleHistogram hist(diff); + for (int i = 255; i > 10; --i) { + sum += hist[i]; + } + + // qDebug() << "% of pixels > 10: " << 100.0 * sum / (diff.width() * diff.height()); + + if (sum < 0.01 * (diff.width() * diff.height())) { + image = method1; if (dbg) { - dbg->add(approximated, "approx_diff"); + dbg->add(image, "use_method1"); } - // Now let's take the difference between the original difference - // and approximated difference. - rasterOpGeneric(diff.data(), diff.stride(), diff.size(), approximated.data(), approximated.stride(), - if_then_else(_1 > _2, _1 -= _2, _1 = _2 - _1)); - approximated = GrayImage(); // save memory. + } else { + // image is already set to method2 if (dbg) { - dbg->add(diff, "raw_vs_approx_diff"); - } - - // Our final decision is like this: - // If we have at least 1% of pixels that are greater than 10, - // we consider that we have a picture rather than a shadow, - // and use method2. - - int sum = 0; - GrayscaleHistogram hist(diff); - for (int i = 255; i > 10; --i) { - sum += hist[i]; - } - - // qDebug() << "% of pixels > 10: " << 100.0 * sum / (diff.width() * diff.height()); - - if (sum < 0.01 * (diff.width() * diff.height())) { - image = method1; - if (dbg) { - dbg->add(image, "use_method1"); - } - } else { - // image is already set to method2 - if (dbg) { - dbg->add(image, "use_method2"); - } + dbg->add(image, "use_method2"); } + } } // morphologicalPreprocessingInPlace imageproc::PolynomialSurface estimateBackground(const GrayImage& input, const QPolygonF& area_to_consider, const TaskStatus& status, DebugImages* dbg) { - QSize reduced_size(input.size()); - reduced_size.scale(300, 300, Qt::KeepAspectRatio); - GrayImage background(scaleToGray(GrayImage(input), reduced_size)); - if (dbg) { - dbg->add(background, "downscaled"); - } + QSize reduced_size(input.size()); + reduced_size.scale(300, 300, Qt::KeepAspectRatio); + GrayImage background(scaleToGray(GrayImage(input), reduced_size)); + if (dbg) { + dbg->add(background, "downscaled"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - morphologicalPreprocessingInPlace(background, dbg); + morphologicalPreprocessingInPlace(background, dbg); - status.throwIfCancelled(); + status.throwIfCancelled(); - const int width = background.width(); - const int height = background.height(); + const int width = background.width(); + const int height = background.height(); - const uint8_t* const bg_data = background.data(); - const int bg_stride = background.stride(); + const uint8_t* const bg_data = background.data(); + const int bg_stride = background.stride(); - BinaryImage mask(background.size(), BLACK); + BinaryImage mask(background.size(), BLACK); - if (!area_to_consider.isEmpty()) { - QTransform xform; - xform.scale((double) reduced_size.width() / input.width(), (double) reduced_size.height() / input.height()); - PolygonRasterizer::fillExcept(mask, WHITE, xform.map(area_to_consider), Qt::WindingFill); - } + if (!area_to_consider.isEmpty()) { + QTransform xform; + xform.scale((double) reduced_size.width() / input.width(), (double) reduced_size.height() / input.height()); + PolygonRasterizer::fillExcept(mask, WHITE, xform.map(area_to_consider), Qt::WindingFill); + } - if (dbg) { - dbg->add(mask, "area_to_consider"); - } + if (dbg) { + dbg->add(mask, "area_to_consider"); + } - uint32_t* mask_data = mask.data(); - int mask_stride = mask.wordsPerLine(); + uint32_t* mask_data = mask.data(); + int mask_stride = mask.wordsPerLine(); - std::vector line(std::max(width, height), 0); - const uint32_t msb = uint32_t(1) << 31; + std::vector line(std::max(width, height), 0); + const uint32_t msb = uint32_t(1) << 31; - status.throwIfCancelled(); + status.throwIfCancelled(); - // Smooth every horizontal line with a polynomial, - // then mask pixels that became significantly lighter. - for (int x = 0; x < width; ++x) { - const uint32_t mask = ~(msb >> (x & 31)); - - const int degree = 2; - PolynomialLine pl(degree, bg_data + x, height, bg_stride); - pl.output(&line[0], height, 1); - - const uint8_t* p_bg = bg_data + x; - uint32_t* p_mask = mask_data + (x >> 5); - for (int y = 0; y < height; ++y) { - if (*p_bg + 30 < line[y]) { - *p_mask &= mask; - } - p_bg += bg_stride; - p_mask += mask_stride; - } - } + // Smooth every horizontal line with a polynomial, + // then mask pixels that became significantly lighter. + for (int x = 0; x < width; ++x) { + const uint32_t mask = ~(msb >> (x & 31)); + + const int degree = 2; + PolynomialLine pl(degree, bg_data + x, height, bg_stride); + pl.output(&line[0], height, 1); - status.throwIfCancelled(); - // Smooth every vertical line with a polynomial, - // then mask pixels that became significantly lighter. - const uint8_t* bg_line = bg_data; - uint32_t* mask_line = mask_data; + const uint8_t* p_bg = bg_data + x; + uint32_t* p_mask = mask_data + (x >> 5); for (int y = 0; y < height; ++y) { - const int degree = 4; - PolynomialLine pl(degree, bg_line, width, 1); - pl.output(&line[0], width, 1); - - for (int x = 0; x < width; ++x) { - if (bg_line[x] + 30 < line[x]) { - mask_line[x >> 5] &= ~(msb >> (x & 31)); - } - } - - bg_line += bg_stride; - mask_line += mask_stride; + if (*p_bg + 30 < line[y]) { + *p_mask &= mask; + } + p_bg += bg_stride; + p_mask += mask_stride; } + } + + status.throwIfCancelled(); + // Smooth every vertical line with a polynomial, + // then mask pixels that became significantly lighter. + const uint8_t* bg_line = bg_data; + uint32_t* mask_line = mask_data; + for (int y = 0; y < height; ++y) { + const int degree = 4; + PolynomialLine pl(degree, bg_line, width, 1); + pl.output(&line[0], width, 1); - if (dbg) { - dbg->add(mask, "mask"); + for (int x = 0; x < width; ++x) { + if (bg_line[x] + 30 < line[x]) { + mask_line[x >> 5] &= ~(msb >> (x & 31)); + } } - status.throwIfCancelled(); - - mask = erodeBrick(mask, QSize(3, 3)); - if (dbg) { - dbg->add(mask, "eroded"); + bg_line += bg_stride; + mask_line += mask_stride; + } + + if (dbg) { + dbg->add(mask, "mask"); + } + + status.throwIfCancelled(); + + mask = erodeBrick(mask, QSize(3, 3)); + if (dbg) { + dbg->add(mask, "eroded"); + } + + status.throwIfCancelled(); + + // Update those because mask was overwritten. + mask_data = mask.data(); + mask_stride = mask.wordsPerLine(); + // Check each horizontal line. If it's mostly + // white (ignored), then make it completely white. + const int last_word_idx = (width - 1) >> 5; + const uint32_t last_word_mask = ~uint32_t(0) << (32 - width - (last_word_idx << 5)); + mask_line = mask_data; + for (int y = 0; y < height; ++y, mask_line += mask_stride) { + int black_count = 0; + int i = 0; + + // Complete words. + for (; i < last_word_idx; ++i) { + black_count += countNonZeroBits(mask_line[i]); } - status.throwIfCancelled(); - - // Update those because mask was overwritten. - mask_data = mask.data(); - mask_stride = mask.wordsPerLine(); - // Check each horizontal line. If it's mostly - // white (ignored), then make it completely white. - const int last_word_idx = (width - 1) >> 5; - const uint32_t last_word_mask = ~uint32_t(0) << (32 - width - (last_word_idx << 5)); - mask_line = mask_data; - for (int y = 0; y < height; ++y, mask_line += mask_stride) { - int black_count = 0; - int i = 0; - - // Complete words. - for (; i < last_word_idx; ++i) { - black_count += countNonZeroBits(mask_line[i]); - } - - // The last (possible incomplete) word. - black_count += countNonZeroBits(mask_line[i] & last_word_mask); - - if (black_count < width / 4) { - memset(mask_line, 0, (last_word_idx + 1) * sizeof(*mask_line)); - } - } + // The last (possible incomplete) word. + black_count += countNonZeroBits(mask_line[i] & last_word_mask); - status.throwIfCancelled(); - // Check each vertical line. If it's mostly - // white (ignored), then make it completely white. - for (int x = 0; x < width; ++x) { - const uint32_t mask = msb >> (x & 31); - uint32_t* p_mask = mask_data + (x >> 5); - int black_count = 0; - for (int y = 0; y < height; ++y) { - if (*p_mask & mask) { - ++black_count; - } - p_mask += mask_stride; - } - if (black_count < height / 4) { - for (int y = height - 1; y >= 0; --y) { - p_mask -= mask_stride; - *p_mask &= ~mask; - } - } + if (black_count < width / 4) { + memset(mask_line, 0, (last_word_idx + 1) * sizeof(*mask_line)); } - - if (dbg) { - dbg->add(mask, "lines_extended"); + } + + status.throwIfCancelled(); + // Check each vertical line. If it's mostly + // white (ignored), then make it completely white. + for (int x = 0; x < width; ++x) { + const uint32_t mask = msb >> (x & 31); + uint32_t* p_mask = mask_data + (x >> 5); + int black_count = 0; + for (int y = 0; y < height; ++y) { + if (*p_mask & mask) { + ++black_count; + } + p_mask += mask_stride; + } + if (black_count < height / 4) { + for (int y = height - 1; y >= 0; --y) { + p_mask -= mask_stride; + *p_mask &= ~mask; + } } + } + + if (dbg) { + dbg->add(mask, "lines_extended"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - return PolynomialSurface(8, 5, background, mask); + return PolynomialSurface(8, 5, background, mask); } // estimateBackground diff --git a/FileNameDisambiguator.cpp b/FileNameDisambiguator.cpp index 21661aef7..89ab3497b 100644 --- a/FileNameDisambiguator.cpp +++ b/FileNameDisambiguator.cpp @@ -17,228 +17,221 @@ */ #include "FileNameDisambiguator.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" -#include #include +#include #include -#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include +#include +#include "AbstractRelinker.h" +#include "RelinkablePath.h" using namespace boost::multi_index; class FileNameDisambiguator::Impl { -public: - Impl(); + public: + Impl(); - Impl(const QDomElement& disambiguator_el, const boost::function& file_path_unpacker); + Impl(const QDomElement& disambiguator_el, const boost::function& file_path_unpacker); - QDomElement toXml(QDomDocument& doc, - const QString& name, - const boost::function& file_path_packer) const; + QDomElement toXml(QDomDocument& doc, + const QString& name, + const boost::function& file_path_packer) const; - int getLabel(const QString& file_path) const; + int getLabel(const QString& file_path) const; - int registerFile(const QString& file_path); + int registerFile(const QString& file_path); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); -private: - class ItemsByFilePathTag; - class ItemsByFileNameLabelTag; + private: + class ItemsByFilePathTag; + class ItemsByFileNameLabelTag; - class UnorderedItemsTag; + class UnorderedItemsTag; - struct Item { - QString filePath; - QString fileName; - int label; + struct Item { + QString filePath; + QString fileName; + int label; - Item(const QString& file_path, int lbl); + Item(const QString& file_path, int lbl); - Item(const QString& file_path, const QString& file_name, int lbl); - }; + Item(const QString& file_path, const QString& file_name, int lbl); + }; - typedef multi_index_container< - Item, - indexed_by, member>, - ordered_unique, - composite_key, - member>>, - sequenced>>> - Container; + typedef multi_index_container< + Item, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, member>>, + sequenced>>> + Container; - typedef Container::index::type ItemsByFilePath; - typedef Container::index::type ItemsByFileNameLabel; - typedef Container::index::type UnorderedItems; + typedef Container::index::type ItemsByFilePath; + typedef Container::index::type ItemsByFileNameLabel; + typedef Container::index::type UnorderedItems; - mutable QMutex m_mutex; - Container m_items; - ItemsByFilePath& m_itemsByFilePath; - ItemsByFileNameLabel& m_itemsByFileNameLabel; - UnorderedItems& m_unorderedItems; + mutable QMutex m_mutex; + Container m_items; + ItemsByFilePath& m_itemsByFilePath; + ItemsByFileNameLabel& m_itemsByFileNameLabel; + UnorderedItems& m_unorderedItems; }; /*====================== FileNameDisambiguator =========================*/ -FileNameDisambiguator::FileNameDisambiguator() : m_ptrImpl(new Impl) { -} +FileNameDisambiguator::FileNameDisambiguator() : m_impl(new Impl) {} FileNameDisambiguator::FileNameDisambiguator(const QDomElement& disambiguator_el) - : m_ptrImpl(new Impl(disambiguator_el, boost::lambda::_1)) { -} + : m_impl(new Impl(disambiguator_el, boost::lambda::_1)) {} FileNameDisambiguator::FileNameDisambiguator(const QDomElement& disambiguator_el, const boost::function& file_path_unpacker) - : m_ptrImpl(new Impl(disambiguator_el, file_path_unpacker)) { -} + : m_impl(new Impl(disambiguator_el, file_path_unpacker)) {} QDomElement FileNameDisambiguator::toXml(QDomDocument& doc, const QString& name) const { - return m_ptrImpl->toXml(doc, name, boost::lambda::_1); + return m_impl->toXml(doc, name, boost::lambda::_1); } QDomElement FileNameDisambiguator::toXml(QDomDocument& doc, const QString& name, const boost::function& file_path_packer) const { - return m_ptrImpl->toXml(doc, name, file_path_packer); + return m_impl->toXml(doc, name, file_path_packer); } int FileNameDisambiguator::getLabel(const QString& file_path) const { - return m_ptrImpl->getLabel(file_path); + return m_impl->getLabel(file_path); } int FileNameDisambiguator::registerFile(const QString& file_path) { - return m_ptrImpl->registerFile(file_path); + return m_impl->registerFile(file_path); } void FileNameDisambiguator::performRelinking(const AbstractRelinker& relinker) { - m_ptrImpl->performRelinking(relinker); + m_impl->performRelinking(relinker); } /*==================== FileNameDisambiguator::Impl ====================*/ FileNameDisambiguator::Impl::Impl() - : m_items(), - m_itemsByFilePath(m_items.get()), - m_itemsByFileNameLabel(m_items.get()), - m_unorderedItems(m_items.get()) { -} + : m_items(), + m_itemsByFilePath(m_items.get()), + m_itemsByFileNameLabel(m_items.get()), + m_unorderedItems(m_items.get()) {} FileNameDisambiguator::Impl::Impl(const QDomElement& disambiguator_el, const boost::function& file_path_unpacker) - : m_items(), - m_itemsByFilePath(m_items.get()), - m_itemsByFileNameLabel(m_items.get()), - m_unorderedItems(m_items.get()) { - QDomNode node(disambiguator_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != "mapping") { - continue; - } - const QDomElement file_el(node.toElement()); - - const QString file_path_shorthand(file_el.attribute("file")); - const QString file_path = file_path_unpacker(file_path_shorthand); - if (file_path.isEmpty()) { - // Unresolved shorthand - skipping this record. - continue; - } - - const int label = file_el.attribute("label").toInt(); - m_items.insert(Item(file_path, label)); + : m_items(), + m_itemsByFilePath(m_items.get()), + m_itemsByFileNameLabel(m_items.get()), + m_unorderedItems(m_items.get()) { + QDomNode node(disambiguator_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } + if (node.nodeName() != "mapping") { + continue; + } + const QDomElement file_el(node.toElement()); + + const QString file_path_shorthand(file_el.attribute("file")); + const QString file_path = file_path_unpacker(file_path_shorthand); + if (file_path.isEmpty()) { + // Unresolved shorthand - skipping this record. + continue; + } + + const int label = file_el.attribute("label").toInt(); + m_items.insert(Item(file_path, label)); + } } QDomElement FileNameDisambiguator::Impl::toXml(QDomDocument& doc, const QString& name, const boost::function& file_path_packer) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - QDomElement el(doc.createElement(name)); + QDomElement el(doc.createElement(name)); - for (const Item& item : m_unorderedItems) { - const QString file_path_shorthand = file_path_packer(item.filePath); - if (file_path_shorthand.isEmpty()) { - // Unrepresentable file path - skipping this record. - continue; - } - - QDomElement file_el(doc.createElement("mapping")); - file_el.setAttribute("file", file_path_shorthand); - file_el.setAttribute("label", item.label); - el.appendChild(file_el); + for (const Item& item : m_unorderedItems) { + const QString file_path_shorthand = file_path_packer(item.filePath); + if (file_path_shorthand.isEmpty()) { + // Unrepresentable file path - skipping this record. + continue; } - return el; + QDomElement file_el(doc.createElement("mapping")); + file_el.setAttribute("file", file_path_shorthand); + file_el.setAttribute("label", item.label); + el.appendChild(file_el); + } + + return el; } int FileNameDisambiguator::Impl::getLabel(const QString& file_path) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const ItemsByFilePath::iterator fp_it(m_itemsByFilePath.find(file_path)); - if (fp_it != m_itemsByFilePath.end()) { - return fp_it->label; - } + const ItemsByFilePath::iterator fp_it(m_itemsByFilePath.find(file_path)); + if (fp_it != m_itemsByFilePath.end()) { + return fp_it->label; + } - return 0; + return 0; } int FileNameDisambiguator::Impl::registerFile(const QString& file_path) { - const QMutexLocker locker(&m_mutex); - - const ItemsByFilePath::iterator fp_it(m_itemsByFilePath.find(file_path)); - if (fp_it != m_itemsByFilePath.end()) { - return fp_it->label; + const QMutexLocker locker(&m_mutex); + + const ItemsByFilePath::iterator fp_it(m_itemsByFilePath.find(file_path)); + if (fp_it != m_itemsByFilePath.end()) { + return fp_it->label; + } + + int label = 0; + + const QString file_name(QFileInfo(file_path).fileName()); + const ItemsByFileNameLabel::iterator fn_it(m_itemsByFileNameLabel.upper_bound(boost::make_tuple(file_name))); + // If the item preceeding fn_it has the same file name, + // the new file belongs to the same disambiguation group. + if (fn_it != m_itemsByFileNameLabel.begin()) { + ItemsByFileNameLabel::iterator prev(fn_it); + --prev; + if (prev->fileName == file_name) { + label = prev->label + 1; } + } // Otherwise, label remains 0. + const Item new_item(file_path, file_name, label); + m_itemsByFileNameLabel.insert(fn_it, new_item); - int label = 0; - - const QString file_name(QFileInfo(file_path).fileName()); - const ItemsByFileNameLabel::iterator fn_it(m_itemsByFileNameLabel.upper_bound(boost::make_tuple(file_name))); - // If the item preceeding fn_it has the same file name, - // the new file belongs to the same disambiguation group. - if (fn_it != m_itemsByFileNameLabel.begin()) { - ItemsByFileNameLabel::iterator prev(fn_it); - --prev; - if (prev->fileName == file_name) { - label = prev->label + 1; - } - } // Otherwise, label remains 0. - const Item new_item(file_path, file_name, label); - m_itemsByFileNameLabel.insert(fn_it, new_item); - - return label; + return label; } void FileNameDisambiguator::Impl::performRelinking(const AbstractRelinker& relinker) { - const QMutexLocker locker(&m_mutex); - Container new_items; + const QMutexLocker locker(&m_mutex); + Container new_items; - for (const Item& item : m_unorderedItems) { - const RelinkablePath old_path(item.filePath, RelinkablePath::File); - Item new_item(relinker.substitutionPathFor(old_path), item.label); - new_items.insert(new_item); - } + for (const Item& item : m_unorderedItems) { + const RelinkablePath old_path(item.filePath, RelinkablePath::File); + Item new_item(relinker.substitutionPathFor(old_path), item.label); + new_items.insert(new_item); + } - m_items.swap(new_items); + m_items.swap(new_items); } /*============================ Impl::Item =============================*/ FileNameDisambiguator::Impl::Item::Item(const QString& file_path, int lbl) - : filePath(file_path), fileName(QFileInfo(file_path).fileName()), label(lbl) { -} + : filePath(file_path), fileName(QFileInfo(file_path).fileName()), label(lbl) {} FileNameDisambiguator::Impl::Item::Item(const QString& file_path, const QString& file_name, int lbl) - : filePath(file_path), fileName(file_name), label(lbl) { -} + : filePath(file_path), fileName(file_name), label(lbl) {} diff --git a/FileNameDisambiguator.h b/FileNameDisambiguator.h index 54c8ac153..78235ff79 100644 --- a/FileNameDisambiguator.h +++ b/FileNameDisambiguator.h @@ -19,11 +19,11 @@ #ifndef FILENAME_DISAMBIGUATOR_H_ #define FILENAME_DISAMBIGUATOR_H_ -#include "NonCopyable.h" -#include "ref_countable.h" #include #include #include +#include "NonCopyable.h" +#include "ref_countable.h" class AbstractRelinker; class QString; @@ -38,54 +38,54 @@ class QDomDocument; * \note This class is thread-safe. */ class FileNameDisambiguator : public ref_countable { - DECLARE_NON_COPYABLE(FileNameDisambiguator) - -public: - FileNameDisambiguator(); - - /** - * \brief Load disambiguation information from XML. - */ - explicit FileNameDisambiguator(const QDomElement& disambiguator_el); - - /** - * \brief Load disambiguation information from XML with file path unpacking. - * - * Supplying a file path unpacker allows storing shorthands rather than - * full paths in XML. Unpacker is a functor taking a shorthand and - * returning the full path. If unpacker returns an empty string, - * the record will be skipped. - */ - FileNameDisambiguator(const QDomElement& disambiguator_el, - const boost::function& file_path_unpacker); - - /** - * \brief Serialize disambiguation information to XML. - */ - QDomElement toXml(QDomDocument& doc, const QString& name) const; - - /** - * \brief Serialize disambiguation information to XML with file path packing. - * - * Supplying a file path packer allows storing shorthands rather than - * full paths in XML. Packer is a functor taking a full file path and - * returning the corresponding shorthand. If packer returns an empty string, - * the record will be skipped. - */ - QDomElement toXml(QDomDocument& doc, - const QString& name, - const boost::function& file_path_packer) const; - - int getLabel(const QString& file_path) const; - - int registerFile(const QString& file_path); - - void performRelinking(const AbstractRelinker& relinker); - -private: - class Impl; - - std::unique_ptr m_ptrImpl; + DECLARE_NON_COPYABLE(FileNameDisambiguator) + + public: + FileNameDisambiguator(); + + /** + * \brief Load disambiguation information from XML. + */ + explicit FileNameDisambiguator(const QDomElement& disambiguator_el); + + /** + * \brief Load disambiguation information from XML with file path unpacking. + * + * Supplying a file path unpacker allows storing shorthands rather than + * full paths in XML. Unpacker is a functor taking a shorthand and + * returning the full path. If unpacker returns an empty string, + * the record will be skipped. + */ + FileNameDisambiguator(const QDomElement& disambiguator_el, + const boost::function& file_path_unpacker); + + /** + * \brief Serialize disambiguation information to XML. + */ + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + /** + * \brief Serialize disambiguation information to XML with file path packing. + * + * Supplying a file path packer allows storing shorthands rather than + * full paths in XML. Packer is a functor taking a full file path and + * returning the corresponding shorthand. If packer returns an empty string, + * the record will be skipped. + */ + QDomElement toXml(QDomDocument& doc, + const QString& name, + const boost::function& file_path_packer) const; + + int getLabel(const QString& file_path) const; + + int registerFile(const QString& file_path); + + void performRelinking(const AbstractRelinker& relinker); + + private: + class Impl; + + std::unique_ptr m_impl; }; diff --git a/FilterData.cpp b/FilterData.cpp index 0755525a7..f31e7ca87 100644 --- a/FilterData.cpp +++ b/FilterData.cpp @@ -23,34 +23,36 @@ using namespace imageproc; FilterData::FilterData(const QImage& image) - : m_origImage(image), m_grayImage(toGrayscale(m_origImage)), m_xform(image.rect(), Dpm(image)) { -} + : m_origImage(image), m_grayImage(toGrayscale(m_origImage)), m_xform(image.rect(), Dpm(image)) {} FilterData::FilterData(const FilterData& other, const ImageTransformation& xform) - : m_origImage(other.m_origImage), - m_grayImage(other.m_grayImage), - m_xform(xform), - m_imageParams(other.m_imageParams) { -} + : m_origImage(other.m_origImage), + m_grayImage(other.m_grayImage), + m_xform(xform), + m_imageParams(other.m_imageParams) {} FilterData::FilterData(const FilterData& other) = default; imageproc::BinaryThreshold FilterData::bwThreshold() const { - return m_imageParams.getBwThreshold(); + return m_imageParams.getBwThreshold(); } const ImageTransformation& FilterData::xform() const { - return m_xform; + return m_xform; } const QImage& FilterData::origImage() const { - return m_origImage; + return m_origImage; } const imageproc::GrayImage& FilterData::grayImage() const { - return m_grayImage; + return m_grayImage; +} + +bool FilterData::isBlackOnWhite() const { + return m_imageParams.isBlackOnWhite(); } void FilterData::updateImageParams(const ImageSettings::PageParams& imageParams) { - m_imageParams = imageParams; + m_imageParams = imageParams; } diff --git a/FilterData.h b/FilterData.h index 4fb85f865..8705cf1f5 100644 --- a/FilterData.h +++ b/FilterData.h @@ -19,36 +19,38 @@ #ifndef FILTERDATA_H_ #define FILTERDATA_H_ +#include +#include "ImageSettings.h" +#include "ImageTransformation.h" #include "imageproc/BinaryThreshold.h" #include "imageproc/GrayImage.h" -#include "ImageTransformation.h" -#include "ImageSettings.h" -#include class FilterData { - // Member-wise copying is OK. -public: - explicit FilterData(const QImage& image); + // Member-wise copying is OK. + public: + explicit FilterData(const QImage& image); + + FilterData(const FilterData& other, const ImageTransformation& xform); - FilterData(const FilterData& other, const ImageTransformation& xform); + FilterData(const FilterData& other); - FilterData(const FilterData& other); + imageproc::BinaryThreshold bwThreshold() const; - imageproc::BinaryThreshold bwThreshold() const; + const ImageTransformation& xform() const; - const ImageTransformation& xform() const; + const QImage& origImage() const; - const QImage& origImage() const; + const imageproc::GrayImage& grayImage() const; - const imageproc::GrayImage& grayImage() const; + bool isBlackOnWhite() const; - void updateImageParams(const ImageSettings::PageParams& imageParams); + void updateImageParams(const ImageSettings::PageParams& imageParams); -private: - QImage m_origImage; - imageproc::GrayImage m_grayImage; - ImageTransformation m_xform; - ImageSettings::PageParams m_imageParams; + private: + QImage m_origImage; + imageproc::GrayImage m_grayImage; + ImageTransformation m_xform; + ImageSettings::PageParams m_imageParams; }; diff --git a/FilterOptionsWidget.h b/FilterOptionsWidget.h index 024f5c79d..8a30ada5c 100644 --- a/FilterOptionsWidget.h +++ b/FilterOptionsWidget.h @@ -19,34 +19,34 @@ #ifndef FILTEROPTIONSWIDGET_H_ #define FILTEROPTIONSWIDGET_H_ +#include #include "PageId.h" #include "PageInfo.h" -#include class FilterOptionsWidget : public QWidget { - Q_OBJECT -signals: + Q_OBJECT + signals: - /** - * \brief To be emitted by subclasses when they want to reload the page. - */ - void reloadRequested(); + /** + * \brief To be emitted by subclasses when they want to reload the page. + */ + void reloadRequested(); - void invalidateThumbnail(const PageId& page_id); + void invalidateThumbnail(const PageId& page_id); - /** - * This signature differs from invalidateThumbnail(PageId) in that - * it will cause PageInfo stored by ThumbnailSequence to be updated. - */ - void invalidateThumbnail(const PageInfo& page_info); + /** + * This signature differs from invalidateThumbnail(PageId) in that + * it will cause PageInfo stored by ThumbnailSequence to be updated. + */ + void invalidateThumbnail(const PageInfo& page_info); - void invalidateAllThumbnails(); + void invalidateAllThumbnails(); - /** - * After we've got rid of "Widest Page" / "Tallest Page" links, - * there is no one using this signal. It's a candidate for removal. - */ - void goToPage(const PageId& page_id); + /** + * After we've got rid of "Widest Page" / "Tallest Page" links, + * there is no one using this signal. It's a candidate for removal. + */ + void goToPage(const PageId& page_id); }; diff --git a/FilterResult.h b/FilterResult.h index 44de8b024..7682f2573 100644 --- a/FilterResult.h +++ b/FilterResult.h @@ -26,17 +26,17 @@ class AbstractFilter; class FilterUiInterface; class FilterResult : public ref_countable { -public: - virtual void updateUI(FilterUiInterface* ui) = 0; + public: + virtual void updateUI(FilterUiInterface* ui) = 0; - /** - * \brief Return the filter that generated this result. - * \note Returning a null smart pointer indicates that the result - * was generated by a task that doesn't belong to a filter. - * That would be LoadFileTask. - */ + /** + * \brief Return the filter that generated this result. + * \note Returning a null smart pointer indicates that the result + * was generated by a task that doesn't belong to a filter. + * That would be LoadFileTask. + */ - virtual intrusive_ptr filter() = 0; + virtual intrusive_ptr filter() = 0; }; diff --git a/FilterUiInterface.h b/FilterUiInterface.h index 80432667c..8ae1e19c3 100644 --- a/FilterUiInterface.h +++ b/FilterUiInterface.h @@ -19,8 +19,8 @@ #ifndef FILTERUIINTERFACE_H_ #define FILTERUIINTERFACE_H_ -#include "PageId.h" #include "AbstractCommand.h" +#include "PageId.h" #include "intrusive_ptr.h" class DebugImages; @@ -31,27 +31,27 @@ class QWidget; * \brief A reduced interface to MainWindow to allow filters to manupulate the UI. */ class FilterUiInterface { -public: - enum Ownership { KEEP_OWNERSHIP, TRANSFER_OWNERSHIP }; + public: + enum Ownership { KEEP_OWNERSHIP, TRANSFER_OWNERSHIP }; - virtual ~FilterUiInterface() = default; + virtual ~FilterUiInterface() = default; - virtual void setOptionsWidget(FilterOptionsWidget* widget, Ownership ownership) = 0; + virtual void setOptionsWidget(FilterOptionsWidget* widget, Ownership ownership) = 0; - virtual void setImageWidget(QWidget* widget, - Ownership ownership, - DebugImages* debug_images = nullptr, - bool clearImageWidget = true) - = 0; + virtual void setImageWidget(QWidget* widget, + Ownership ownership, + DebugImages* debug_images = nullptr, + bool overlay = false) + = 0; - virtual void invalidateThumbnail(const PageId& page_id) = 0; + virtual void invalidateThumbnail(const PageId& page_id) = 0; - virtual void invalidateAllThumbnails() = 0; + virtual void invalidateAllThumbnails() = 0; - /** - * Returns a callable object that when called will open a relinking dialog. - */ - virtual intrusive_ptr> relinkingDialogRequester() = 0; + /** + * Returns a callable object that when called will open a relinking dialog. + */ + virtual intrusive_ptr> relinkingDialogRequester() = 0; }; diff --git a/FixDpiDialog.cpp b/FixDpiDialog.cpp index 22b26247a..4e0ff7db6 100644 --- a/FixDpiDialog.cpp +++ b/FixDpiDialog.cpp @@ -17,11 +17,11 @@ */ #include "FixDpiDialog.h" -#include "ColorSchemeManager.h" #include #include -#include #include +#include +#include "ColorSchemeManager.h" // To be able to use it in QVariant Q_DECLARE_METATYPE(ImageMetadata) @@ -47,30 +47,30 @@ static const int AGGREGATE_NOT_OK_METADATA_ROLE = Qt::UserRole + 1; * ImageMetadata will have the consistent DPI and zero size. */ class FixDpiDialog::DpiCounts { -public: - void add(const ImageMetadata& metadata); + public: + void add(const ImageMetadata& metadata); - void remove(const ImageMetadata& metadata); + void remove(const ImageMetadata& metadata); - /** - * Checks if all ImageMetadata objects return true for ImageMetadata::isDpiOK(). - */ - bool allDpisOK() const; + /** + * Checks if all ImageMetadata objects return true for ImageMetadata::isDpiOK(). + */ + bool allDpisOK() const; - /** - * If all ImageMetadata objects are equal, one of them will be returned. - * Otherwise, a default-constructed ImageMetadata() object will be returned. - */ - ImageMetadata aggregate(Scope scope) const; + /** + * If all ImageMetadata objects are equal, one of them will be returned. + * Otherwise, a default-constructed ImageMetadata() object will be returned. + */ + ImageMetadata aggregate(Scope scope) const; -private: - struct MetadataComparator { - bool operator()(const ImageMetadata& lhs, const ImageMetadata& rhs) const; - }; + private: + struct MetadataComparator { + bool operator()(const ImageMetadata& lhs, const ImageMetadata& rhs) const; + }; - typedef std::map Map; + typedef std::map Map; - Map m_counts; + Map m_counts; }; @@ -78,262 +78,243 @@ class FixDpiDialog::DpiCounts { * This comparator puts objects that are not OK to the front. */ bool FixDpiDialog::DpiCounts::MetadataComparator::operator()(const ImageMetadata& lhs, const ImageMetadata& rhs) const { - const bool lhs_ok = lhs.isDpiOK(); - const bool rhs_ok = rhs.isDpiOK(); - if (lhs_ok != rhs_ok) { - return rhs_ok; - } - - if (lhs.size().width() < rhs.size().width()) { - return true; - } else if (lhs.size().width() > rhs.size().width()) { - return false; - } else if (lhs.size().height() < rhs.size().height()) { - return true; - } else if (lhs.size().height() > rhs.size().height()) { - return false; - } else if (lhs.dpi().horizontal() < rhs.dpi().horizontal()) { - return true; - } else if (lhs.dpi().horizontal() > rhs.dpi().horizontal()) { - return false; - } else { - return lhs.dpi().vertical() < rhs.dpi().vertical(); - } + const bool lhs_ok = lhs.isDpiOK(); + const bool rhs_ok = rhs.isDpiOK(); + if (lhs_ok != rhs_ok) { + return rhs_ok; + } + + if (lhs.size().width() < rhs.size().width()) { + return true; + } else if (lhs.size().width() > rhs.size().width()) { + return false; + } else if (lhs.size().height() < rhs.size().height()) { + return true; + } else if (lhs.size().height() > rhs.size().height()) { + return false; + } else if (lhs.dpi().horizontal() < rhs.dpi().horizontal()) { + return true; + } else if (lhs.dpi().horizontal() > rhs.dpi().horizontal()) { + return false; + } else { + return lhs.dpi().vertical() < rhs.dpi().vertical(); + } } class FixDpiDialog::SizeGroup { -public: - struct Item { - int fileIdx; - int imageIdx; + public: + struct Item { + int fileIdx; + int imageIdx; - Item(int file_idx, int image_idx) : fileIdx(file_idx), imageIdx(image_idx) { - } - }; + Item(int file_idx, int image_idx) : fileIdx(file_idx), imageIdx(image_idx) {} + }; - explicit SizeGroup(const QSize& size) : m_size(size) { - } + explicit SizeGroup(const QSize& size) : m_size(size) {} - void append(const Item& item, const ImageMetadata& metadata); + void append(const Item& item, const ImageMetadata& metadata); - const QSize& size() const { - return m_size; - } + const QSize& size() const { return m_size; } - const std::vector& items() const { - return m_items; - } + const std::vector& items() const { return m_items; } - DpiCounts& dpiCounts() { - return m_dpiCounts; - } + DpiCounts& dpiCounts() { return m_dpiCounts; } - const DpiCounts& dpiCounts() const { - return m_dpiCounts; - } + const DpiCounts& dpiCounts() const { return m_dpiCounts; } -private: - QSize m_size; - std::vector m_items; - DpiCounts m_dpiCounts; + private: + QSize m_size; + std::vector m_items; + DpiCounts m_dpiCounts; }; class FixDpiDialog::TreeModel : private QAbstractItemModel { -public: - explicit TreeModel(const std::vector& files); + public: + explicit TreeModel(const std::vector& files); - const std::vector& files() const { - return m_files; - } + const std::vector& files() const { return m_files; } - QAbstractItemModel* model() { - return this; - } + QAbstractItemModel* model() { return this; } - bool allDpisOK() const { - return m_dpiCounts.allDpisOK(); - } + bool allDpisOK() const { return m_dpiCounts.allDpisOK(); } - bool isVisibleForFilter(const QModelIndex& parent, int row) const; + bool isVisibleForFilter(const QModelIndex& parent, int row) const; - void applyDpiToSelection(Scope scope, const Dpi& dpi, const QItemSelection& selection); + void applyDpiToSelection(Scope scope, const Dpi& dpi, const QItemSelection& selection); -private: - struct Tag {}; + private: + struct Tag {}; - int columnCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; - int rowCount(const QModelIndex& parent) const override; + int rowCount(const QModelIndex& parent) const override; - QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex index(int row, int column, const QModelIndex& parent) const override; - QModelIndex parent(const QModelIndex& index) const override; + QModelIndex parent(const QModelIndex& index) const override; - QVariant data(const QModelIndex& index, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; - void applyDpiToAllGroups(Scope scope, const Dpi& dpi); + void applyDpiToAllGroups(Scope scope, const Dpi& dpi); - void applyDpiToGroup(Scope scope, const Dpi& dpi, SizeGroup& group, DpiCounts& total_dpi_counts); + void applyDpiToGroup(Scope scope, const Dpi& dpi, SizeGroup& group, DpiCounts& total_dpi_counts); - void applyDpiToItem(Scope scope, - const ImageMetadata& new_metadata, - SizeGroup::Item item, - DpiCounts& total_dpi_counts, - DpiCounts& group_dpi_counts); + void applyDpiToItem(Scope scope, + const ImageMetadata& new_metadata, + SizeGroup::Item item, + DpiCounts& total_dpi_counts, + DpiCounts& group_dpi_counts); - void emitAllPagesChanged(const QModelIndex& idx); + void emitAllPagesChanged(const QModelIndex& idx); - void emitSizeGroupChanged(const QModelIndex& idx); + void emitSizeGroupChanged(const QModelIndex& idx); - void emitItemChanged(const QModelIndex& idx); + void emitItemChanged(const QModelIndex& idx); - SizeGroup& sizeGroupFor(QSize size); + SizeGroup& sizeGroupFor(QSize size); - static QString sizeToString(QSize size); + static QString sizeToString(QSize size); - static Tag m_allPagesNodeId; - static Tag m_sizeGroupNodeId; + static Tag m_allPagesNodeId; + static Tag m_sizeGroupNodeId; - std::vector m_files; - std::vector m_sizes; - DpiCounts m_dpiCounts; + std::vector m_files; + std::vector m_sizes; + DpiCounts m_dpiCounts; }; class FixDpiDialog::FilterModel : private QSortFilterProxyModel { -public: - explicit FilterModel(TreeModel& delegate); + public: + explicit FilterModel(TreeModel& delegate); - QAbstractProxyModel* model() { - return this; - } + QAbstractProxyModel* model() { return this; } -private: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + private: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; - QVariant data(const QModelIndex& index, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; - TreeModel& m_rDelegate; + TreeModel& m_delegate; }; FixDpiDialog::FixDpiDialog(const std::vector& files, QWidget* parent) - : QDialog(parent), m_ptrPages(new TreeModel(files)), m_ptrUndefinedDpiPages(new FilterModel(*m_ptrPages)) { - setupUi(this); + : QDialog(parent), m_pages(new TreeModel(files)), m_undefinedDpiPages(new FilterModel(*m_pages)) { + setupUi(this); - m_normalPalette = xDpi->palette(); - m_errorPalette = m_normalPalette; - m_errorPalette.setColor( - QPalette::Text, - ColorSchemeManager::instance()->getColorParam("fix_dpi_dialog_error_text_color", Qt::red).color()); + m_normalPalette = xDpi->palette(); + m_errorPalette = m_normalPalette; + const QColor error_text_color + = ColorSchemeManager::instance()->getColorParam(ColorScheme::FixDpiDialogErrorText, QColor(Qt::red)); + m_errorPalette.setColor(QPalette::Text, error_text_color); - dpiCombo->addItem("300 x 300", QSize(300, 300)); - dpiCombo->addItem("400 x 400", QSize(400, 400)); - dpiCombo->addItem("600 x 600", QSize(600, 600)); + dpiCombo->addItem("300 x 300", QSize(300, 300)); + dpiCombo->addItem("400 x 400", QSize(400, 400)); + dpiCombo->addItem("600 x 600", QSize(600, 600)); - tabWidget->setTabText(NEED_FIXING_TAB, tr("Need Fixing")); - tabWidget->setTabText(ALL_PAGES_TAB, tr("All Pages")); - undefinedDpiView->setModel(m_ptrUndefinedDpiPages->model()), undefinedDpiView->header()->hide(); - allPagesView->setModel(m_ptrPages->model()); - allPagesView->header()->hide(); + tabWidget->setTabText(NEED_FIXING_TAB, tr("Need Fixing")); + tabWidget->setTabText(ALL_PAGES_TAB, tr("All Pages")); + undefinedDpiView->setModel(m_undefinedDpiPages->model()), undefinedDpiView->header()->hide(); + allPagesView->setModel(m_pages->model()); + allPagesView->header()->hide(); - xDpi->setMaxLength(4); - yDpi->setMaxLength(4); - xDpi->setValidator(new QIntValidator(xDpi)); - yDpi->setValidator(new QIntValidator(yDpi)); + xDpi->setMaxLength(4); + yDpi->setMaxLength(4); + xDpi->setValidator(new QIntValidator(xDpi)); + yDpi->setValidator(new QIntValidator(yDpi)); - connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); - connect(undefinedDpiView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - this, SLOT(selectionChanged(const QItemSelection&))); - connect(allPagesView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - this, SLOT(selectionChanged(const QItemSelection&))); + connect(undefinedDpiView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(selectionChanged(const QItemSelection&))); + connect(allPagesView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, + SLOT(selectionChanged(const QItemSelection&))); - connect(dpiCombo, SIGNAL(activated(int)), this, SLOT(dpiComboChangedByUser(int))); + connect(dpiCombo, SIGNAL(activated(int)), this, SLOT(dpiComboChangedByUser(int))); - connect(xDpi, SIGNAL(textEdited(const QString&)), this, SLOT(dpiValueChanged())); - connect(yDpi, SIGNAL(textEdited(const QString&)), this, SLOT(dpiValueChanged())); + connect(xDpi, SIGNAL(textEdited(const QString&)), this, SLOT(dpiValueChanged())); + connect(yDpi, SIGNAL(textEdited(const QString&)), this, SLOT(dpiValueChanged())); - connect(applyBtn, SIGNAL(clicked()), this, SLOT(applyClicked())); + connect(applyBtn, SIGNAL(clicked()), this, SLOT(applyClicked())); - enableDisableOkButton(); + enableDisableOkButton(); } FixDpiDialog::~FixDpiDialog() = default; const std::vector& FixDpiDialog::files() const { - return m_ptrPages->files(); + return m_pages->files(); } void FixDpiDialog::tabChanged(const int tab) { - QTreeView* views[2]; - views[NEED_FIXING_TAB] = undefinedDpiView; - views[ALL_PAGES_TAB] = allPagesView; - updateDpiFromSelection(views[tab]->selectionModel()->selection()); + QTreeView* views[2]; + views[NEED_FIXING_TAB] = undefinedDpiView; + views[ALL_PAGES_TAB] = allPagesView; + updateDpiFromSelection(views[tab]->selectionModel()->selection()); } void FixDpiDialog::selectionChanged(const QItemSelection& selection) { - updateDpiFromSelection(selection); + updateDpiFromSelection(selection); } void FixDpiDialog::dpiComboChangedByUser(const int index) { - const QVariant data(dpiCombo->itemData(index)); - if (data.isValid()) { - const QSize dpi(data.toSize()); - xDpi->setText(QString::number(dpi.width())); - yDpi->setText(QString::number(dpi.height())); - dpiValueChanged(); - } + const QVariant data(dpiCombo->itemData(index)); + if (data.isValid()) { + const QSize dpi(data.toSize()); + xDpi->setText(QString::number(dpi.width())); + yDpi->setText(QString::number(dpi.height())); + dpiValueChanged(); + } } void FixDpiDialog::dpiValueChanged() { - updateDpiCombo(); + updateDpiCombo(); - const Dpi dpi(xDpi->text().toInt(), yDpi->text().toInt()); - const ImageMetadata metadata(m_selectedItemPixelSize, dpi); + const Dpi dpi(xDpi->text().toInt(), yDpi->text().toInt()); + const ImageMetadata metadata(m_selectedItemPixelSize, dpi); - decorateDpiInputField(xDpi, metadata.horizontalDpiStatus()); - decorateDpiInputField(yDpi, metadata.verticalDpiStatus()); + decorateDpiInputField(xDpi, metadata.horizontalDpiStatus()); + decorateDpiInputField(yDpi, metadata.verticalDpiStatus()); - if ((m_xDpiInitialValue == xDpi->text()) && (m_yDpiInitialValue == yDpi->text())) { - applyBtn->setEnabled(false); + if ((m_xDpiInitialValue == xDpi->text()) && (m_yDpiInitialValue == yDpi->text())) { + applyBtn->setEnabled(false); - return; - } + return; + } - if (metadata.isDpiOK()) { - applyBtn->setEnabled(true); + if (metadata.isDpiOK()) { + applyBtn->setEnabled(true); - return; - } + return; + } - applyBtn->setEnabled(false); + applyBtn->setEnabled(false); } void FixDpiDialog::applyClicked() { - const Dpi dpi(xDpi->text().toInt(), yDpi->text().toInt()); - QItemSelectionModel* selection_model = nullptr; - - if (tabWidget->currentIndex() == ALL_PAGES_TAB) { - selection_model = allPagesView->selectionModel(); - const QItemSelection selection(selection_model->selection()); - m_ptrPages->applyDpiToSelection(ALL, dpi, selection); - } else { - selection_model = undefinedDpiView->selectionModel(); - const QItemSelection selection( - m_ptrUndefinedDpiPages->model()->mapSelectionToSource(selection_model->selection())); - m_ptrPages->applyDpiToSelection(NOT_OK, dpi, selection); - } + const Dpi dpi(xDpi->text().toInt(), yDpi->text().toInt()); + QItemSelectionModel* selection_model = nullptr; - updateDpiFromSelection(selection_model->selection()); - enableDisableOkButton(); + if (tabWidget->currentIndex() == ALL_PAGES_TAB) { + selection_model = allPagesView->selectionModel(); + const QItemSelection selection(selection_model->selection()); + m_pages->applyDpiToSelection(ALL, dpi, selection); + } else { + selection_model = undefinedDpiView->selectionModel(); + const QItemSelection selection(m_undefinedDpiPages->model()->mapSelectionToSource(selection_model->selection())); + m_pages->applyDpiToSelection(NOT_OK, dpi, selection); + } + + updateDpiFromSelection(selection_model->selection()); + enableDisableOkButton(); } void FixDpiDialog::enableDisableOkButton() { - const bool enable = m_ptrPages->allDpisOK(); - buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); + const bool enable = m_pages->allDpisOK(); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); } /** @@ -341,153 +322,154 @@ void FixDpiDialog::enableDisableOkButton() { * It is assumed that only a single item is selected. */ void FixDpiDialog::updateDpiFromSelection(const QItemSelection& selection) { - if (selection.isEmpty()) { - resetDpiForm(); - dpiCombo->setEnabled(false); - xDpi->setEnabled(false); - yDpi->setEnabled(false); - // applyBtn is managed elsewhere. - return; - } - - dpiCombo->setEnabled(true); - xDpi->setEnabled(true); - yDpi->setEnabled(true); - - // FilterModel may replace AGGREGATE_METADATA_ROLE with AGGREGATE_NOT_OK_METADATA_ROLE. - const QVariant data(selection.front().topLeft().data(AGGREGATE_METADATA_ROLE)); - if (data.isValid()) { - setDpiForm(data.value()); - } else { - resetDpiForm(); - } + if (selection.isEmpty()) { + resetDpiForm(); + dpiCombo->setEnabled(false); + xDpi->setEnabled(false); + yDpi->setEnabled(false); + // applyBtn is managed elsewhere. + return; + } + + dpiCombo->setEnabled(true); + xDpi->setEnabled(true); + yDpi->setEnabled(true); + + // FilterModel may replace AGGREGATE_METADATA_ROLE with AGGREGATE_NOT_OK_METADATA_ROLE. + const QVariant data(selection.front().topLeft().data(AGGREGATE_METADATA_ROLE)); + if (data.isValid()) { + setDpiForm(data.value()); + } else { + resetDpiForm(); + } } void FixDpiDialog::resetDpiForm() { - dpiCombo->setCurrentIndex(0); - m_xDpiInitialValue.clear(); - m_yDpiInitialValue.clear(); - xDpi->setText(m_xDpiInitialValue); - yDpi->setText(m_yDpiInitialValue); - dpiValueChanged(); + dpiCombo->setCurrentIndex(0); + m_xDpiInitialValue.clear(); + m_yDpiInitialValue.clear(); + xDpi->setText(m_xDpiInitialValue); + yDpi->setText(m_yDpiInitialValue); + dpiValueChanged(); } void FixDpiDialog::setDpiForm(const ImageMetadata& metadata) { - const Dpi dpi(metadata.dpi()); + const Dpi dpi(metadata.dpi()); - if (dpi.isNull()) { - resetDpiForm(); + if (dpi.isNull()) { + resetDpiForm(); - return; - } + return; + } - m_xDpiInitialValue = QString::number(dpi.horizontal()); - m_yDpiInitialValue = QString::number(dpi.vertical()); - m_selectedItemPixelSize = metadata.size(); - xDpi->setText(m_xDpiInitialValue); - yDpi->setText(m_yDpiInitialValue); - dpiValueChanged(); + m_xDpiInitialValue = QString::number(dpi.horizontal()); + m_yDpiInitialValue = QString::number(dpi.vertical()); + m_selectedItemPixelSize = metadata.size(); + xDpi->setText(m_xDpiInitialValue); + yDpi->setText(m_yDpiInitialValue); + dpiValueChanged(); } void FixDpiDialog::updateDpiCombo() { - bool x_ok = true, y_ok = true; - const QSize dpi(xDpi->text().toInt(&x_ok), yDpi->text().toInt(&y_ok)); - - if (x_ok && y_ok) { - const int count = dpiCombo->count(); - for (int i = 0; i < count; ++i) { - const QVariant data(dpiCombo->itemData(i)); - if (data.isValid()) { - if (dpi == data.toSize()) { - dpiCombo->setCurrentIndex(i); - - return; - } - } + bool x_ok = true, y_ok = true; + const QSize dpi(xDpi->text().toInt(&x_ok), yDpi->text().toInt(&y_ok)); + + if (x_ok && y_ok) { + const int count = dpiCombo->count(); + for (int i = 0; i < count; ++i) { + const QVariant data(dpiCombo->itemData(i)); + if (data.isValid()) { + if (dpi == data.toSize()) { + dpiCombo->setCurrentIndex(i); + + return; } + } } + } - dpiCombo->setCurrentIndex(0); + dpiCombo->setCurrentIndex(0); } void FixDpiDialog::decorateDpiInputField(QLineEdit* field, ImageMetadata::DpiStatus dpi_status) const { - if (dpi_status == ImageMetadata::DPI_OK) { - field->setPalette(m_normalPalette); - } else { - field->setPalette(m_errorPalette); - } - - switch (dpi_status) { - case ImageMetadata::DPI_OK: - case ImageMetadata::DPI_UNDEFINED: - field->setToolTip(QString()); - break; - case ImageMetadata::DPI_TOO_LARGE: - field->setToolTip(tr("DPI is too large and most likely wrong.")); - break; - case ImageMetadata::DPI_TOO_SMALL: - field->setToolTip( - tr("DPI is too small. Even if it's correct, you are not going to get acceptable results with it.")); - break; - case ImageMetadata::DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE: - field->setToolTip(tr("DPI is too small for this pixel size. Such combination would probably lead to out of " - "memory errors.")); - break; - } + if (dpi_status == ImageMetadata::DPI_OK) { + field->setPalette(m_normalPalette); + } else { + field->setPalette(m_errorPalette); + } + + switch (dpi_status) { + case ImageMetadata::DPI_OK: + case ImageMetadata::DPI_UNDEFINED: + field->setToolTip(QString()); + break; + case ImageMetadata::DPI_TOO_LARGE: + field->setToolTip(tr("DPI is too large and most likely wrong.")); + break; + case ImageMetadata::DPI_TOO_SMALL: + field->setToolTip( + tr("DPI is too small. Even if it's correct, you are not going to get acceptable results with it.")); + break; + case ImageMetadata::DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE: + field->setToolTip( + tr("DPI is too small for this pixel size. Such combination would probably lead to out of " + "memory errors.")); + break; + } } /*====================== FixDpiDialog::DpiCounts ======================*/ void FixDpiDialog::DpiCounts::add(const ImageMetadata& metadata) { - ++m_counts[metadata]; + ++m_counts[metadata]; } void FixDpiDialog::DpiCounts::remove(const ImageMetadata& metadata) { - if (--m_counts[metadata] == 0) { - m_counts.erase(metadata); - } + if (--m_counts[metadata] == 0) { + m_counts.erase(metadata); + } } bool FixDpiDialog::DpiCounts::allDpisOK() const { - // We put wrong DPIs to the front, so if the first one is OK, - // the others are OK as well. - const auto it(m_counts.begin()); + // We put wrong DPIs to the front, so if the first one is OK, + // the others are OK as well. + const auto it(m_counts.begin()); - return it == m_counts.end() || it->first.isDpiOK(); + return it == m_counts.end() || it->first.isDpiOK(); } ImageMetadata FixDpiDialog::DpiCounts::aggregate(const Scope scope) const { - const auto it(m_counts.begin()); + const auto it(m_counts.begin()); - if (it == m_counts.end()) { - return ImageMetadata(); - } + if (it == m_counts.end()) { + return ImageMetadata(); + } - if ((scope == NOT_OK) && it->first.isDpiOK()) { - // If this one is OK, the following ones are OK as well. - return ImageMetadata(); - } + if ((scope == NOT_OK) && it->first.isDpiOK()) { + // If this one is OK, the following ones are OK as well. + return ImageMetadata(); + } - Map::const_iterator next(it); - ++next; + Map::const_iterator next(it); + ++next; - if (next == m_counts.end()) { - return it->first; - } + if (next == m_counts.end()) { + return it->first; + } - if ((scope == NOT_OK) && next->first.isDpiOK()) { - // If this one is OK, the following ones are OK as well. - return it->first; - } + if ((scope == NOT_OK) && next->first.isDpiOK()) { + // If this one is OK, the following ones are OK as well. + return it->first; + } - return ImageMetadata(); + return ImageMetadata(); } /*====================== FixDpiDialog::SizeGroup ======================*/ void FixDpiDialog::SizeGroup::append(const Item& item, const ImageMetadata& metadata) { - m_items.push_back(item); - m_dpiCounts.add(metadata); + m_items.push_back(item); + m_dpiCounts.add(metadata); } /*====================== FixDpiDialog::TreeModel ======================*/ @@ -496,192 +478,192 @@ FixDpiDialog::TreeModel::Tag FixDpiDialog::TreeModel::m_allPagesNodeId; FixDpiDialog::TreeModel::Tag FixDpiDialog::TreeModel::m_sizeGroupNodeId; FixDpiDialog::TreeModel::TreeModel(const std::vector& files) : m_files(files) { - const auto num_files = static_cast(m_files.size()); - for (int i = 0; i < num_files; ++i) { - const ImageFileInfo& file = m_files[i]; - const auto num_images = static_cast(file.imageInfo().size()); - for (int j = 0; j < num_images; ++j) { - const ImageMetadata& metadata = file.imageInfo()[j]; - SizeGroup& group = sizeGroupFor(metadata.size()); - group.append(SizeGroup::Item(i, j), metadata); - m_dpiCounts.add(metadata); - } + const auto num_files = static_cast(m_files.size()); + for (int i = 0; i < num_files; ++i) { + const ImageFileInfo& file = m_files[i]; + const auto num_images = static_cast(file.imageInfo().size()); + for (int j = 0; j < num_images; ++j) { + const ImageMetadata& metadata = file.imageInfo()[j]; + SizeGroup& group = sizeGroupFor(metadata.size()); + group.append(SizeGroup::Item(i, j), metadata); + m_dpiCounts.add(metadata); } + } } bool FixDpiDialog::TreeModel::isVisibleForFilter(const QModelIndex& parent, int row) const { - const void* const ptr = parent.internalPointer(); - - if (!parent.isValid()) { - // 'All Pages'. - return !m_dpiCounts.allDpisOK(); - } else if (ptr == &m_allPagesNodeId) { - // A size group. - return !m_sizes[row].dpiCounts().allDpisOK(); - } else if (ptr == &m_sizeGroupNodeId) { - // An image. - const SizeGroup& group = m_sizes[parent.row()]; - const SizeGroup::Item& item = group.items()[row]; - const ImageFileInfo& file = m_files[item.fileIdx]; - - return !file.imageInfo()[item.imageIdx].isDpiOK(); - } else { - // Should not happen. - return false; - } + const void* const ptr = parent.internalPointer(); + + if (!parent.isValid()) { + // 'All Pages'. + return !m_dpiCounts.allDpisOK(); + } else if (ptr == &m_allPagesNodeId) { + // A size group. + return !m_sizes[row].dpiCounts().allDpisOK(); + } else if (ptr == &m_sizeGroupNodeId) { + // An image. + const SizeGroup& group = m_sizes[parent.row()]; + const SizeGroup::Item& item = group.items()[row]; + const ImageFileInfo& file = m_files[item.fileIdx]; + + return !file.imageInfo()[item.imageIdx].isDpiOK(); + } else { + // Should not happen. + return false; + } } void FixDpiDialog::TreeModel::applyDpiToSelection(const Scope scope, const Dpi& dpi, const QItemSelection& selection) { - if (selection.isEmpty()) { - return; - } - - const QModelIndex parent(selection.front().parent()); - const int row = selection.front().top(); - const void* const ptr = parent.internalPointer(); - const QModelIndex idx(index(row, 0, parent)); - - if (!parent.isValid()) { - // Apply to all pages. - applyDpiToAllGroups(scope, dpi); - emitAllPagesChanged(idx); - } else if (ptr == &m_allPagesNodeId) { - // Apply to a size group. - SizeGroup& group = m_sizes[row]; - applyDpiToGroup(scope, dpi, group, m_dpiCounts); - emitSizeGroupChanged(index(row, 0, parent)); - } else if (ptr == &m_sizeGroupNodeId) { - // Images within a size group. - SizeGroup& group = m_sizes[parent.row()]; - const SizeGroup::Item& item = group.items()[row]; - const ImageMetadata metadata(group.size(), dpi); - applyDpiToItem(scope, metadata, item, m_dpiCounts, group.dpiCounts()); - emitItemChanged(idx); - } + if (selection.isEmpty()) { + return; + } + + const QModelIndex parent(selection.front().parent()); + const int row = selection.front().top(); + const void* const ptr = parent.internalPointer(); + const QModelIndex idx(index(row, 0, parent)); + + if (!parent.isValid()) { + // Apply to all pages. + applyDpiToAllGroups(scope, dpi); + emitAllPagesChanged(idx); + } else if (ptr == &m_allPagesNodeId) { + // Apply to a size group. + SizeGroup& group = m_sizes[row]; + applyDpiToGroup(scope, dpi, group, m_dpiCounts); + emitSizeGroupChanged(index(row, 0, parent)); + } else if (ptr == &m_sizeGroupNodeId) { + // Images within a size group. + SizeGroup& group = m_sizes[parent.row()]; + const SizeGroup::Item& item = group.items()[row]; + const ImageMetadata metadata(group.size(), dpi); + applyDpiToItem(scope, metadata, item, m_dpiCounts, group.dpiCounts()); + emitItemChanged(idx); + } } int FixDpiDialog::TreeModel::columnCount(const QModelIndex& parent) const { - return 1; + return 1; } int FixDpiDialog::TreeModel::rowCount(const QModelIndex& parent) const { - const void* const ptr = parent.internalPointer(); - - if (!parent.isValid()) { - // The single 'All Pages' item. - return 1; - } else if (ptr == &m_allPagesNodeId) { - // Size groups. - return static_cast(m_sizes.size()); - } else if (ptr == &m_sizeGroupNodeId) { - // Images within a size group. - return static_cast(m_sizes[parent.row()].items().size()); - } else { - // Children of an image. - return 0; - } + const void* const ptr = parent.internalPointer(); + + if (!parent.isValid()) { + // The single 'All Pages' item. + return 1; + } else if (ptr == &m_allPagesNodeId) { + // Size groups. + return static_cast(m_sizes.size()); + } else if (ptr == &m_sizeGroupNodeId) { + // Images within a size group. + return static_cast(m_sizes[parent.row()].items().size()); + } else { + // Children of an image. + return 0; + } } QModelIndex FixDpiDialog::TreeModel::index(const int row, const int column, const QModelIndex& parent) const { - const void* const ptr = parent.internalPointer(); - - if (!parent.isValid()) { - // The 'All Pages' item. - return createIndex(row, column, &m_allPagesNodeId); - } else if (ptr == &m_allPagesNodeId) { - // A size group. - return createIndex(row, column, &m_sizeGroupNodeId); - } else if (ptr == &m_sizeGroupNodeId) { - // An image within some size group. - return createIndex(row, column, (void*) &m_sizes[parent.row()]); - } + const void* const ptr = parent.internalPointer(); - return QModelIndex(); + if (!parent.isValid()) { + // The 'All Pages' item. + return createIndex(row, column, &m_allPagesNodeId); + } else if (ptr == &m_allPagesNodeId) { + // A size group. + return createIndex(row, column, &m_sizeGroupNodeId); + } else if (ptr == &m_sizeGroupNodeId) { + // An image within some size group. + return createIndex(row, column, (void*) &m_sizes[parent.row()]); + } + + return QModelIndex(); } QModelIndex FixDpiDialog::TreeModel::parent(const QModelIndex& index) const { - const void* const ptr = index.internalPointer(); - - if (!index.isValid()) { - // Should not happen. - return QModelIndex(); - } else if (ptr == &m_allPagesNodeId) { - // 'All Pages' -> tree root. - return QModelIndex(); - } else if (ptr == &m_sizeGroupNodeId) { - // Size group -> 'All Pages'. - return createIndex(0, index.column(), &m_allPagesNodeId); - } else { - // Image -> size group. - const auto* group = static_cast(ptr); - - return createIndex(static_cast(group - &m_sizes[0]), index.column(), &m_sizeGroupNodeId); - } + const void* const ptr = index.internalPointer(); + + if (!index.isValid()) { + // Should not happen. + return QModelIndex(); + } else if (ptr == &m_allPagesNodeId) { + // 'All Pages' -> tree root. + return QModelIndex(); + } else if (ptr == &m_sizeGroupNodeId) { + // Size group -> 'All Pages'. + return createIndex(0, index.column(), &m_allPagesNodeId); + } else { + // Image -> size group. + const auto* group = static_cast(ptr); + + return createIndex(static_cast(group - &m_sizes[0]), index.column(), &m_sizeGroupNodeId); + } } QVariant FixDpiDialog::TreeModel::data(const QModelIndex& index, const int role) const { - const void* const ptr = index.internalPointer(); - - if (!index.isValid()) { - // Should not happen. - return QVariant(); - } else if (ptr == &m_allPagesNodeId) { - // 'All Pages'. - if (role == Qt::DisplayRole) { - return FixDpiDialog::tr("All Pages"); - } else if (role == AGGREGATE_METADATA_ROLE) { - return QVariant::fromValue(m_dpiCounts.aggregate(ALL)); - } else if (role == AGGREGATE_NOT_OK_METADATA_ROLE) { - return QVariant::fromValue(m_dpiCounts.aggregate(NOT_OK)); - } - } else if (ptr == &m_sizeGroupNodeId) { - // Size group. - const SizeGroup& group = m_sizes[index.row()]; - if (role == Qt::DisplayRole) { - return sizeToString(group.size()); - } else if (role == AGGREGATE_METADATA_ROLE) { - return QVariant::fromValue(group.dpiCounts().aggregate(ALL)); - } else if (role == AGGREGATE_NOT_OK_METADATA_ROLE) { - return QVariant::fromValue(group.dpiCounts().aggregate(NOT_OK)); - } - } else { - // Image. - const auto* group = static_cast(ptr); - const SizeGroup::Item& item = group->items()[index.row()]; - const ImageFileInfo& file = m_files[item.fileIdx]; - if (role == Qt::DisplayRole) { - const QString& fname = file.fileInfo().fileName(); - if (file.imageInfo().size() == 1) { - return fname; - } else { - return FixDpiDialog::tr("%1 (page %2)").arg(fname).arg(item.imageIdx + 1); - } - } else if ((role == AGGREGATE_METADATA_ROLE) || (role == AGGREGATE_NOT_OK_METADATA_ROLE)) { - return QVariant::fromValue(file.imageInfo()[item.imageIdx]); - } - } + const void* const ptr = index.internalPointer(); + if (!index.isValid()) { + // Should not happen. return QVariant(); + } else if (ptr == &m_allPagesNodeId) { + // 'All Pages'. + if (role == Qt::DisplayRole) { + return FixDpiDialog::tr("All Pages"); + } else if (role == AGGREGATE_METADATA_ROLE) { + return QVariant::fromValue(m_dpiCounts.aggregate(ALL)); + } else if (role == AGGREGATE_NOT_OK_METADATA_ROLE) { + return QVariant::fromValue(m_dpiCounts.aggregate(NOT_OK)); + } + } else if (ptr == &m_sizeGroupNodeId) { + // Size group. + const SizeGroup& group = m_sizes[index.row()]; + if (role == Qt::DisplayRole) { + return sizeToString(group.size()); + } else if (role == AGGREGATE_METADATA_ROLE) { + return QVariant::fromValue(group.dpiCounts().aggregate(ALL)); + } else if (role == AGGREGATE_NOT_OK_METADATA_ROLE) { + return QVariant::fromValue(group.dpiCounts().aggregate(NOT_OK)); + } + } else { + // Image. + const auto* group = static_cast(ptr); + const SizeGroup::Item& item = group->items()[index.row()]; + const ImageFileInfo& file = m_files[item.fileIdx]; + if (role == Qt::DisplayRole) { + const QString& fname = file.fileInfo().fileName(); + if (file.imageInfo().size() == 1) { + return fname; + } else { + return FixDpiDialog::tr("%1 (page %2)").arg(fname).arg(item.imageIdx + 1); + } + } else if ((role == AGGREGATE_METADATA_ROLE) || (role == AGGREGATE_NOT_OK_METADATA_ROLE)) { + return QVariant::fromValue(file.imageInfo()[item.imageIdx]); + } + } + + return QVariant(); } // FixDpiDialog::TreeModel::data void FixDpiDialog::TreeModel::applyDpiToAllGroups(const Scope scope, const Dpi& dpi) { - const auto num_groups = static_cast(m_sizes.size()); - for (int i = 0; i < num_groups; ++i) { - applyDpiToGroup(scope, dpi, m_sizes[i], m_dpiCounts); - } + const auto num_groups = static_cast(m_sizes.size()); + for (int i = 0; i < num_groups; ++i) { + applyDpiToGroup(scope, dpi, m_sizes[i], m_dpiCounts); + } } void FixDpiDialog::TreeModel::applyDpiToGroup(const Scope scope, const Dpi& dpi, SizeGroup& group, DpiCounts& total_dpi_counts) { - DpiCounts& group_dpi_counts = group.dpiCounts(); - const ImageMetadata metadata(group.size(), dpi); - const std::vector& items = group.items(); - const auto num_items = static_cast(items.size()); - for (int i = 0; i < num_items; ++i) { - applyDpiToItem(scope, metadata, items[i], total_dpi_counts, group_dpi_counts); - } + DpiCounts& group_dpi_counts = group.dpiCounts(); + const ImageMetadata metadata(group.size(), dpi); + const std::vector& items = group.items(); + const auto num_items = static_cast(items.size()); + for (int i = 0; i < num_items; ++i) { + applyDpiToItem(scope, metadata, items[i], total_dpi_counts, group_dpi_counts); + } } void FixDpiDialog::TreeModel::applyDpiToItem(const Scope scope, @@ -689,93 +671,93 @@ void FixDpiDialog::TreeModel::applyDpiToItem(const Scope scope, const SizeGroup::Item item, DpiCounts& total_dpi_counts, DpiCounts& group_dpi_counts) { - ImageFileInfo& file = m_files[item.fileIdx]; - ImageMetadata& old_metadata = file.imageInfo()[item.imageIdx]; + ImageFileInfo& file = m_files[item.fileIdx]; + ImageMetadata& old_metadata = file.imageInfo()[item.imageIdx]; - if ((scope == NOT_OK) && old_metadata.isDpiOK()) { - return; - } + if ((scope == NOT_OK) && old_metadata.isDpiOK()) { + return; + } - total_dpi_counts.add(new_metadata); - group_dpi_counts.add(new_metadata); - total_dpi_counts.remove(old_metadata); - group_dpi_counts.remove(old_metadata); + total_dpi_counts.add(new_metadata); + group_dpi_counts.add(new_metadata); + total_dpi_counts.remove(old_metadata); + group_dpi_counts.remove(old_metadata); - old_metadata = new_metadata; + old_metadata = new_metadata; } void FixDpiDialog::TreeModel::emitAllPagesChanged(const QModelIndex& idx) { - const auto num_groups = static_cast(m_sizes.size()); - for (int i = 0; i < num_groups; ++i) { - const QModelIndex group_node(index(i, 0, idx)); - const int num_items = rowCount(group_node); - for (int j = 0; j < num_items; ++j) { - const QModelIndex image_node(index(j, 0, group_node)); - emit dataChanged(image_node, image_node); - } - emit dataChanged(group_node, group_node); + const auto num_groups = static_cast(m_sizes.size()); + for (int i = 0; i < num_groups; ++i) { + const QModelIndex group_node(index(i, 0, idx)); + const int num_items = rowCount(group_node); + for (int j = 0; j < num_items; ++j) { + const QModelIndex image_node(index(j, 0, group_node)); + emit dataChanged(image_node, image_node); } + emit dataChanged(group_node, group_node); + } - // The 'All Pages' node. - emit dataChanged(idx, idx); + // The 'All Pages' node. + emit dataChanged(idx, idx); } void FixDpiDialog::TreeModel::emitSizeGroupChanged(const QModelIndex& idx) { - // Every item in this size group. - emit dataChanged(index(0, 0, idx), index(rowCount(idx), 0, idx)); + // Every item in this size group. + emit dataChanged(index(0, 0, idx), index(rowCount(idx), 0, idx)); - // The size group itself. - emit dataChanged(idx, idx); + // The size group itself. + emit dataChanged(idx, idx); - // The 'All Pages' node. - const QModelIndex all_pages_node(idx.parent()); - emit dataChanged(all_pages_node, all_pages_node); + // The 'All Pages' node. + const QModelIndex all_pages_node(idx.parent()); + emit dataChanged(all_pages_node, all_pages_node); } void FixDpiDialog::TreeModel::emitItemChanged(const QModelIndex& idx) { - // The item itself. - emit dataChanged(idx, idx); + // The item itself. + emit dataChanged(idx, idx); - // The size group node. - const QModelIndex group_node(idx.parent()); - emit dataChanged(group_node, group_node); - // The 'All Pages' node. - const QModelIndex all_pages_node(group_node.parent()); - emit dataChanged(all_pages_node, all_pages_node); + // The size group node. + const QModelIndex group_node(idx.parent()); + emit dataChanged(group_node, group_node); + // The 'All Pages' node. + const QModelIndex all_pages_node(group_node.parent()); + emit dataChanged(all_pages_node, all_pages_node); } FixDpiDialog::SizeGroup& FixDpiDialog::TreeModel::sizeGroupFor(const QSize size) { - using namespace boost::lambda; + using namespace boost::lambda; - const auto it(std::find_if(m_sizes.begin(), m_sizes.end(), bind(&SizeGroup::size, _1) == size)); - if (it != m_sizes.end()) { - return *it; - } else { - m_sizes.emplace_back(size); + const auto it(std::find_if(m_sizes.begin(), m_sizes.end(), bind(&SizeGroup::size, _1) == size)); + if (it != m_sizes.end()) { + return *it; + } else { + m_sizes.emplace_back(size); - return m_sizes.back(); - } + return m_sizes.back(); + } } QString FixDpiDialog::TreeModel::sizeToString(const QSize size) { - return QString("%1 x %2 px").arg(size.width()).arg(size.height()); + return QString("%1 x %2 px").arg(size.width()).arg(size.height()); } /*====================== FixDpiDialog::FilterModel ======================*/ -FixDpiDialog::FilterModel::FilterModel(TreeModel& delegate) : m_rDelegate(delegate) { - setDynamicSortFilter(true); - setSourceModel(delegate.model()); +FixDpiDialog::FilterModel::FilterModel(TreeModel& delegate) : m_delegate(delegate) { + setDynamicSortFilter(true); + setSourceModel(delegate.model()); } bool FixDpiDialog::FilterModel::filterAcceptsRow(const int source_row, const QModelIndex& source_parent) const { - return m_rDelegate.isVisibleForFilter(source_parent, source_row); + return m_delegate.isVisibleForFilter(source_parent, source_row); } QVariant FixDpiDialog::FilterModel::data(const QModelIndex& index, int role) const { - if (role == AGGREGATE_METADATA_ROLE) { - role = AGGREGATE_NOT_OK_METADATA_ROLE; - } + if (role == AGGREGATE_METADATA_ROLE) { + role = AGGREGATE_NOT_OK_METADATA_ROLE; + } - return QSortFilterProxyModel::data(index, role); + return QSortFilterProxyModel::data(index, role); } diff --git a/FixDpiDialog.h b/FixDpiDialog.h index 7126921dd..7e78ca0d9 100644 --- a/FixDpiDialog.h +++ b/FixDpiDialog.h @@ -19,68 +19,68 @@ #ifndef FIXDPIDIALOG_H_ #define FIXDPIDIALOG_H_ -#include "ui_FixDpiDialog.h" -#include "ImageFileInfo.h" -#include "ImageMetadata.h" -#include "Dpi.h" #include -#include #include #include #include -#include +#include #include +#include +#include "Dpi.h" +#include "ImageFileInfo.h" +#include "ImageMetadata.h" +#include "ui_FixDpiDialog.h" class QItemSelection; class FixDpiDialog : public QDialog, private Ui::FixDpiDialog { - Q_OBJECT -public: - explicit FixDpiDialog(const std::vector& files, QWidget* parent = nullptr); + Q_OBJECT + public: + explicit FixDpiDialog(const std::vector& files, QWidget* parent = nullptr); - ~FixDpiDialog() override; + ~FixDpiDialog() override; - const std::vector& files() const; + const std::vector& files() const; -private slots: + private slots: - void tabChanged(int tab); + void tabChanged(int tab); - void selectionChanged(const QItemSelection& selection); + void selectionChanged(const QItemSelection& selection); - void dpiComboChangedByUser(int index); + void dpiComboChangedByUser(int index); - void dpiValueChanged(); + void dpiValueChanged(); - void applyClicked(); + void applyClicked(); -private: - class DpiCounts; - class SizeGroup; - class TreeModel; - class FilterModel; + private: + class DpiCounts; + class SizeGroup; + class TreeModel; + class FilterModel; - enum Scope { ALL, NOT_OK }; + enum Scope { ALL, NOT_OK }; - void enableDisableOkButton(); + void enableDisableOkButton(); - void updateDpiFromSelection(const QItemSelection& selection); + void updateDpiFromSelection(const QItemSelection& selection); - void resetDpiForm(); + void resetDpiForm(); - void setDpiForm(const ImageMetadata& metadata); + void setDpiForm(const ImageMetadata& metadata); - void updateDpiCombo(); + void updateDpiCombo(); - void decorateDpiInputField(QLineEdit* field, ImageMetadata::DpiStatus dpi_status) const; + void decorateDpiInputField(QLineEdit* field, ImageMetadata::DpiStatus dpi_status) const; - std::unique_ptr m_ptrPages; - std::unique_ptr m_ptrUndefinedDpiPages; - QString m_xDpiInitialValue; - QString m_yDpiInitialValue; - QSize m_selectedItemPixelSize; - QPalette m_normalPalette; - QPalette m_errorPalette; + std::unique_ptr m_pages; + std::unique_ptr m_undefinedDpiPages; + QString m_xDpiInitialValue; + QString m_yDpiInitialValue; + QSize m_selectedItemPixelSize; + QPalette m_normalPalette; + QPalette m_errorPalette; }; diff --git a/ImageFileInfo.cpp b/ImageFileInfo.cpp index 6e2995ae7..1babf19ce 100644 --- a/ImageFileInfo.cpp +++ b/ImageFileInfo.cpp @@ -17,12 +17,11 @@ */ #include "ImageFileInfo.h" -#include #include +#include bool ImageFileInfo::isDpiOK() const { - using namespace boost::lambda; + using namespace boost::lambda; - return std::find_if(m_imageInfo.begin(), m_imageInfo.end(), !bind(&ImageMetadata::isDpiOK, _1)) - == m_imageInfo.end(); + return std::find_if(m_imageInfo.begin(), m_imageInfo.end(), !bind(&ImageMetadata::isDpiOK, _1)) == m_imageInfo.end(); } diff --git a/ImageFileInfo.h b/ImageFileInfo.h index 87675c521..fc654c76c 100644 --- a/ImageFileInfo.h +++ b/ImageFileInfo.h @@ -19,34 +19,27 @@ #ifndef IMAGEFILEINFO_H_ #define IMAGEFILEINFO_H_ -#include "ImageMetadata.h" #include #include +#include "ImageMetadata.h" class ImageFileInfo { - // Member-wise copying is OK. -public: - ImageFileInfo(const QFileInfo& file_info, const std::vector& image_info) - : m_fileInfo(file_info), m_imageInfo(image_info) { - } + // Member-wise copying is OK. + public: + ImageFileInfo(const QFileInfo& file_info, const std::vector& image_info) + : m_fileInfo(file_info), m_imageInfo(image_info) {} - const QFileInfo& fileInfo() const { - return m_fileInfo; - } + const QFileInfo& fileInfo() const { return m_fileInfo; } - std::vector& imageInfo() { - return m_imageInfo; - } + std::vector& imageInfo() { return m_imageInfo; } - const std::vector& imageInfo() const { - return m_imageInfo; - } + const std::vector& imageInfo() const { return m_imageInfo; } - bool isDpiOK() const; + bool isDpiOK() const; -private: - QFileInfo m_fileInfo; - std::vector m_imageInfo; + private: + QFileInfo m_fileInfo; + std::vector m_imageInfo; }; diff --git a/ImageId.cpp b/ImageId.cpp index e6a916a6a..373f2ad3f 100644 --- a/ImageId.cpp +++ b/ImageId.cpp @@ -19,27 +19,25 @@ #include "ImageId.h" #include -ImageId::ImageId(const QString& file_path, const int page) : m_filePath(file_path), m_page(page) { -} +ImageId::ImageId(const QString& file_path, const int page) : m_filePath(file_path), m_page(page) {} -ImageId::ImageId(const QFileInfo& file_info, const int page) : m_filePath(file_info.absoluteFilePath()), m_page(page) { -} +ImageId::ImageId(const QFileInfo& file_info, const int page) : m_filePath(file_info.absoluteFilePath()), m_page(page) {} bool operator==(const ImageId& lhs, const ImageId& rhs) { - return ((lhs.page() == rhs.page()) && (lhs.filePath() == rhs.filePath())); + return ((lhs.page() == rhs.page()) && (lhs.filePath() == rhs.filePath())); } bool operator!=(const ImageId& lhs, const ImageId& rhs) { - return !(lhs == rhs); + return !(lhs == rhs); } bool operator<(const ImageId& lhs, const ImageId& rhs) { - const int comp = lhs.filePath().compare(rhs.filePath()); - if (comp < 0) { - return true; - } else if (comp > 0) { - return false; - } - - return lhs.page() < rhs.page(); + const int comp = lhs.filePath().compare(rhs.filePath()); + if (comp < 0) { + return true; + } else if (comp > 0) { + return false; + } + + return lhs.page() < rhs.page(); } diff --git a/ImageId.h b/ImageId.h index 6a37f6aa0..b49b95b11 100644 --- a/ImageId.h +++ b/ImageId.h @@ -19,57 +19,42 @@ #ifndef IMAGEID_H_ #define IMAGEID_H_ -#include #include +#include class QFileInfo; class ImageId { - // Member-wise copying is OK. -public: - ImageId() : m_filePath(), m_page(0) { - } + // Member-wise copying is OK. + public: + ImageId() : m_filePath(), m_page(0) {} - explicit ImageId(const QString& file_path, int page = 0); + explicit ImageId(const QString& file_path, int page = 0); - explicit ImageId(const QFileInfo& file_info, int page = 0); + explicit ImageId(const QFileInfo& file_info, int page = 0); - bool isNull() const { - return m_filePath.isNull(); - } + bool isNull() const { return m_filePath.isNull(); } - const QString& filePath() const { - return m_filePath; - } + const QString& filePath() const { return m_filePath; } - void setFilePath(const QString& path) { - m_filePath = path; - } + void setFilePath(const QString& path) { m_filePath = path; } - int page() const { - return m_page; - } + int page() const { return m_page; } - void setPage(int page) { - m_page = page; - } + void setPage(int page) { m_page = page; } - int zeroBasedPage() const { - return m_page > 0 ? m_page - 1 : 0; - } + int zeroBasedPage() const { return m_page > 0 ? m_page - 1 : 0; } - bool isMultiPageFile() const { - return m_page > 0; - } + bool isMultiPageFile() const { return m_page > 0; } -private: - QString m_filePath; + private: + QString m_filePath; - /** - * If zero, indicates the file is not multipage. - * If above zero, indicates Nth page in a multipage file. - */ - int m_page; + /** + * If zero, indicates the file is not multipage. + * If above zero, indicates Nth page in a multipage file. + */ + int m_page; }; @@ -80,11 +65,11 @@ bool operator!=(const ImageId& lhs, const ImageId& rhs); bool operator<(const ImageId& lhs, const ImageId& rhs); namespace std { -template<> +template <> struct hash { - size_t operator()(const ImageId& imageId) const noexcept { - return (hashes::hash()(imageId.filePath()) ^ hash()(imageId.page()) << 1); - } + size_t operator()(const ImageId& imageId) const noexcept { + return (hashes::hash()(imageId.filePath()) ^ hash()(imageId.page()) << 1); + } }; } // namespace std diff --git a/ImageInfo.cpp b/ImageInfo.cpp index 7c4e01e90..b7134c2be 100644 --- a/ImageInfo.cpp +++ b/ImageInfo.cpp @@ -18,17 +18,15 @@ #include "ImageInfo.h" -ImageInfo::ImageInfo() : m_numSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) { -} +ImageInfo::ImageInfo() : m_numSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) {} ImageInfo::ImageInfo(const ImageId& id, const ImageMetadata& metadata, int num_sub_pages, bool left_half_removed, bool right_half_removed) - : m_id(id), - m_metadata(metadata), - m_numSubPages(num_sub_pages), - m_leftHalfRemoved(left_half_removed), - m_rightHalfRemoved(right_half_removed) { -} + : m_id(id), + m_metadata(metadata), + m_numSubPages(num_sub_pages), + m_leftHalfRemoved(left_half_removed), + m_rightHalfRemoved(right_half_removed) {} diff --git a/ImageInfo.h b/ImageInfo.h index a99a61fc4..bff07a37b 100644 --- a/ImageInfo.h +++ b/ImageInfo.h @@ -28,42 +28,32 @@ * ProjectPages doesn't operate with ImageInfo objects, but with PageInfo ones. */ class ImageInfo { - // Member-wise copying is OK. -public: - ImageInfo(); + // Member-wise copying is OK. + public: + ImageInfo(); - ImageInfo(const ImageId& id, - const ImageMetadata& metadata, - int num_sub_pages, - bool left_page_removed, - bool right_page_removed); + ImageInfo(const ImageId& id, + const ImageMetadata& metadata, + int num_sub_pages, + bool left_page_removed, + bool right_page_removed); - const ImageId& id() const { - return m_id; - } + const ImageId& id() const { return m_id; } - const ImageMetadata& metadata() const { - return m_metadata; - } + const ImageMetadata& metadata() const { return m_metadata; } - int numSubPages() const { - return m_numSubPages; - } + int numSubPages() const { return m_numSubPages; } - bool leftHalfRemoved() const { - return m_leftHalfRemoved; - } + bool leftHalfRemoved() const { return m_leftHalfRemoved; } - bool rightHalfRemoved() const { - return m_rightHalfRemoved; - } + bool rightHalfRemoved() const { return m_rightHalfRemoved; } -private: - ImageId m_id; - ImageMetadata m_metadata; - int m_numSubPages; // 1 or 2 - bool m_leftHalfRemoved; // Both can't be true, and if one is true, - bool m_rightHalfRemoved; // then m_numSubPages is 1. + private: + ImageId m_id; + ImageMetadata m_metadata; + int m_numSubPages; // 1 or 2 + bool m_leftHalfRemoved; // Both can't be true, and if one is true, + bool m_rightHalfRemoved; // then m_numSubPages is 1. }; diff --git a/ImageLoader.cpp b/ImageLoader.cpp index a5581355a..4e952766e 100644 --- a/ImageLoader.cpp +++ b/ImageLoader.cpp @@ -17,37 +17,37 @@ */ #include "ImageLoader.h" -#include "TiffReader.h" -#include "ImageId.h" -#include #include +#include #include +#include "ImageId.h" +#include "TiffReader.h" QImage ImageLoader::load(const ImageId& image_id) { - return load(image_id.filePath(), image_id.zeroBasedPage()); + return load(image_id.filePath(), image_id.zeroBasedPage()); } QImage ImageLoader::load(const QString& file_path, const int page_num) { - QFile file(file_path); - if (!file.open(QIODevice::ReadOnly)) { - return QImage(); - } + QFile file(file_path); + if (!file.open(QIODevice::ReadOnly)) { + return QImage(); + } - return load(file, page_num); + return load(file, page_num); } QImage ImageLoader::load(QIODevice& io_dev, const int page_num) { - if (TiffReader::canRead(io_dev)) { - return TiffReader::readImage(io_dev, page_num); - } + if (TiffReader::canRead(io_dev)) { + return TiffReader::readImage(io_dev, page_num); + } - if (page_num != 0) { - // Qt can only load the first page of multi-page images. - return QImage(); - } + if (page_num != 0) { + // Qt can only load the first page of multi-page images. + return QImage(); + } - QImage image; - QImageReader(&io_dev).read(&image); + QImage image; + QImageReader(&io_dev).read(&image); - return image; + return image; } diff --git a/ImageLoader.h b/ImageLoader.h index 76445ab34..6ee09e117 100644 --- a/ImageLoader.h +++ b/ImageLoader.h @@ -25,12 +25,12 @@ class QString; class QIODevice; class ImageLoader { -public: - static QImage load(const QString& file_path, int page_num = 0); + public: + static QImage load(const QString& file_path, int page_num = 0); - static QImage load(const ImageId& image_id); + static QImage load(const ImageId& image_id); - static QImage load(QIODevice& io_dev, int page_num); + static QImage load(QIODevice& io_dev, int page_num); }; diff --git a/ImageMetadata.cpp b/ImageMetadata.cpp index f2a10d34b..f94ed6f00 100644 --- a/ImageMetadata.cpp +++ b/ImageMetadata.cpp @@ -22,51 +22,51 @@ using namespace imageproc::constants; bool ImageMetadata::operator==(const ImageMetadata& other) const { - if (m_size != other.m_size) { - return false; - } else if (m_dpi.isNull() && other.m_dpi.isNull()) { - return true; - } else { - return m_dpi == other.m_dpi; - } + if (m_size != other.m_size) { + return false; + } else if (m_dpi.isNull() && other.m_dpi.isNull()) { + return true; + } else { + return m_dpi == other.m_dpi; + } } bool ImageMetadata::isDpiOK() const { - return horizontalDpiStatus() != DPI_UNDEFINED && verticalDpiStatus() != DPI_UNDEFINED; + return horizontalDpiStatus() != DPI_UNDEFINED && verticalDpiStatus() != DPI_UNDEFINED; } ImageMetadata::DpiStatus ImageMetadata::horizontalDpiStatus() const { - return dpiStatus(m_size.width(), m_dpi.horizontal()); + return dpiStatus(m_size.width(), m_dpi.horizontal()); } ImageMetadata::DpiStatus ImageMetadata::verticalDpiStatus() const { - return dpiStatus(m_size.height(), m_dpi.vertical()); + return dpiStatus(m_size.height(), m_dpi.vertical()); } ImageMetadata::DpiStatus ImageMetadata::dpiStatus(int pixel_size, int dpi) { - if (dpi <= 1) { - return DPI_UNDEFINED; - } + if (dpi <= 1) { + return DPI_UNDEFINED; + } - if (dpi < 150) { - return DPI_TOO_SMALL; - } + if (dpi < 150) { + return DPI_TOO_SMALL; + } - if (dpi > 9999) { - return DPI_TOO_LARGE; - } + if (dpi > 9999) { + return DPI_TOO_LARGE; + } - const double mm = INCH2MM * pixel_size / dpi; - if (mm > 500) { - // This may indicate we are working with very large printed materials, - // but most likely it indicates the DPI is wrong (too low). - // DPIs that are too low may easily cause crashes due to out of memory - // conditions. The memory consumption is proportional to: - // (real_hor_dpi / provided_hor_dpi) * (real_vert_dpi / provided_vert_dpi). - // For example, if the real DPI is 600x600 but 200x200 is specified, - // memory consumption is increased 9 times. - return DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE; - } + const double mm = INCH2MM * pixel_size / dpi; + if (mm > 500) { + // This may indicate we are working with very large printed materials, + // but most likely it indicates the DPI is wrong (too low). + // DPIs that are too low may easily cause crashes due to out of memory + // conditions. The memory consumption is proportional to: + // (real_hor_dpi / provided_hor_dpi) * (real_vert_dpi / provided_vert_dpi). + // For example, if the real DPI is 600x600 but 200x200 is specified, + // memory consumption is increased 9 times. + return DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE; + } - return DPI_OK; + return DPI_OK; } diff --git a/ImageMetadata.h b/ImageMetadata.h index d30df083a..6d1a2b9e3 100644 --- a/ImageMetadata.h +++ b/ImageMetadata.h @@ -23,48 +23,37 @@ #include "Dpi.h" class ImageMetadata { - // Member-wise copying is OK. -public: - enum DpiStatus { DPI_OK, DPI_UNDEFINED, DPI_TOO_LARGE, DPI_TOO_SMALL, DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE }; + // Member-wise copying is OK. + public: + enum DpiStatus { DPI_OK, DPI_UNDEFINED, DPI_TOO_LARGE, DPI_TOO_SMALL, DPI_TOO_SMALL_FOR_THIS_PIXEL_SIZE }; - ImageMetadata() = default; + ImageMetadata() = default; - ImageMetadata(QSize size, Dpi dpi) : m_size(size), m_dpi(dpi) { - } + ImageMetadata(QSize size, Dpi dpi) : m_size(size), m_dpi(dpi) {} - const QSize& size() const { - return m_size; - } + const QSize& size() const { return m_size; } - void setSize(const QSize& size) { - m_size = size; - } + void setSize(const QSize& size) { m_size = size; } - const Dpi& dpi() const { - return m_dpi; - } + const Dpi& dpi() const { return m_dpi; } - void setDpi(const Dpi& dpi) { - m_dpi = dpi; - } + void setDpi(const Dpi& dpi) { m_dpi = dpi; } - bool isDpiOK() const; + bool isDpiOK() const; - DpiStatus horizontalDpiStatus() const; + DpiStatus horizontalDpiStatus() const; - DpiStatus verticalDpiStatus() const; + DpiStatus verticalDpiStatus() const; - bool operator==(const ImageMetadata& other) const; + bool operator==(const ImageMetadata& other) const; - bool operator!=(const ImageMetadata& other) const { - return !(*this == other); - } + bool operator!=(const ImageMetadata& other) const { return !(*this == other); } -private: - static DpiStatus dpiStatus(int pixel_size, int dpi); + private: + static DpiStatus dpiStatus(int pixel_size, int dpi); - QSize m_size; - Dpi m_dpi; + QSize m_size; + Dpi m_dpi; }; diff --git a/ImageMetadataLoader.cpp b/ImageMetadataLoader.cpp index e99937076..f034aae4c 100644 --- a/ImageMetadataLoader.cpp +++ b/ImageMetadataLoader.cpp @@ -17,37 +17,37 @@ */ #include "ImageMetadataLoader.h" -#include "ImageMetadata.h" -#include -#include #include +#include +#include +#include "ImageMetadata.h" ImageMetadataLoader::LoaderList ImageMetadataLoader::m_sLoaders; void ImageMetadataLoader::registerLoader(intrusive_ptr loader) { - m_sLoaders.push_back(std::move(loader)); + m_sLoaders.push_back(std::move(loader)); } ImageMetadataLoader::Status ImageMetadataLoader::loadImpl(QIODevice& io_device, const VirtualFunction& out) { - auto it(m_sLoaders.begin()); - const auto end(m_sLoaders.end()); - for (; it != end; ++it) { - const Status status = (*it)->loadMetadata(io_device, out); - if (status != FORMAT_NOT_RECOGNIZED) { - return status; - } + auto it(m_sLoaders.begin()); + const auto end(m_sLoaders.end()); + for (; it != end; ++it) { + const Status status = (*it)->loadMetadata(io_device, out); + if (status != FORMAT_NOT_RECOGNIZED) { + return status; } + } - return FORMAT_NOT_RECOGNIZED; + return FORMAT_NOT_RECOGNIZED; } ImageMetadataLoader::Status ImageMetadataLoader::loadImpl(const QString& file_path, const VirtualFunction& out) { - QFile file(file_path); - if (!file.open(QIODevice::ReadOnly)) { - return GENERIC_ERROR; - } + QFile file(file_path); + if (!file.open(QIODevice::ReadOnly)) { + return GENERIC_ERROR; + } - return loadImpl(file, out); + return loadImpl(file, out); } diff --git a/ImageMetadataLoader.h b/ImageMetadataLoader.h index e6edb087b..75107721b 100644 --- a/ImageMetadataLoader.h +++ b/ImageMetadataLoader.h @@ -19,74 +19,74 @@ #ifndef IMAGEMETADATALOADER_H_ #define IMAGEMETADATALOADER_H_ +#include #include "VirtualFunction.h" -#include "ref_countable.h" #include "intrusive_ptr.h" -#include +#include "ref_countable.h" class QString; class QIODevice; class ImageMetadata; class ImageMetadataLoader : public ref_countable { -public: - enum Status { - LOADED, /**< Loaded successfully */ - NO_IMAGES, /**< File contained no images. */ - FORMAT_NOT_RECOGNIZED, /**< File format not recognized. */ - GENERIC_ERROR /**< Some other error has occured. */ - }; - - /** - * \brief Registers a loader for a particular image format. - * - * This function may not be called before main() or after additional - * threads have been created. - */ - static void registerLoader(intrusive_ptr loader); - - template - static Status load(QIODevice& io_device, OutFunc out); - - template - static Status load(const QString& file_path, OutFunc out); - -protected: - ~ImageMetadataLoader() override = default; - - /** - * \brief Loads metadata from a particular image format. - * - * This function must be reentrant, as it may be called from multiple - * threads at the same time. - * - * \param io_device The I/O device to read from. Usually a QFile. - * In case FORMAT_NO_RECOGNIZED is returned, the implementation - * must leave \p io_device in its original state. - * \param out A callback functional object that will be called to handle - * the image metadata. If there are multiple images (pages) in - * the file, this object will be called multiple times. - */ - virtual Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) = 0; - -private: - static Status loadImpl(QIODevice& io_device, const VirtualFunction& out); - - static Status loadImpl(const QString& file_path, const VirtualFunction& out); - - typedef std::vector> LoaderList; - static LoaderList m_sLoaders; + public: + enum Status { + LOADED, /**< Loaded successfully */ + NO_IMAGES, /**< File contained no images. */ + FORMAT_NOT_RECOGNIZED, /**< File format not recognized. */ + GENERIC_ERROR /**< Some other error has occured. */ + }; + + /** + * \brief Registers a loader for a particular image format. + * + * This function may not be called before main() or after additional + * threads have been created. + */ + static void registerLoader(intrusive_ptr loader); + + template + static Status load(QIODevice& io_device, OutFunc out); + + template + static Status load(const QString& file_path, OutFunc out); + + protected: + ~ImageMetadataLoader() override = default; + + /** + * \brief Loads metadata from a particular image format. + * + * This function must be reentrant, as it may be called from multiple + * threads at the same time. + * + * \param io_device The I/O device to read from. Usually a QFile. + * In case FORMAT_NO_RECOGNIZED is returned, the implementation + * must leave \p io_device in its original state. + * \param out A callback functional object that will be called to handle + * the image metadata. If there are multiple images (pages) in + * the file, this object will be called multiple times. + */ + virtual Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) = 0; + + private: + static Status loadImpl(QIODevice& io_device, const VirtualFunction& out); + + static Status loadImpl(const QString& file_path, const VirtualFunction& out); + + typedef std::vector> LoaderList; + static LoaderList m_sLoaders; }; -template +template ImageMetadataLoader::Status ImageMetadataLoader::load(QIODevice& io_device, Callable out) { - return loadImpl(io_device, ProxyFunction(out)); + return loadImpl(io_device, ProxyFunction(out)); } -template +template ImageMetadataLoader::Status ImageMetadataLoader::load(const QString& file_path, Callable out) { - return loadImpl(file_path, ProxyFunction(out)); + return loadImpl(file_path, ProxyFunction(out)); } #endif // ifndef IMAGEMETADATALOADER_H_ diff --git a/ImagePixmapUnion.h b/ImagePixmapUnion.h index 4c13625ed..1db4b6038 100644 --- a/ImagePixmapUnion.h +++ b/ImagePixmapUnion.h @@ -23,31 +23,23 @@ #include class ImagePixmapUnion { - // Member-wise copying is OK. -public: - ImagePixmapUnion() = default; + // Member-wise copying is OK. + public: + ImagePixmapUnion() = default; - ImagePixmapUnion(const QImage& image) : m_image(image) { - } + ImagePixmapUnion(const QImage& image) : m_image(image) {} - ImagePixmapUnion(const QPixmap& pixmap) : m_pixmap(pixmap) { - } + ImagePixmapUnion(const QPixmap& pixmap) : m_pixmap(pixmap) {} - const QImage& image() const { - return m_image; - } + const QImage& image() const { return m_image; } - const QPixmap& pixmap() const { - return m_pixmap; - } + const QPixmap& pixmap() const { return m_pixmap; } - bool isNull() const { - return m_image.isNull() && m_pixmap.isNull(); - } + bool isNull() const { return m_image.isNull() && m_pixmap.isNull(); } -private: - QImage m_image; - QPixmap m_pixmap; + private: + QImage m_image; + QPixmap m_pixmap; }; diff --git a/ImagePresentation.h b/ImagePresentation.h index 048f5f50e..333f38719 100644 --- a/ImagePresentation.h +++ b/ImagePresentation.h @@ -19,9 +19,9 @@ #ifndef IMAGE_PRESENTATION_H_ #define IMAGE_PRESENTATION_H_ -#include #include #include +#include /** * Image presentation consists of 3 components: @@ -34,44 +34,30 @@ * or extra controls. */ class ImagePresentation { - // Member-wise copying is OK. -public: - ImagePresentation(const QTransform& xform, const QPolygonF& crop_area) - : m_xform(xform), m_cropArea(crop_area), m_displayArea(crop_area.boundingRect()) { - } - - ImagePresentation(const QTransform& xform, const QPolygonF& crop_area, const QRectF& display_area) - : m_xform(xform), m_cropArea(crop_area), m_displayArea(display_area) { - } - - const QTransform& transform() const { - return m_xform; - } - - void setTransform(const QTransform& xform) { - m_xform = xform; - } - - const QPolygonF& cropArea() const { - return m_cropArea; - } - - void setCropArea(const QPolygonF& crop_area) { - m_cropArea = crop_area; - } - - const QRectF& displayArea() const { - return m_displayArea; - } - - void setDisplayArea(const QRectF& display_area) { - m_displayArea = display_area; - } - -private: - QTransform m_xform; - QPolygonF m_cropArea; - QRectF m_displayArea; + // Member-wise copying is OK. + public: + ImagePresentation(const QTransform& xform, const QPolygonF& crop_area) + : m_xform(xform), m_cropArea(crop_area), m_displayArea(crop_area.boundingRect()) {} + + ImagePresentation(const QTransform& xform, const QPolygonF& crop_area, const QRectF& display_area) + : m_xform(xform), m_cropArea(crop_area), m_displayArea(display_area) {} + + const QTransform& transform() const { return m_xform; } + + void setTransform(const QTransform& xform) { m_xform = xform; } + + const QPolygonF& cropArea() const { return m_cropArea; } + + void setCropArea(const QPolygonF& crop_area) { m_cropArea = crop_area; } + + const QRectF& displayArea() const { return m_displayArea; } + + void setDisplayArea(const QRectF& display_area) { m_displayArea = display_area; } + + private: + QTransform m_xform; + QPolygonF m_cropArea; + QRectF m_displayArea; }; diff --git a/ImageSettings.cpp b/ImageSettings.cpp index df8451819..b7efc548c 100644 --- a/ImageSettings.cpp +++ b/ImageSettings.cpp @@ -7,60 +7,68 @@ using namespace imageproc; void ImageSettings::clear() { - QMutexLocker locker(&mutex); - perPageParams.clear(); + QMutexLocker locker(&m_mutex); + m_perPageParams.clear(); } void ImageSettings::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&mutex); - PerPageParams newParams; - for (const auto& kv : perPageParams) { - const RelinkablePath oldPath(kv.first.imageId().filePath(), RelinkablePath::File); - PageId newPageId(kv.first); - newPageId.imageId().setFilePath(relinker.substitutionPathFor(oldPath)); - newParams.insert(PerPageParams::value_type(newPageId, kv.second)); - } - - perPageParams.swap(newParams); + QMutexLocker locker(&m_mutex); + PerPageParams newParams; + for (const auto& kv : m_perPageParams) { + const RelinkablePath oldPath(kv.first.imageId().filePath(), RelinkablePath::File); + PageId newPageId(kv.first); + newPageId.imageId().setFilePath(relinker.substitutionPathFor(oldPath)); + newParams.insert(PerPageParams::value_type(newPageId, kv.second)); + } + + m_perPageParams.swap(newParams); } void ImageSettings::setPageParams(const PageId& page_id, const PageParams& params) { - QMutexLocker locker(&mutex); - Utils::mapSetValue(perPageParams, page_id, params); + QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageParams, page_id, params); } std::unique_ptr ImageSettings::getPageParams(const PageId& page_id) const { - QMutexLocker locker(&mutex); - const auto it(perPageParams.find(page_id)); - if (it != perPageParams.end()) { - return std::make_unique(it->second); - } else { - return nullptr; - } + QMutexLocker locker(&m_mutex); + const auto it(m_perPageParams.find(page_id)); + if (it != m_perPageParams.end()) { + return std::make_unique(it->second); + } else { + return nullptr; + } } /*=============================== ImageSettings::Params ==================================*/ -ImageSettings::PageParams::PageParams() : bwThreshold(0) { -} +ImageSettings::PageParams::PageParams() : m_bwThreshold(0), m_blackOnWhite(true) {} -ImageSettings::PageParams::PageParams(const BinaryThreshold& bwThreshold) : bwThreshold(bwThreshold) { -} +ImageSettings::PageParams::PageParams(const BinaryThreshold& bwThreshold, bool blackOnWhite) + : m_bwThreshold(bwThreshold), m_blackOnWhite(blackOnWhite) {} -ImageSettings::PageParams::PageParams(const QDomElement& el) : bwThreshold(el.attribute("bwThreshold").toInt()) { -} +ImageSettings::PageParams::PageParams(const QDomElement& el) + : m_bwThreshold(el.attribute("bwThreshold").toInt()), m_blackOnWhite(el.attribute("blackOnWhite") == "1") {} QDomElement ImageSettings::PageParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("bwThreshold", bwThreshold); + QDomElement el(doc.createElement(name)); + el.setAttribute("bwThreshold", m_bwThreshold); + el.setAttribute("blackOnWhite", m_blackOnWhite ? "1" : "0"); - return el; + return el; } const BinaryThreshold& ImageSettings::PageParams::getBwThreshold() const { - return bwThreshold; + return m_bwThreshold; } void ImageSettings::PageParams::setBwThreshold(const BinaryThreshold& bwThreshold) { - PageParams::bwThreshold = bwThreshold; -} \ No newline at end of file + PageParams::m_bwThreshold = bwThreshold; +} + +bool ImageSettings::PageParams::isBlackOnWhite() const { + return m_blackOnWhite; +} + +void ImageSettings::PageParams::setBlackOnWhite(bool blackOnWhite) { + PageParams::m_blackOnWhite = blackOnWhite; +} diff --git a/ImageSettings.h b/ImageSettings.h index d9ac44ad2..53038892c 100644 --- a/ImageSettings.h +++ b/ImageSettings.h @@ -2,54 +2,60 @@ #ifndef SCANTAILOR_IMAGESETTINGS_H #define SCANTAILOR_IMAGESETTINGS_H - -#include -#include +#include #include -#include +#include #include +#include #include +#include #include "PageId.h" class AbstractRelinker; class ImageSettings : public ref_countable { -public: - class PageParams { - public: - PageParams(); + DECLARE_NON_COPYABLE(ImageSettings) + public: + class PageParams { + public: + PageParams(); + + PageParams(const imageproc::BinaryThreshold& bwThreshold, bool blackOnWhite); + + explicit PageParams(const QDomElement& el); - explicit PageParams(const imageproc::BinaryThreshold& bwThreshold); + QDomElement toXml(QDomDocument& doc, const QString& name) const; - explicit PageParams(const QDomElement& el); + const imageproc::BinaryThreshold& getBwThreshold() const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + void setBwThreshold(const imageproc::BinaryThreshold& bwThreshold); - const imageproc::BinaryThreshold& getBwThreshold() const; + bool isBlackOnWhite() const; - void setBwThreshold(const imageproc::BinaryThreshold& bwThreshold); + void setBlackOnWhite(bool blackOnWhite); - private: - imageproc::BinaryThreshold bwThreshold; - }; + private: + imageproc::BinaryThreshold m_bwThreshold; + bool m_blackOnWhite; + }; - ImageSettings() = default; + ImageSettings() = default; - ~ImageSettings() override = default; + ~ImageSettings() override = default; - void clear(); + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - void setPageParams(const PageId& page_id, const PageParams& params); + void setPageParams(const PageId& page_id, const PageParams& params); - std::unique_ptr getPageParams(const PageId& page_id) const; + std::unique_ptr getPageParams(const PageId& page_id) const; -private: - typedef std::unordered_map PerPageParams; + private: + typedef std::unordered_map PerPageParams; - mutable QMutex mutex; - PerPageParams perPageParams; + mutable QMutex m_mutex; + PerPageParams m_perPageParams; }; diff --git a/ImageTransformation.cpp b/ImageTransformation.cpp index b0945b601..24fb96bcf 100644 --- a/ImageTransformation.cpp +++ b/ImageTransformation.cpp @@ -19,186 +19,186 @@ #include "ImageTransformation.h" ImageTransformation::ImageTransformation(const QRectF& orig_image_rect, const Dpi& orig_dpi) - : m_postRotation(0.0), m_origRect(orig_image_rect), m_resultingRect(orig_image_rect), m_origDpi(orig_dpi) { - preScaleToEqualizeDpi(); + : m_postRotation(0.0), m_origRect(orig_image_rect), m_resultingRect(orig_image_rect), m_origDpi(orig_dpi) { + preScaleToEqualizeDpi(); } ImageTransformation::~ImageTransformation() = default; void ImageTransformation::preScaleToDpi(const Dpi& dpi) { - if (m_origDpi.isNull() || dpi.isNull()) { - return; - } + if (m_origDpi.isNull() || dpi.isNull()) { + return; + } - m_preScaledDpi = dpi; + m_preScaledDpi = dpi; - const double xscale = (double) dpi.horizontal() / m_origDpi.horizontal(); - const double yscale = (double) dpi.vertical() / m_origDpi.vertical(); + const double xscale = (double) dpi.horizontal() / m_origDpi.horizontal(); + const double yscale = (double) dpi.vertical() / m_origDpi.vertical(); - const QSizeF new_pre_scaled_image_size(m_origRect.width() * xscale, m_origRect.height() * yscale); + const QSizeF new_pre_scaled_image_size(m_origRect.width() * xscale, m_origRect.height() * yscale); - // Undo's for the specified steps. - const QTransform undo21(m_preRotateXform.inverted() * m_preScaleXform.inverted()); - const QTransform undo4321(m_postRotateXform.inverted() * m_preCropXform.inverted() * undo21); + // Undo's for the specified steps. + const QTransform undo21(m_preRotateXform.inverted() * m_preScaleXform.inverted()); + const QTransform undo4321(m_postRotateXform.inverted() * m_preCropXform.inverted() * undo21); - // Update transform #1: pre-scale. - m_preScaleXform.reset(); - m_preScaleXform.scale(xscale, yscale); + // Update transform #1: pre-scale. + m_preScaleXform.reset(); + m_preScaleXform.scale(xscale, yscale); - // Update transform #2: pre-rotate. - m_preRotateXform = m_preRotation.transform(new_pre_scaled_image_size); + // Update transform #2: pre-rotate. + m_preRotateXform = m_preRotation.transform(new_pre_scaled_image_size); - // Update transform #3: pre-crop. - const QTransform redo12(m_preScaleXform * m_preRotateXform); - m_preCropArea = (undo21 * redo12).map(m_preCropArea); - m_preCropXform = calcCropXform(m_preCropArea); + // Update transform #3: pre-crop. + const QTransform redo12(m_preScaleXform * m_preRotateXform); + m_preCropArea = (undo21 * redo12).map(m_preCropArea); + m_preCropXform = calcCropXform(m_preCropArea); - // Update transform #4: post-rotate. - m_postRotateXform = calcPostRotateXform(m_postRotation); + // Update transform #4: post-rotate. + m_postRotateXform = calcPostRotateXform(m_postRotation); - // Update transform #5: post-crop. - const QTransform redo1234(redo12 * m_preCropXform * m_postRotateXform); - m_postCropArea = (undo4321 * redo1234).map(m_postCropArea); - m_postCropXform = calcCropXform(m_postCropArea); - // Update transform #6: post-scale. - m_postScaleXform = calcPostScaleXform(m_postScaledDpi); + // Update transform #5: post-crop. + const QTransform redo1234(redo12 * m_preCropXform * m_postRotateXform); + m_postCropArea = (undo4321 * redo1234).map(m_postCropArea); + m_postCropXform = calcCropXform(m_postCropArea); + // Update transform #6: post-scale. + m_postScaleXform = calcPostScaleXform(m_postScaledDpi); - update(); + update(); } // ImageTransformation::preScaleToDpi void ImageTransformation::preScaleToEqualizeDpi() { - const int min_dpi = std::min(m_origDpi.horizontal(), m_origDpi.vertical()); - preScaleToDpi(Dpi(min_dpi, min_dpi)); + const int min_dpi = std::min(m_origDpi.horizontal(), m_origDpi.vertical()); + preScaleToDpi(Dpi(min_dpi, min_dpi)); } void ImageTransformation::setPreRotation(const OrthogonalRotation rotation) { - m_preRotation = rotation; - m_preRotateXform = m_preRotation.transform(m_origRect.size()); - resetPreCropArea(); - resetPostRotation(); - resetPostCrop(); - resetPostScale(); - update(); + m_preRotation = rotation; + m_preRotateXform = m_preRotation.transform(m_origRect.size()); + resetPreCropArea(); + resetPostRotation(); + resetPostCrop(); + resetPostScale(); + update(); } void ImageTransformation::setPreCropArea(const QPolygonF& area) { - m_preCropArea = area; - m_preCropXform = calcCropXform(area); - resetPostRotation(); - resetPostCrop(); - resetPostScale(); - update(); + m_preCropArea = area; + m_preCropXform = calcCropXform(area); + resetPostRotation(); + resetPostCrop(); + resetPostScale(); + update(); } void ImageTransformation::setPostRotation(const double degrees) { - m_postRotateXform = calcPostRotateXform(degrees); - m_postRotation = degrees; - resetPostCrop(); - resetPostScale(); - update(); + m_postRotateXform = calcPostRotateXform(degrees); + m_postRotation = degrees; + resetPostCrop(); + resetPostScale(); + update(); } void ImageTransformation::setPostCropArea(const QPolygonF& area) { - m_postCropArea = area; - m_postCropXform = calcCropXform(area); - resetPostScale(); - update(); + m_postCropArea = area; + m_postCropXform = calcCropXform(area); + resetPostScale(); + update(); } void ImageTransformation::postScaleToDpi(const Dpi& dpi) { - m_postScaledDpi = dpi; - m_postScaleXform = calcPostScaleXform(dpi); - update(); + m_postScaledDpi = dpi; + m_postScaleXform = calcPostScaleXform(dpi); + update(); } QTransform ImageTransformation::calcCropXform(const QPolygonF& area) { - const QRectF bounds(area.boundingRect()); - QTransform xform; - xform.translate(-bounds.x(), -bounds.y()); + const QRectF bounds(area.boundingRect()); + QTransform xform; + xform.translate(-bounds.x(), -bounds.y()); - return xform; + return xform; } QTransform ImageTransformation::calcPostRotateXform(const double degrees) { - QTransform xform; - if (degrees != 0.0) { - const QPointF origin(m_preCropArea.boundingRect().center()); - xform.translate(-origin.x(), -origin.y()); - xform *= QTransform().rotate(degrees); - xform *= QTransform().translate(origin.x(), origin.y()); - - // Calculate size changes. - const QPolygonF pre_rotate_poly(m_preCropXform.map(m_preCropArea)); - const QRectF pre_rotate_rect(pre_rotate_poly.boundingRect()); - const QPolygonF post_rotate_poly(xform.map(pre_rotate_poly)); - const QRectF post_rotate_rect(post_rotate_poly.boundingRect()); - - xform *= QTransform().translate(pre_rotate_rect.left() - post_rotate_rect.left(), - pre_rotate_rect.top() - post_rotate_rect.top()); - } - - return xform; + QTransform xform; + if (degrees != 0.0) { + const QPointF origin(m_preCropArea.boundingRect().center()); + xform.translate(-origin.x(), -origin.y()); + xform *= QTransform().rotate(degrees); + xform *= QTransform().translate(origin.x(), origin.y()); + + // Calculate size changes. + const QPolygonF pre_rotate_poly(m_preCropXform.map(m_preCropArea)); + const QRectF pre_rotate_rect(pre_rotate_poly.boundingRect()); + const QPolygonF post_rotate_poly(xform.map(pre_rotate_poly)); + const QRectF post_rotate_rect(post_rotate_poly.boundingRect()); + + xform *= QTransform().translate(pre_rotate_rect.left() - post_rotate_rect.left(), + pre_rotate_rect.top() - post_rotate_rect.top()); + } + + return xform; } QTransform ImageTransformation::calcPostScaleXform(const Dpi& target_dpi) { - if (target_dpi.isNull()) { - return QTransform(); - } + if (target_dpi.isNull()) { + return QTransform(); + } - // We are going to measure the effective DPI after the previous transforms. - // Normally m_preScaledDpi would be symmetric, so we could just - // use that, but just in case ... + // We are going to measure the effective DPI after the previous transforms. + // Normally m_preScaledDpi would be symmetric, so we could just + // use that, but just in case ... - const QTransform to_orig(m_postScaleXform * m_transform.inverted()); - // IMPORTANT: in the above line we assume post-scale is the last transform. + const QTransform to_orig(m_postScaleXform * m_transform.inverted()); + // IMPORTANT: in the above line we assume post-scale is the last transform. - const QLineF hor_unit(QPointF(0, 0), QPointF(1, 0)); - const QLineF vert_unit(QPointF(0, 0), QPointF(0, 1)); - const QLineF orig_hor_unit(to_orig.map(hor_unit)); - const QLineF orig_vert_unit(to_orig.map(vert_unit)); + const QLineF hor_unit(QPointF(0, 0), QPointF(1, 0)); + const QLineF vert_unit(QPointF(0, 0), QPointF(0, 1)); + const QLineF orig_hor_unit(to_orig.map(hor_unit)); + const QLineF orig_vert_unit(to_orig.map(vert_unit)); - const double xscale = target_dpi.horizontal() * orig_hor_unit.length() / m_origDpi.horizontal(); - const double yscale = target_dpi.vertical() * orig_vert_unit.length() / m_origDpi.vertical(); - QTransform xform; - xform.scale(xscale, yscale); + const double xscale = target_dpi.horizontal() * orig_hor_unit.length() / m_origDpi.horizontal(); + const double yscale = target_dpi.vertical() * orig_vert_unit.length() / m_origDpi.vertical(); + QTransform xform; + xform.scale(xscale, yscale); - return xform; + return xform; } void ImageTransformation::resetPreCropArea() { - m_preCropArea.clear(); - m_preCropXform.reset(); + m_preCropArea.clear(); + m_preCropXform.reset(); } void ImageTransformation::resetPostRotation() { - m_postRotation = 0.0; - m_postRotateXform.reset(); + m_postRotation = 0.0; + m_postRotateXform.reset(); } void ImageTransformation::resetPostCrop() { - m_postCropArea.clear(); - m_postCropXform.reset(); + m_postCropArea.clear(); + m_postCropXform.reset(); } void ImageTransformation::resetPostScale() { - m_postScaledDpi = Dpi(); - m_postScaleXform.reset(); + m_postScaledDpi = Dpi(); + m_postScaleXform.reset(); } void ImageTransformation::update() { - const QTransform pre_scale_then_pre_rotate(m_preScaleXform * m_preRotateXform); // 12 - const QTransform pre_crop_then_post_rotate(m_preCropXform * m_postRotateXform); // 34 - const QTransform post_crop_then_post_scale(m_postCropXform * m_postScaleXform); // 56 - const QTransform pre_crop_and_further(pre_crop_then_post_rotate * post_crop_then_post_scale); // 3456 - m_transform = pre_scale_then_pre_rotate * pre_crop_and_further; - m_invTransform = m_transform.inverted(); - if (m_preCropArea.empty()) { - m_preCropArea = pre_scale_then_pre_rotate.map(m_origRect); - } - if (m_postCropArea.empty()) { - m_postCropArea = pre_crop_then_post_rotate.map(m_preCropArea); - } - m_resultingPreCropArea = pre_crop_and_further.map(m_preCropArea); - m_resultingPostCropArea = post_crop_then_post_scale.map(m_postCropArea); - m_resultingRect = m_resultingPostCropArea.boundingRect(); + const QTransform pre_scale_then_pre_rotate(m_preScaleXform * m_preRotateXform); // 12 + const QTransform pre_crop_then_post_rotate(m_preCropXform * m_postRotateXform); // 34 + const QTransform post_crop_then_post_scale(m_postCropXform * m_postScaleXform); // 56 + const QTransform pre_crop_and_further(pre_crop_then_post_rotate * post_crop_then_post_scale); // 3456 + m_transform = pre_scale_then_pre_rotate * pre_crop_and_further; + m_invTransform = m_transform.inverted(); + if (m_preCropArea.empty()) { + m_preCropArea = pre_scale_then_pre_rotate.map(m_origRect); + } + if (m_postCropArea.empty()) { + m_postCropArea = pre_crop_then_post_rotate.map(m_preCropArea); + } + m_resultingPreCropArea = pre_crop_and_further.map(m_preCropArea); + m_resultingPostCropArea = post_crop_then_post_scale.map(m_postCropArea); + m_resultingRect = m_resultingPostCropArea.boundingRect(); } diff --git a/ImageTransformation.h b/ImageTransformation.h index c5aa8191f..4e1329103 100644 --- a/ImageTransformation.h +++ b/ImageTransformation.h @@ -19,11 +19,11 @@ #ifndef IMAGETRANSFORMATION_H_ #define IMAGETRANSFORMATION_H_ -#include "OrthogonalRotation.h" -#include "Dpi.h" -#include #include #include +#include +#include "Dpi.h" +#include "OrthogonalRotation.h" /** * \brief Provides a transformed view of an image. @@ -60,215 +60,189 @@ * Note that all transformation steps are optional. */ class ImageTransformation { -public: - // Member-wise copying is OK. - - ImageTransformation(const QRectF& orig_image_rect, const Dpi& orig_dpi); - - ~ImageTransformation(); - - /** - * \brief Set the 1st step transformation, recalculating the following ones. - * - * \see \ref transformations Transformations. - */ - void preScaleToDpi(const Dpi& dpi); - - /** - * \brief Set the 1st step transformation, recalculating the following ones. - * - * Suppose the original image DPI is 300x600. This will scale it to - * 300x300, the minimum of two. - * - * \note This transformation is applied automatically on construction. - * - * \see \ref transformations Transformations. - */ - void preScaleToEqualizeDpi(); - - /** - * \brief Get the original image DPI. - */ - const Dpi& origDpi() const { - return m_origDpi; - } - - /** - * \brief Get the target DPI for pre-scaling. - * - * Note that if the original DPI was assymetric, pre-scaling to - * a symmetric DPI will be applied implicitly. - */ - const Dpi& preScaledDpi() const { - return m_preScaledDpi; - } - - /** - * \brief Set the 2nd step transformation, resetting the following ones. - * - * \see \ref transformations Transformations. - */ - void setPreRotation(OrthogonalRotation rotation); - - /** - * \brief Returns the 2nd step rotation. - */ - OrthogonalRotation preRotation() const { - return m_preRotation; - } - - /** - * \brief Set the 3rd step transformation, resetting the following ones. - * - * Providing a null polygon has the same effect as providing a polygon - * that covers the entire image. A crop area that exceedes the image - * is allowed. - * - * \see \ref transformations Transformations. - */ - void setPreCropArea(const QPolygonF& area); - - /** - * \brief Get the effective pre-crop area in pre-rotated coordinates. - * - * If pre-crop area was explicitly set with setPreCropArea(), then - * this function returns it as is. Otherwise, the whole available - * area is returned. - */ - const QPolygonF& preCropArea() const { - return m_preCropArea; - } - - /** - * \brief Returns the pre-crop area after all transformations. - * - * If no pre-crop area was set, the whole image is assumed to be - * the pre-crop area. - */ - const QPolygonF& resultingPreCropArea() const { - return m_resultingPreCropArea; - } - - /** - * \brief Set the 4th step transformation, resetting the following ones. - * - * \see \ref transformations Transformations. - */ - void setPostRotation(double degrees); - - /** - * \brief Returns the 4th step rotation in degrees, as specified. - */ - double postRotation() const { - return m_postRotation; - } - - /** - * \brief Returns the sine of the 4th step rotation angle. - */ - double postRotationSin() const { - return m_postRotateXform.m12(); - } - - /** - * \brief Returns the cosine of the 3rd step rotation angle. - */ - double postRotationCos() const { - return m_postRotateXform.m11(); - } - - /** - * \brief Set the 5th step transformation, resetting the following ones. - */ - void setPostCropArea(const QPolygonF& area); - - /** - * \brief Returns the post-crop area after all transformations. - * - * If no post-crop area was set, the whole image is assumed to be - * the post-crop area. - */ - const QPolygonF& resultingPostCropArea() const { - return m_resultingPostCropArea; - } - - /** - * \brief Set the 6th step transformation. - * - * Passing a null (default constructed) Dpi means "don't apply post-scaling". - */ - void postScaleToDpi(const Dpi& dpi); - - /** - * \brief Returns the transformation matrix from the original - * to resulting image coordinates. - */ - const QTransform& transform() const { - return m_transform; - } - - /** - * \brief Returns the transformation matrix from the resulting - * to original image coordinates. - */ - const QTransform& transformBack() const { - return m_invTransform; - } - - /** - * \brief Returns the original image rectangle, as specified. - */ - const QRectF& origRect() const { - return m_origRect; - } - - /** - * \brief Returns the resulting image rectangle. - * - * The top-left corner of the resulting rectangle is expected - * to be very close to (0, 0), assuming the original rectangle - * had it at (0, 0), but it's not guaranteed to be exactly there. - */ - const QRectF& resultingRect() const { - return m_resultingRect; - } - -private: - QTransform calcCropXform(const QPolygonF& crop_area); - - QTransform calcPostRotateXform(double degrees); - - QTransform calcPostScaleXform(const Dpi& target_dpi); - - void resetPreCropArea(); - - void resetPostRotation(); - - void resetPostCrop(); - - void resetPostScale(); - - void update(); - - QTransform m_preScaleXform; - QTransform m_preRotateXform; - QTransform m_preCropXform; - QTransform m_postRotateXform; - QTransform m_postCropXform; - QTransform m_postScaleXform; - QTransform m_transform; - QTransform m_invTransform; - double m_postRotation; - QRectF m_origRect; - QRectF m_resultingRect; // Managed by update(). - QPolygonF m_preCropArea; - QPolygonF m_resultingPreCropArea; // Managed by update(). - QPolygonF m_postCropArea; - QPolygonF m_resultingPostCropArea; // Managed by update(). - Dpi m_origDpi; - Dpi m_preScaledDpi; // Always set, as preScaleToEqualizeDpi() is called from the constructor. - Dpi m_postScaledDpi; // Default constructed object if no post-scaling. - OrthogonalRotation m_preRotation; + public: + // Member-wise copying is OK. + + ImageTransformation(const QRectF& orig_image_rect, const Dpi& orig_dpi); + + ~ImageTransformation(); + + /** + * \brief Set the 1st step transformation, recalculating the following ones. + * + * \see \ref transformations Transformations. + */ + void preScaleToDpi(const Dpi& dpi); + + /** + * \brief Set the 1st step transformation, recalculating the following ones. + * + * Suppose the original image DPI is 300x600. This will scale it to + * 300x300, the minimum of two. + * + * \note This transformation is applied automatically on construction. + * + * \see \ref transformations Transformations. + */ + void preScaleToEqualizeDpi(); + + /** + * \brief Get the original image DPI. + */ + const Dpi& origDpi() const { return m_origDpi; } + + /** + * \brief Get the target DPI for pre-scaling. + * + * Note that if the original DPI was assymetric, pre-scaling to + * a symmetric DPI will be applied implicitly. + */ + const Dpi& preScaledDpi() const { return m_preScaledDpi; } + + /** + * \brief Set the 2nd step transformation, resetting the following ones. + * + * \see \ref transformations Transformations. + */ + void setPreRotation(OrthogonalRotation rotation); + + /** + * \brief Returns the 2nd step rotation. + */ + OrthogonalRotation preRotation() const { return m_preRotation; } + + /** + * \brief Set the 3rd step transformation, resetting the following ones. + * + * Providing a null polygon has the same effect as providing a polygon + * that covers the entire image. A crop area that exceedes the image + * is allowed. + * + * \see \ref transformations Transformations. + */ + void setPreCropArea(const QPolygonF& area); + + /** + * \brief Get the effective pre-crop area in pre-rotated coordinates. + * + * If pre-crop area was explicitly set with setPreCropArea(), then + * this function returns it as is. Otherwise, the whole available + * area is returned. + */ + const QPolygonF& preCropArea() const { return m_preCropArea; } + + /** + * \brief Returns the pre-crop area after all transformations. + * + * If no pre-crop area was set, the whole image is assumed to be + * the pre-crop area. + */ + const QPolygonF& resultingPreCropArea() const { return m_resultingPreCropArea; } + + /** + * \brief Set the 4th step transformation, resetting the following ones. + * + * \see \ref transformations Transformations. + */ + void setPostRotation(double degrees); + + /** + * \brief Returns the 4th step rotation in degrees, as specified. + */ + double postRotation() const { return m_postRotation; } + + /** + * \brief Returns the sine of the 4th step rotation angle. + */ + double postRotationSin() const { return m_postRotateXform.m12(); } + + /** + * \brief Returns the cosine of the 3rd step rotation angle. + */ + double postRotationCos() const { return m_postRotateXform.m11(); } + + /** + * \brief Set the 5th step transformation, resetting the following ones. + */ + void setPostCropArea(const QPolygonF& area); + + /** + * \brief Returns the post-crop area after all transformations. + * + * If no post-crop area was set, the whole image is assumed to be + * the post-crop area. + */ + const QPolygonF& resultingPostCropArea() const { return m_resultingPostCropArea; } + + /** + * \brief Set the 6th step transformation. + * + * Passing a null (default constructed) Dpi means "don't apply post-scaling". + */ + void postScaleToDpi(const Dpi& dpi); + + /** + * \brief Returns the transformation matrix from the original + * to resulting image coordinates. + */ + const QTransform& transform() const { return m_transform; } + + /** + * \brief Returns the transformation matrix from the resulting + * to original image coordinates. + */ + const QTransform& transformBack() const { return m_invTransform; } + + /** + * \brief Returns the original image rectangle, as specified. + */ + const QRectF& origRect() const { return m_origRect; } + + /** + * \brief Returns the resulting image rectangle. + * + * The top-left corner of the resulting rectangle is expected + * to be very close to (0, 0), assuming the original rectangle + * had it at (0, 0), but it's not guaranteed to be exactly there. + */ + const QRectF& resultingRect() const { return m_resultingRect; } + + private: + QTransform calcCropXform(const QPolygonF& crop_area); + + QTransform calcPostRotateXform(double degrees); + + QTransform calcPostScaleXform(const Dpi& target_dpi); + + void resetPreCropArea(); + + void resetPostRotation(); + + void resetPostCrop(); + + void resetPostScale(); + + void update(); + + QTransform m_preScaleXform; + QTransform m_preRotateXform; + QTransform m_preCropXform; + QTransform m_postRotateXform; + QTransform m_postCropXform; + QTransform m_postScaleXform; + QTransform m_transform; + QTransform m_invTransform; + double m_postRotation; + QRectF m_origRect; + QRectF m_resultingRect; // Managed by update(). + QPolygonF m_preCropArea; + QPolygonF m_resultingPreCropArea; // Managed by update(). + QPolygonF m_postCropArea; + QPolygonF m_resultingPostCropArea; // Managed by update(). + Dpi m_origDpi; + Dpi m_preScaledDpi; // Always set, as preScaleToEqualizeDpi() is called from the constructor. + Dpi m_postScaledDpi; // Default constructed object if no post-scaling. + OrthogonalRotation m_preRotation; }; diff --git a/ImageViewBase.cpp b/ImageViewBase.cpp index ca720cd2f..6863f13a4 100644 --- a/ImageViewBase.cpp +++ b/ImageViewBase.cpp @@ -17,74 +17,66 @@ */ #include "ImageViewBase.h" -#include "ImagePresentation.h" -#include "PixmapRenderer.h" -#include "BackgroundExecutor.h" -#include "Dpm.h" -#include "ScopedIncDec.h" -#include "imageproc/PolygonUtils.h" -#include "imageproc/Transform.h" -#include "OpenGLSupport.h" -#include "ColorSchemeManager.h" -#include "UnitsProvider.h" -#include "Utils.h" #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include #include +#include "BackgroundExecutor.h" +#include "ColorSchemeManager.h" +#include "Dpm.h" +#include "ImagePresentation.h" +#include "OpenGLSupport.h" +#include "PixmapRenderer.h" +#include "ScopedIncDec.h" +#include "UnitsProvider.h" +#include "Utils.h" +#include "imageproc/PolygonUtils.h" +#include "imageproc/Transform.h" using namespace imageproc; class ImageViewBase::HqTransformTask : public AbstractCommand>>, public QObject { - DECLARE_NON_COPYABLE(HqTransformTask) + DECLARE_NON_COPYABLE(HqTransformTask) -public: - HqTransformTask(ImageViewBase* image_view, const QImage& image, const QTransform& xform, const QSize& target_size); + public: + HqTransformTask(ImageViewBase* image_view, const QImage& image, const QTransform& xform, const QSize& target_size); - void cancel() { - m_ptrResult->cancel(); - } + void cancel() { m_result->cancel(); } - const bool isCancelled() const { - return m_ptrResult->isCancelled(); - } + const bool isCancelled() const { return m_result->isCancelled(); } - intrusive_ptr> operator()() override; + intrusive_ptr> operator()() override; -private: - class Result : public AbstractCommand { - public: - explicit Result(ImageViewBase* image_view); + private: + class Result : public AbstractCommand { + public: + explicit Result(ImageViewBase* image_view); - void setData(const QPoint& origin, const QImage& hq_image); + void setData(const QPoint& origin, const QImage& hq_image); - void cancel() { - m_cancelFlag.fetchAndStoreRelaxed(1); - } + void cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } - bool isCancelled() const { - return m_cancelFlag.fetchAndAddRelaxed(0) != 0; - } + bool isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } - void operator()() override; + void operator()() override; - private: - QPointer m_ptrImageView; - QPoint m_origin; - QImage m_hqImage; - mutable QAtomicInt m_cancelFlag; - }; + private: + QPointer m_imageView; + QPoint m_origin; + QImage m_hqImage; + mutable QAtomicInt m_cancelFlag; + }; - intrusive_ptr m_ptrResult; - QImage m_image; - QTransform m_xform; - QSize m_targetSize; + intrusive_ptr m_result; + QImage m_image; + QTransform m_xform; + QSize m_targetSize; }; @@ -95,39 +87,39 @@ class ImageViewBase::HqTransformTask : public AbstractCommandsetAutoFillBackground(false); - - if (QSettings().value("settings/use_3d_acceleration", false) != false) { - if (OpenGLSupport::supported()) { - QGLFormat format; - format.setSampleBuffers(true); - format.setStencil(true); - format.setAlpha(true); - format.setRgba(true); - format.setDepth(false); - - // Most of hardware refuses to work for us with direct rendering enabled. - format.setDirectRendering(false); - - setViewport(new QGLWidget(format)); - } + : m_image(image), + m_virtualImageCropArea(presentation.cropArea()), + m_virtualDisplayArea(presentation.displayArea()), + m_imageToVirtual(presentation.transform()), + m_virtualToImage(presentation.transform().inverted()), + m_lastMaximumViewportSize(maximumViewportSize()), + m_margins(margins), + m_zoom(1.0), + m_transformChangeWatchersActive(0), + m_ignoreScrollEvents(0), + m_ignoreResizeEvents(0), + m_hqTransformEnabled(true), + m_infoProvider(Dpm(m_image)) { + /* For some reason, the default viewport fills background with + * a color different from QPalette::Window at the first show on Windows. + * Here we make it not fill it automatically at all + * doing the work in paintEvent(). + */ + setAttribute(Qt::WA_OpaquePaintEvent); + + /* For some reason, the default viewport fills background with + * a color different from QPalette::Window. Here we make it not + * fill it at all, assuming QMainWindow will do that anyway + * (with the correct color). Note that an attempt to do the same + * to an OpenGL viewport produces "black hole" artefacts. Therefore, + * we do this before setting an OpenGL viewport rather than after. + */ + viewport()->setAutoFillBackground(false); + + if (QSettings().value("settings/use_3d_acceleration", false) != false) { + if (OpenGLSupport::supported()) { + QGLFormat format; + format.setSampleBuffers(true); + format.setStencil(true); + format.setAlpha(true); + format.setRgba(true); + format.setDepth(false); + + // Most of hardware refuses to work for us with direct rendering enabled. + format.setDirectRendering(false); + + setViewport(new QGLWidget(format)); } + } + + setFrameShape(QFrame::NoFrame); + viewport()->setFocusPolicy(Qt::WheelFocus); + + if (downscaled_version.isNull()) { + m_pixmap = QPixmap::fromImage(createDownscaledImage(image)); + } else if (downscaled_version.pixmap().isNull()) { + m_pixmap = QPixmap::fromImage(downscaled_version.image()); + } else { + m_pixmap = downscaled_version.pixmap(); + } + + m_pixmapToImage.scale((double) m_image.width() / m_pixmap.width(), (double) m_image.height() / m_pixmap.height()); + + m_widgetFocalPoint = centeredWidgetFocalPoint(); + m_pixmapFocalPoint = m_virtualToImage.map(virtualDisplayRect().center()); + + m_timer.setSingleShot(true); + m_timer.setInterval(150); // msec + connect(&m_timer, SIGNAL(timeout()), this, SLOT(initiateBuildingHqVersion())); + + setMouseTracking(true); + m_cursorTrackerTimer.setSingleShot(true); + m_cursorTrackerTimer.setInterval(150); // msec + connect(&m_cursorTrackerTimer, &QTimer::timeout, [this]() { + QPointF cursorPos; + if (!m_cursorPos.isNull()) { + cursorPos = m_widgetToVirtual.map(m_cursorPos) - m_virtualImageCropArea.boundingRect().topLeft(); + } + m_infoProvider.setMousePos(cursorPos); + }); - setFrameShape(QFrame::NoFrame); - viewport()->setFocusPolicy(Qt::WheelFocus); + updatePhysSize(); - if (downscaled_version.isNull()) { - m_pixmap = QPixmap::fromImage(createDownscaledImage(image)); - } else if (downscaled_version.pixmap().isNull()) { - m_pixmap = QPixmap::fromImage(downscaled_version.image()); - } else { - m_pixmap = downscaled_version.pixmap(); - } + updateWidgetTransformAndFixFocalPoint(CENTER_IF_FITS); - m_pixmapToImage.scale((double) m_image.width() / m_pixmap.width(), (double) m_image.height() / m_pixmap.height()); + interactionState().setDefaultStatusTip(tr("Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible.")); + ensureStatusTip(interactionState().statusTip()); - m_widgetFocalPoint = centeredWidgetFocalPoint(); - m_pixmapFocalPoint = m_virtualToImage.map(virtualDisplayRect().center()); - - m_timer.setSingleShot(true); - m_timer.setInterval(150); // msec - connect(&m_timer, SIGNAL(timeout()), this, SLOT(initiateBuildingHqVersion())); - - setMouseTracking(true); - m_cursorTrackerTimer.setSingleShot(true); - m_cursorTrackerTimer.setInterval(150); // msec - connect(&m_cursorTrackerTimer, &QTimer::timeout, [this]() { - QPointF cursorPos; - if (!m_cursorPos.isNull()) { - cursorPos = m_widgetToVirtual.map(m_cursorPos) - m_virtualImageCropArea.boundingRect().topLeft(); - } - m_infoProvider.setMousePos(cursorPos); - }); - - updatePhysSize(); - - updateWidgetTransformAndFixFocalPoint(CENTER_IF_FITS); - - interactionState().setDefaultStatusTip( - tr("Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible.")); - ensureStatusTip(interactionState().statusTip()); - - connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(updateScrollBars())); - connect(verticalScrollBar(), SIGNAL(sliderReleased()), SLOT(updateScrollBars())); - connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(reactToScrollBars())); - connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(reactToScrollBars())); + connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(updateScrollBars())); + connect(verticalScrollBar(), SIGNAL(sliderReleased()), SLOT(updateScrollBars())); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(reactToScrollBars())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(reactToScrollBars())); } ImageViewBase::~ImageViewBase() = default; void ImageViewBase::hqTransformSetEnabled(const bool enabled) { - if (!enabled && m_hqTransformEnabled) { - // Turning off. - m_hqTransformEnabled = false; - if (m_ptrHqTransformTask) { - m_ptrHqTransformTask->cancel(); - m_ptrHqTransformTask.reset(); - } - if (!m_hqPixmap.isNull()) { - m_hqPixmap = QPixmap(); - update(); - } - } else if (enabled && !m_hqTransformEnabled) { - // Turning on. - m_hqTransformEnabled = true; - update(); + if (!enabled && m_hqTransformEnabled) { + // Turning off. + m_hqTransformEnabled = false; + if (m_hqTransformTask) { + m_hqTransformTask->cancel(); + m_hqTransformTask.reset(); + } + if (!m_hqPixmap.isNull()) { + m_hqPixmap = QPixmap(); + update(); } + } else if (enabled && !m_hqTransformEnabled) { + // Turning on. + m_hqTransformEnabled = true; + update(); + } } QImage ImageViewBase::createDownscaledImage(const QImage& image) { - assert(!image.isNull()); + assert(!image.isNull()); - // Original and downscaled DPM. - const Dpm o_dpm(image); - const Dpm d_dpm(Dpi(200, 200)); + // Original and downscaled DPM. + const Dpm o_dpm(image); + const Dpm d_dpm(Dpi(200, 200)); - const int o_w = image.width(); - const int o_h = image.height(); + const int o_w = image.width(); + const int o_h = image.height(); - int d_w = o_w * d_dpm.horizontal() / o_dpm.horizontal(); - int d_h = o_h * d_dpm.vertical() / o_dpm.vertical(); - d_w = qBound(1, d_w, o_w); - d_h = qBound(1, d_h, o_h); + int d_w = o_w * d_dpm.horizontal() / o_dpm.horizontal(); + int d_h = o_h * d_dpm.vertical() / o_dpm.vertical(); + d_w = qBound(1, d_w, o_w); + d_h = qBound(1, d_h, o_h); - if ((d_w * 1.2 > o_w) || (d_h * 1.2 > o_h)) { - // Sizes are close - no point in downscaling. - return image; - } + if ((d_w * 1.2 > o_w) || (d_h * 1.2 > o_h)) { + // Sizes are close - no point in downscaling. + return image; + } - QTransform xform; - xform.scale((double) d_w / o_w, (double) d_h / o_h); + QTransform xform; + xform.scale((double) d_w / o_w, (double) d_h / o_h); - return transform(image, xform, QRect(0, 0, d_w, d_h), OutsidePixels::assumeColor(Qt::white)); + return transform(image, xform, QRect(0, 0, d_w, d_h), OutsidePixels::assumeColor(Qt::white)); } QRectF ImageViewBase::maxViewportRect() const { - const QRectF viewport_rect(QPointF(0, 0), maximumViewportSize()); - QRectF r(viewport_rect); - r.adjust(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); - if (r.isEmpty()) { - return QRectF(viewport_rect.center(), viewport_rect.center()); - } - - return r; + const QRectF viewport_rect(QPointF(0, 0), maximumViewportSize()); + QRectF r(viewport_rect); + r.adjust(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); + if (r.isEmpty()) { + return QRectF(viewport_rect.center(), viewport_rect.center()); + } + + return r; } QRectF ImageViewBase::dynamicViewportRect() const { - const QRectF viewport_rect(viewport()->rect()); - QRectF r(viewport_rect); - r.adjust(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); - if (r.isEmpty()) { - return QRectF(viewport_rect.center(), viewport_rect.center()); - } - - return r; + const QRectF viewport_rect(viewport()->rect()); + QRectF r(viewport_rect); + r.adjust(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); + if (r.isEmpty()) { + return QRectF(viewport_rect.center(), viewport_rect.center()); + } + + return r; } QRectF ImageViewBase::getOccupiedWidgetRect() const { - const QRectF widget_rect(m_virtualToWidget.mapRect(virtualDisplayRect())); + const QRectF widget_rect(m_virtualToWidget.mapRect(virtualDisplayRect())); - return widget_rect.intersected(dynamicViewportRect()); + return widget_rect.intersected(dynamicViewportRect()); } void ImageViewBase::setWidgetFocalPoint(const QPointF& widget_fp) { - setNewWidgetFP(widget_fp, /*update =*/true); + setNewWidgetFP(widget_fp, /*update =*/true); } void ImageViewBase::adjustAndSetWidgetFocalPoint(const QPointF& widget_fp) { - adjustAndSetNewWidgetFP(widget_fp, /*update=*/true); + adjustAndSetNewWidgetFP(widget_fp, /*update=*/true); } void ImageViewBase::setZoomLevel(double zoom) { - if (m_zoom != zoom) { - m_zoom = zoom; - updateWidgetTransform(); - update(); - } + if (m_zoom != zoom) { + m_zoom = zoom; + updateWidgetTransform(); + update(); + } } void ImageViewBase::moveTowardsIdealPosition(const double pixel_length) { - if (pixel_length <= 0) { - // The name implies we are moving *towards* the ideal position. - return; - } - - const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); - if (ideal_widget_fp == m_widgetFocalPoint) { - return; - } - - QPointF vec(ideal_widget_fp - m_widgetFocalPoint); - const double max_length = std::sqrt(vec.x() * vec.x() + vec.y() * vec.y()); - if (pixel_length >= max_length) { - m_widgetFocalPoint = ideal_widget_fp; - } else { - vec *= pixel_length / max_length; - m_widgetFocalPoint += vec; - } - - updateWidgetTransform(); - update(); + if (pixel_length <= 0) { + // The name implies we are moving *towards* the ideal position. + return; + } + + const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); + if (ideal_widget_fp == m_widgetFocalPoint) { + return; + } + + QPointF vec(ideal_widget_fp - m_widgetFocalPoint); + const double max_length = std::sqrt(vec.x() * vec.x() + vec.y() * vec.y()); + if (pixel_length >= max_length) { + m_widgetFocalPoint = ideal_widget_fp; + } else { + vec *= pixel_length / max_length; + m_widgetFocalPoint += vec; + } + + updateWidgetTransform(); + update(); } void ImageViewBase::updateTransform(const ImagePresentation& presentation) { - const TransformChangeWatcher watcher(*this); - const TempFocalPointAdjuster temp_fp(*this); + const TransformChangeWatcher watcher(*this); + const TempFocalPointAdjuster temp_fp(*this); - m_imageToVirtual = presentation.transform(); - m_virtualToImage = m_imageToVirtual.inverted(); - m_virtualImageCropArea = presentation.cropArea(); - m_virtualDisplayArea = presentation.displayArea(); + m_imageToVirtual = presentation.transform(); + m_virtualToImage = m_imageToVirtual.inverted(); + m_virtualImageCropArea = presentation.cropArea(); + m_virtualDisplayArea = presentation.displayArea(); - updateWidgetTransform(); - update(); - updatePhysSize(); + updateWidgetTransform(); + update(); + updatePhysSize(); } void ImageViewBase::updateTransformAndFixFocalPoint(const ImagePresentation& presentation, const FocalPointMode mode) { - const TransformChangeWatcher watcher(*this); - const TempFocalPointAdjuster temp_fp(*this); + const TransformChangeWatcher watcher(*this); + const TempFocalPointAdjuster temp_fp(*this); - m_imageToVirtual = presentation.transform(); - m_virtualToImage = m_imageToVirtual.inverted(); - m_virtualImageCropArea = presentation.cropArea(); - m_virtualDisplayArea = presentation.displayArea(); + m_imageToVirtual = presentation.transform(); + m_virtualToImage = m_imageToVirtual.inverted(); + m_virtualImageCropArea = presentation.cropArea(); + m_virtualDisplayArea = presentation.displayArea(); - updateWidgetTransformAndFixFocalPoint(mode); - update(); - updatePhysSize(); + updateWidgetTransformAndFixFocalPoint(mode); + update(); + updatePhysSize(); } void ImageViewBase::updateTransformPreservingScale(const ImagePresentation& presentation) { - const TransformChangeWatcher watcher(*this); - const TempFocalPointAdjuster temp_fp(*this); + const TransformChangeWatcher watcher(*this); + const TempFocalPointAdjuster temp_fp(*this); - // An arbitrary line in image coordinates. - const QLineF image_line(0.0, 0.0, 1.0, 1.0); + // An arbitrary line in image coordinates. + const QLineF image_line(0.0, 0.0, 1.0, 1.0); - const QLineF widget_line_before((m_imageToVirtual * m_virtualToWidget).map(image_line)); + const QLineF widget_line_before((m_imageToVirtual * m_virtualToWidget).map(image_line)); - m_imageToVirtual = presentation.transform(); - m_virtualToImage = m_imageToVirtual.inverted(); - m_virtualImageCropArea = presentation.cropArea(); - m_virtualDisplayArea = presentation.displayArea(); + m_imageToVirtual = presentation.transform(); + m_virtualToImage = m_imageToVirtual.inverted(); + m_virtualImageCropArea = presentation.cropArea(); + m_virtualDisplayArea = presentation.displayArea(); - updateWidgetTransform(); + updateWidgetTransform(); - const QLineF widget_line_after((m_imageToVirtual * m_virtualToWidget).map(image_line)); + const QLineF widget_line_after((m_imageToVirtual * m_virtualToWidget).map(image_line)); - m_zoom *= widget_line_before.length() / widget_line_after.length(); - updateWidgetTransform(); + m_zoom *= widget_line_before.length() / widget_line_after.length(); + updateWidgetTransform(); - update(); - updatePhysSize(); + update(); + updatePhysSize(); } void ImageViewBase::ensureStatusTip(const QString& status_tip) { - const QString cur_status_tip(statusTip()); - if (cur_status_tip.constData() == status_tip.constData()) { - return; - } - if (cur_status_tip == status_tip) { - return; - } - - viewport()->setStatusTip(status_tip); - - if (viewport()->underMouse()) { - // Note that setStatusTip() alone is not enough, - // as it's only taken into account when the mouse - // enters the widget. - // Also note that we use postEvent() rather than sendEvent(), - // because sendEvent() may immediately process other events. - QApplication::postEvent(viewport(), new QStatusTipEvent(status_tip)); - } + const QString cur_status_tip(statusTip()); + if (cur_status_tip.constData() == status_tip.constData()) { + return; + } + if (cur_status_tip == status_tip) { + return; + } + + viewport()->setStatusTip(status_tip); + + if (viewport()->underMouse()) { + // Note that setStatusTip() alone is not enough, + // as it's only taken into account when the mouse + // enters the widget. + // Also note that we use postEvent() rather than sendEvent(), + // because sendEvent() may immediately process other events. + QApplication::postEvent(viewport(), new QStatusTipEvent(status_tip)); + } } void ImageViewBase::paintEvent(QPaintEvent* event) { - QPainter painter(viewport()); - painter.save(); + QPainter painter(viewport()); - const double xscale = m_virtualToWidget.m11(); + // Fill the background as Qt::WA_OpaquePaintEvent attribute is enabled. + painter.fillRect(viewport()->rect(), palette().color(backgroundRole())); - // Width of a source pixel in mm, as it's displayed on screen. - const double pixel_width = widthMM() * xscale / width(); + painter.save(); - // Make clipping smooth. - painter.setRenderHint(QPainter::Antialiasing, true); + const double xscale = m_virtualToWidget.m11(); - // Disable antialiasing for large zoom levels. - painter.setRenderHint(QPainter::SmoothPixmapTransform, pixel_width < 0.5); + // Width of a source pixel in mm, as it's displayed on screen. + const double pixel_width = widthMM() * xscale / width(); - if (validateHqPixmap()) { - // HQ pixmap maps one to one to screen pixels, so antialiasing is not necessary. - painter.setRenderHint(QPainter::SmoothPixmapTransform, false); + // Make clipping smooth. + painter.setRenderHint(QPainter::Antialiasing, true); - QPainterPath clip_path; - clip_path.addPolygon(m_virtualToWidget.map(m_virtualImageCropArea)); - painter.setClipPath(clip_path); + // Disable antialiasing for large zoom levels. + painter.setRenderHint(QPainter::SmoothPixmapTransform, pixel_width < 0.5); - painter.drawPixmap(m_hqPixmapPos, m_hqPixmap); - } else { - scheduleHqVersionRebuild(); + if (validateHqPixmap()) { + // HQ pixmap maps one to one to screen pixels, so antialiasing is not necessary. + painter.setRenderHint(QPainter::SmoothPixmapTransform, false); - const QTransform pixmap_to_virtual(m_pixmapToImage * m_imageToVirtual); - painter.setWorldTransform(pixmap_to_virtual * m_virtualToWidget); + QPainterPath clip_path; + clip_path.addPolygon(m_virtualToWidget.map(m_virtualImageCropArea)); + painter.setClipPath(clip_path); - QPainterPath clip_path; - clip_path.addPolygon(pixmap_to_virtual.inverted().map(m_virtualImageCropArea)); - painter.setClipPath(clip_path); + painter.drawPixmap(m_hqPixmapPos, m_hqPixmap); + } else { + scheduleHqVersionRebuild(); - PixmapRenderer::drawPixmap(painter, m_pixmap); - } + const QTransform pixmap_to_virtual(m_pixmapToImage * m_imageToVirtual); + painter.setWorldTransform(pixmap_to_virtual * m_virtualToWidget); - painter.restore(); + QPainterPath clip_path; + clip_path.addPolygon(pixmap_to_virtual.inverted().map(m_virtualImageCropArea)); + painter.setClipPath(clip_path); - painter.setWorldTransform(m_virtualToWidget); + PixmapRenderer::drawPixmap(painter, m_pixmap); + } - m_interactionState.resetProximity(); - if (!m_interactionState.captured()) { - m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + mapFromGlobal(QCursor::pos()), m_interactionState); - updateStatusTipAndCursor(); - } + painter.restore(); - m_rootInteractionHandler.paint(painter, m_interactionState); - maybeQueueRedraw(); + painter.setWorldTransform(m_virtualToWidget); + + m_interactionState.resetProximity(); + if (!m_interactionState.captured()) { + m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + mapFromGlobal(QCursor::pos()), m_interactionState); + updateStatusTipAndCursor(); + } + + m_rootInteractionHandler.paint(painter, m_interactionState); + maybeQueueRedraw(); } // ImageViewBase::paintEvent void ImageViewBase::keyPressEvent(QKeyEvent* event) { - event->setAccepted(false); - m_rootInteractionHandler.keyPressEvent(event, m_interactionState); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + event->setAccepted(false); + m_rootInteractionHandler.keyPressEvent(event, m_interactionState); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::keyReleaseEvent(QKeyEvent* event) { - event->setAccepted(false); - m_rootInteractionHandler.keyReleaseEvent(event, m_interactionState); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + event->setAccepted(false); + m_rootInteractionHandler.keyReleaseEvent(event, m_interactionState); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::mousePressEvent(QMouseEvent* event) { - m_interactionState.resetProximity(); - if (!m_interactionState.captured()) { - m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); - } - - event->setAccepted(false); - m_rootInteractionHandler.mousePressEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + m_interactionState.resetProximity(); + if (!m_interactionState.captured()) { + m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); + } + + event->setAccepted(false); + m_rootInteractionHandler.mousePressEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::mouseReleaseEvent(QMouseEvent* event) { - m_interactionState.resetProximity(); - if (!m_interactionState.captured()) { - m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); - } - - event->setAccepted(false); - m_rootInteractionHandler.mouseReleaseEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + m_interactionState.resetProximity(); + if (!m_interactionState.captured()) { + m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); + } + + event->setAccepted(false); + m_rootInteractionHandler.mouseReleaseEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::mouseDoubleClickEvent(QMouseEvent* event) { - m_interactionState.resetProximity(); - if (!m_interactionState.captured()) { - m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); - } - - event->setAccepted(false); - m_rootInteractionHandler.mouseDoubleClickEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + m_interactionState.resetProximity(); + if (!m_interactionState.captured()) { + m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); + } + + event->setAccepted(false); + m_rootInteractionHandler.mouseDoubleClickEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::mouseMoveEvent(QMouseEvent* event) { - m_interactionState.resetProximity(); - if (!m_interactionState.captured()) { - m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); - } - - event->setAccepted(false); - m_rootInteractionHandler.mouseMoveEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); - updateCursorPos(event->localPos()); + m_interactionState.resetProximity(); + if (!m_interactionState.captured()) { + m_rootInteractionHandler.proximityUpdate(QPointF(0.5, 0.5) + event->pos(), m_interactionState); + } + + event->setAccepted(false); + m_rootInteractionHandler.mouseMoveEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); + updateCursorPos(event->localPos()); } void ImageViewBase::wheelEvent(QWheelEvent* event) { - event->setAccepted(false); - m_rootInteractionHandler.wheelEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + event->setAccepted(false); + m_rootInteractionHandler.wheelEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::contextMenuEvent(QContextMenuEvent* event) { - event->setAccepted(false); - m_rootInteractionHandler.contextMenuEvent(event, m_interactionState); - event->setAccepted(true); - updateStatusTipAndCursor(); - maybeQueueRedraw(); + event->setAccepted(false); + m_rootInteractionHandler.contextMenuEvent(event, m_interactionState); + event->setAccepted(true); + updateStatusTipAndCursor(); + maybeQueueRedraw(); } void ImageViewBase::resizeEvent(QResizeEvent* event) { - QAbstractScrollArea::resizeEvent(event); + QAbstractScrollArea::resizeEvent(event); - if (m_ignoreResizeEvents) { - return; - } + if (m_ignoreResizeEvents) { + return; + } - const ScopedIncDec guard(m_ignoreScrollEvents); + const ScopedIncDec guard(m_ignoreScrollEvents); - if (maximumViewportSize() != m_lastMaximumViewportSize) { - m_lastMaximumViewportSize = maximumViewportSize(); - m_widgetFocalPoint = centeredWidgetFocalPoint(); - updateWidgetTransform(); - } else { - const TransformChangeWatcher watcher(*this); - const TempFocalPointAdjuster temp_fp(*this, QPointF(0, 0)); - updateTransformPreservingScale( - ImagePresentation(m_imageToVirtual, m_virtualImageCropArea, m_virtualDisplayArea)); - } + if (maximumViewportSize() != m_lastMaximumViewportSize) { + m_lastMaximumViewportSize = maximumViewportSize(); + m_widgetFocalPoint = centeredWidgetFocalPoint(); + updateWidgetTransform(); + } else { + const TransformChangeWatcher watcher(*this); + const TempFocalPointAdjuster temp_fp(*this, QPointF(0, 0)); + updateTransformPreservingScale(ImagePresentation(m_imageToVirtual, m_virtualImageCropArea, m_virtualDisplayArea)); + } } void ImageViewBase::enterEvent(QEvent* event) { - viewport()->setFocus(); - QAbstractScrollArea::enterEvent(event); + viewport()->setFocus(); + QAbstractScrollArea::enterEvent(event); } void ImageViewBase::leaveEvent(QEvent* event) { - updateCursorPos(QPointF()); - QAbstractScrollArea::leaveEvent(event); + updateCursorPos(QPointF()); + QAbstractScrollArea::leaveEvent(event); } void ImageViewBase::showEvent(QShowEvent* event) { - QWidget::showEvent(event); + QWidget::showEvent(event); - if (auto* mainWindow = dynamic_cast(window())) { - if (auto* infoObserver = Utils::castOrFindChild(mainWindow->statusBar())) { - infoObserver->setInfoProvider(&infoProvider()); - } + if (auto* mainWindow = dynamic_cast(window())) { + if (auto* infoObserver = Utils::castOrFindChild(mainWindow->statusBar())) { + infoObserver->setInfoProvider(&infoProvider()); } + } } /** * Called when any of the transformations change. */ void ImageViewBase::transformChanged() { - updateScrollBars(); + updateScrollBars(); } void ImageViewBase::updateScrollBars() { - if (verticalScrollBar()->isSliderDown() || horizontalScrollBar()->isSliderDown()) { - return; + if (verticalScrollBar()->isSliderDown() || horizontalScrollBar()->isSliderDown()) { + return; + } + + const ScopedIncDec guard1(m_ignoreScrollEvents); + const ScopedIncDec guard2(m_ignoreResizeEvents); + + const QRectF picture(m_virtualToWidget.mapRect(virtualDisplayRect())); + const QPointF viewport_center(maxViewportRect().center()); + const QPointF picture_center(picture.center()); + QRectF viewport(maxViewportRect()); + + // Introduction of one scrollbar will decrease the available size in + // another direction, which may cause a scrollbar in that direction + // to become necessary. For this reason, we have a loop here. + for (int i = 0; i < 2; ++i) { + const double xval = picture_center.x(); + double xmin, xmax; // Minimum and maximum positions for picture center. + if (picture_center.x() < viewport_center.x()) { + xmin = std::min(xval, viewport.right() - 0.5 * picture.width()); + xmax = std::max(viewport_center.x(), viewport.left() + 0.5 * picture.width()); + } else { + xmax = std::max(xval, viewport.left() + 0.5 * picture.width()); + xmin = std::min(viewport_center.x(), viewport.right() - 0.5 * picture.width()); + } + + const double yval = picture_center.y(); + double ymin, ymax; // Minimum and maximum positions for picture center. + if (picture_center.y() < viewport_center.y()) { + ymin = std::min(yval, viewport.bottom() - 0.5 * picture.height()); + ymax = std::max(viewport_center.y(), viewport.top() + 0.5 * picture.height()); + } else { + ymax = std::max(yval, viewport.top() + 0.5 * picture.height()); + ymin = std::min(viewport_center.y(), viewport.bottom() - 0.5 * picture.height()); } - const ScopedIncDec guard1(m_ignoreScrollEvents); - const ScopedIncDec guard2(m_ignoreResizeEvents); - - const QRectF picture(m_virtualToWidget.mapRect(virtualDisplayRect())); - const QPointF viewport_center(maxViewportRect().center()); - const QPointF picture_center(picture.center()); - QRectF viewport(maxViewportRect()); - - // Introduction of one scrollbar will decrease the available size in - // another direction, which may cause a scrollbar in that direction - // to become necessary. For this reason, we have a loop here. - for (int i = 0; i < 2; ++i) { - const double xval = picture_center.x(); - double xmin, xmax; // Minimum and maximum positions for picture center. - if (picture_center.x() < viewport_center.x()) { - xmin = std::min(xval, viewport.right() - 0.5 * picture.width()); - xmax = std::max(viewport_center.x(), viewport.left() + 0.5 * picture.width()); - } else { - xmax = std::max(xval, viewport.left() + 0.5 * picture.width()); - xmin = std::min(viewport_center.x(), viewport.right() - 0.5 * picture.width()); - } - - const double yval = picture_center.y(); - double ymin, ymax; // Minimum and maximum positions for picture center. - if (picture_center.y() < viewport_center.y()) { - ymin = std::min(yval, viewport.bottom() - 0.5 * picture.height()); - ymax = std::max(viewport_center.y(), viewport.top() + 0.5 * picture.height()); - } else { - ymax = std::max(yval, viewport.top() + 0.5 * picture.height()); - ymin = std::min(viewport_center.y(), viewport.bottom() - 0.5 * picture.height()); - } - - const auto xrange = (int) std::ceil(xmax - xmin); - const auto yrange = (int) std::ceil(ymax - ymin); - const int xfirst = 0; - const int xlast = xrange - 1; - const int yfirst = 0; - const int ylast = yrange - 1; - - // We are going to map scrollbar coordinates to widget coordinates - // of the central point of the display area using a linear function. - // f(x) = ax + b - - // xmin = xa * xlast + xb - // xmax = xa * xfirst + xb - const double xa = (xfirst == xlast) ? 1 : (xmax - xmin) / (xfirst - xlast); - const double xb = xmax - xa * xfirst; - const double ya = (yfirst == ylast) ? 1 : (ymax - ymin) / (yfirst - ylast); - const double yb = ymax - ya * yfirst; - - // Inverse transformation. - // xlast = ixa * xmin + ixb - // xfirst = ixa * xmax + ixb - const double ixa = (xmax == xmin) ? 1 : (xfirst - xlast) / (xmax - xmin); - const double ixb = xfirst - ixa * xmax; - const double iya = (ymax == ymin) ? 1 : (yfirst - ylast) / (ymax - ymin); - const double iyb = yfirst - iya * ymax; - - m_scrollTransform.setMatrix(xa, 0, 0, 0, ya, 0, xb, yb, 1); - - const int xcur = qRound(ixa * xval + ixb); - const int ycur = qRound(iya * yval + iyb); - - horizontalScrollBar()->setRange(xfirst, xlast); - verticalScrollBar()->setRange(yfirst, ylast); - - horizontalScrollBar()->setValue(xcur); - verticalScrollBar()->setValue(ycur); - - horizontalScrollBar()->setPageStep(qRound(viewport.width())); - verticalScrollBar()->setPageStep(qRound(viewport.height())); - // XXX: a hack to force immediate update of viewport()->rect(), - // which is used by dynamicViewportRect() below. - // Note that it involves a resize event being sent not only to - // the viewport, but for some reason also to the containing - // QAbstractScrollArea, that is to this object. - setHorizontalScrollBarPolicy(horizontalScrollBarPolicy()); - - const QRectF old_viewport(viewport); - viewport = dynamicViewportRect(); - if (viewport == old_viewport) { - break; - } + const auto xrange = (int) std::ceil(xmax - xmin); + const auto yrange = (int) std::ceil(ymax - ymin); + const int xfirst = 0; + const int xlast = xrange - 1; + const int yfirst = 0; + const int ylast = yrange - 1; + + // We are going to map scrollbar coordinates to widget coordinates + // of the central point of the display area using a linear function. + // f(x) = ax + b + + // xmin = xa * xlast + xb + // xmax = xa * xfirst + xb + const double xa = (xfirst == xlast) ? 1 : (xmax - xmin) / (xfirst - xlast); + const double xb = xmax - xa * xfirst; + const double ya = (yfirst == ylast) ? 1 : (ymax - ymin) / (yfirst - ylast); + const double yb = ymax - ya * yfirst; + + // Inverse transformation. + // xlast = ixa * xmin + ixb + // xfirst = ixa * xmax + ixb + const double ixa = (xmax == xmin) ? 1 : (xfirst - xlast) / (xmax - xmin); + const double ixb = xfirst - ixa * xmax; + const double iya = (ymax == ymin) ? 1 : (yfirst - ylast) / (ymax - ymin); + const double iyb = yfirst - iya * ymax; + + m_scrollTransform.setMatrix(xa, 0, 0, 0, ya, 0, xb, yb, 1); + + const int xcur = qRound(ixa * xval + ixb); + const int ycur = qRound(iya * yval + iyb); + + horizontalScrollBar()->setRange(xfirst, xlast); + verticalScrollBar()->setRange(yfirst, ylast); + + horizontalScrollBar()->setValue(xcur); + verticalScrollBar()->setValue(ycur); + + horizontalScrollBar()->setPageStep(qRound(viewport.width())); + verticalScrollBar()->setPageStep(qRound(viewport.height())); + // XXX: a hack to force immediate update of viewport()->rect(), + // which is used by dynamicViewportRect() below. + // Note that it involves a resize event being sent not only to + // the viewport, but for some reason also to the containing + // QAbstractScrollArea, that is to this object. + setHorizontalScrollBarPolicy(horizontalScrollBarPolicy()); + + const QRectF old_viewport(viewport); + viewport = dynamicViewportRect(); + if (viewport == old_viewport) { + break; } + } } // ImageViewBase::updateScrollBars void ImageViewBase::reactToScrollBars() { - if (m_ignoreScrollEvents) { - return; - } + if (m_ignoreScrollEvents) { + return; + } - const TransformChangeWatcher watcher(*this); + const TransformChangeWatcher watcher(*this); - const QPointF raw_position(horizontalScrollBar()->value(), verticalScrollBar()->value()); - const QPointF new_fp(m_scrollTransform.map(raw_position)); - const QPointF old_fp(getWidgetFocalPoint()); + const QPointF raw_position(horizontalScrollBar()->value(), verticalScrollBar()->value()); + const QPointF new_fp(m_scrollTransform.map(raw_position)); + const QPointF old_fp(getWidgetFocalPoint()); - m_pixmapFocalPoint = m_virtualToImage.map(m_virtualDisplayArea.center()); - m_widgetFocalPoint = new_fp; - updateWidgetTransform(); + m_pixmapFocalPoint = m_virtualToImage.map(m_virtualDisplayArea.center()); + m_widgetFocalPoint = new_fp; + updateWidgetTransform(); - setWidgetFocalPointWithoutMoving(old_fp); + setWidgetFocalPointWithoutMoving(old_fp); } /** @@ -706,25 +707,25 @@ void ImageViewBase::reactToScrollBars() { * that doesn't cause image movement doesn't require calling this method. */ void ImageViewBase::updateWidgetTransform() { - const TransformChangeWatcher watcher(*this); + const TransformChangeWatcher watcher(*this); - const QRectF virt_rect(virtualDisplayRect()); - const QPointF virt_origin(m_imageToVirtual.map(m_pixmapFocalPoint)); - const QPointF widget_origin(m_widgetFocalPoint); + const QRectF virt_rect(virtualDisplayRect()); + const QPointF virt_origin(m_imageToVirtual.map(m_pixmapFocalPoint)); + const QPointF widget_origin(m_widgetFocalPoint); - QSizeF zoom1_widget_size(virt_rect.size()); - zoom1_widget_size.scale(maxViewportRect().size(), Qt::KeepAspectRatio); + QSizeF zoom1_widget_size(virt_rect.size()); + zoom1_widget_size.scale(maxViewportRect().size(), Qt::KeepAspectRatio); - const double zoom1_x = zoom1_widget_size.width() / virt_rect.width(); - const double zoom1_y = zoom1_widget_size.height() / virt_rect.height(); + const double zoom1_x = zoom1_widget_size.width() / virt_rect.width(); + const double zoom1_y = zoom1_widget_size.height() / virt_rect.height(); - QTransform xform; - xform.translate(-virt_origin.x(), -virt_origin.y()); - xform *= QTransform().scale(zoom1_x * m_zoom, zoom1_y * m_zoom); - xform *= QTransform().translate(widget_origin.x(), widget_origin.y()); + QTransform xform; + xform.translate(-virt_origin.x(), -virt_origin.y()); + xform *= QTransform().scale(zoom1_x * m_zoom, zoom1_y * m_zoom); + xform *= QTransform().translate(widget_origin.x(), widget_origin.y()); - m_virtualToWidget = xform; - m_widgetToVirtual = m_virtualToWidget.inverted(); + m_virtualToWidget = xform; + m_widgetToVirtual = m_virtualToWidget.inverted(); } /** @@ -734,18 +735,18 @@ void ImageViewBase::updateWidgetTransform() { * may invalidate the focal point. */ void ImageViewBase::updateWidgetTransformAndFixFocalPoint(const FocalPointMode mode) { - const TransformChangeWatcher watcher(*this); + const TransformChangeWatcher watcher(*this); - // This must go before getIdealWidgetFocalPoint(), as it - // recalculates m_virtualToWidget, that is used by - // getIdealWidgetFocalPoint(). - updateWidgetTransform(); + // This must go before getIdealWidgetFocalPoint(), as it + // recalculates m_virtualToWidget, that is used by + // getIdealWidgetFocalPoint(). + updateWidgetTransform(); - const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(mode)); - if (ideal_widget_fp != m_widgetFocalPoint) { - m_widgetFocalPoint = ideal_widget_fp; - updateWidgetTransform(); - } + const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(mode)); + if (ideal_widget_fp != m_widgetFocalPoint) { + m_widgetFocalPoint = ideal_widget_fp; + updateWidgetTransform(); + } } /** @@ -767,64 +768,64 @@ void ImageViewBase::updateWidgetTransformAndFixFocalPoint(const FocalPointMode m * in horizontal and vertical dimensions independently. */ QPointF ImageViewBase::getIdealWidgetFocalPoint(const FocalPointMode mode) const { - // Widget rect reduced by margins. - const QRectF display_area(maxViewportRect()); - - // The virtual image rectangle in widget coordinates. - const QRectF image_area(m_virtualToWidget.mapRect(virtualDisplayRect())); - // Unused display space from each side. - const double left_margin = image_area.left() - display_area.left(); - const double right_margin = display_area.right() - image_area.right(); - const double top_margin = image_area.top() - display_area.top(); - const double bottom_margin = display_area.bottom() - image_area.bottom(); - - QPointF widget_focal_point(m_widgetFocalPoint); - - if ((mode == CENTER_IF_FITS) && (left_margin + right_margin >= 0.0)) { - // Image fits horizontally, so center it in that direction - // by equalizing its left and right margins. - const double new_margins = 0.5 * (left_margin + right_margin); - widget_focal_point.rx() += new_margins - left_margin; - } else if ((left_margin < 0.0) && (right_margin > 0.0)) { - // Move image to the right so that either left_margin or - // right_margin becomes zero, whichever requires less movement. - const double movement = std::min(std::fabs(left_margin), std::fabs(right_margin)); - widget_focal_point.rx() += movement; - } else if ((right_margin < 0.0) && (left_margin > 0.0)) { - // Move image to the left so that either left_margin or - // right_margin becomes zero, whichever requires less movement. - const double movement = std::min(std::fabs(left_margin), std::fabs(right_margin)); - widget_focal_point.rx() -= movement; - } - - if ((mode == CENTER_IF_FITS) && (top_margin + bottom_margin >= 0.0)) { - // Image fits vertically, so center it in that direction - // by equalizing its top and bottom margins. - const double new_margins = 0.5 * (top_margin + bottom_margin); - widget_focal_point.ry() += new_margins - top_margin; - } else if ((top_margin < 0.0) && (bottom_margin > 0.0)) { - // Move image down so that either top_margin or bottom_margin - // becomes zero, whichever requires less movement. - const double movement = std::min(std::fabs(top_margin), std::fabs(bottom_margin)); - widget_focal_point.ry() += movement; - } else if ((bottom_margin < 0.0) && (top_margin > 0.0)) { - // Move image up so that either top_margin or bottom_margin - // becomes zero, whichever requires less movement. - const double movement = std::min(std::fabs(top_margin), std::fabs(bottom_margin)); - widget_focal_point.ry() -= movement; - } - - return widget_focal_point; + // Widget rect reduced by margins. + const QRectF display_area(maxViewportRect()); + + // The virtual image rectangle in widget coordinates. + const QRectF image_area(m_virtualToWidget.mapRect(virtualDisplayRect())); + // Unused display space from each side. + const double left_margin = image_area.left() - display_area.left(); + const double right_margin = display_area.right() - image_area.right(); + const double top_margin = image_area.top() - display_area.top(); + const double bottom_margin = display_area.bottom() - image_area.bottom(); + + QPointF widget_focal_point(m_widgetFocalPoint); + + if ((mode == CENTER_IF_FITS) && (left_margin + right_margin >= 0.0)) { + // Image fits horizontally, so center it in that direction + // by equalizing its left and right margins. + const double new_margins = 0.5 * (left_margin + right_margin); + widget_focal_point.rx() += new_margins - left_margin; + } else if ((left_margin < 0.0) && (right_margin > 0.0)) { + // Move image to the right so that either left_margin or + // right_margin becomes zero, whichever requires less movement. + const double movement = std::min(std::fabs(left_margin), std::fabs(right_margin)); + widget_focal_point.rx() += movement; + } else if ((right_margin < 0.0) && (left_margin > 0.0)) { + // Move image to the left so that either left_margin or + // right_margin becomes zero, whichever requires less movement. + const double movement = std::min(std::fabs(left_margin), std::fabs(right_margin)); + widget_focal_point.rx() -= movement; + } + + if ((mode == CENTER_IF_FITS) && (top_margin + bottom_margin >= 0.0)) { + // Image fits vertically, so center it in that direction + // by equalizing its top and bottom margins. + const double new_margins = 0.5 * (top_margin + bottom_margin); + widget_focal_point.ry() += new_margins - top_margin; + } else if ((top_margin < 0.0) && (bottom_margin > 0.0)) { + // Move image down so that either top_margin or bottom_margin + // becomes zero, whichever requires less movement. + const double movement = std::min(std::fabs(top_margin), std::fabs(bottom_margin)); + widget_focal_point.ry() += movement; + } else if ((bottom_margin < 0.0) && (top_margin > 0.0)) { + // Move image up so that either top_margin or bottom_margin + // becomes zero, whichever requires less movement. + const double movement = std::min(std::fabs(top_margin), std::fabs(bottom_margin)); + widget_focal_point.ry() -= movement; + } + + return widget_focal_point; } // ImageViewBase::getIdealWidgetFocalPoint void ImageViewBase::setNewWidgetFP(const QPointF widget_fp, const bool update) { - if (widget_fp != m_widgetFocalPoint) { - m_widgetFocalPoint = widget_fp; - updateWidgetTransform(); - if (update) { - this->update(); - } + if (widget_fp != m_widgetFocalPoint) { + m_widgetFocalPoint = widget_fp; + updateWidgetTransform(); + if (update) { + this->update(); } + } } /** @@ -839,175 +840,175 @@ void ImageViewBase::setNewWidgetFP(const QPointF widget_fp, const bool update) { * has changed. */ void ImageViewBase::adjustAndSetNewWidgetFP(const QPointF proposed_widget_fp, const bool update) { - // We first apply the proposed focal point, and only then - // calculate the ideal one. That's done because - // the ideal focal point is the current focal point when - // no widget space is wasted (image covers the whole widget). - // We don't want the ideal focal point to be equal to the current - // one, as that would disallow any movements. - const QPointF old_widget_fp(m_widgetFocalPoint); - setNewWidgetFP(proposed_widget_fp, update); - - const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); - - const QPointF towards_ideal(ideal_widget_fp - old_widget_fp); - const QPointF towards_proposed(proposed_widget_fp - old_widget_fp); - - QPointF movement(towards_proposed); - - // Horizontal movement. - if (towards_ideal.x() * towards_proposed.x() < 0.0) { - // Wrong direction - no movement at all. - movement.setX(0.0); - } else if (std::fabs(towards_proposed.x()) > std::fabs(towards_ideal.x())) { - // Too much movement - limit it. - movement.setX(towards_ideal.x()); - } - // Vertical movement. - if (towards_ideal.y() * towards_proposed.y() < 0.0) { - // Wrong direction - no movement at all. - movement.setY(0.0); - } else if (std::fabs(towards_proposed.y()) > std::fabs(towards_ideal.y())) { - // Too much movement - limit it. - movement.setY(towards_ideal.y()); - } - - const QPointF adjusted_widget_fp(old_widget_fp + movement); - if (adjusted_widget_fp != m_widgetFocalPoint) { - m_widgetFocalPoint = adjusted_widget_fp; - updateWidgetTransform(); - if (update) { - this->update(); - } + // We first apply the proposed focal point, and only then + // calculate the ideal one. That's done because + // the ideal focal point is the current focal point when + // no widget space is wasted (image covers the whole widget). + // We don't want the ideal focal point to be equal to the current + // one, as that would disallow any movements. + const QPointF old_widget_fp(m_widgetFocalPoint); + setNewWidgetFP(proposed_widget_fp, update); + + const QPointF ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); + + const QPointF towards_ideal(ideal_widget_fp - old_widget_fp); + const QPointF towards_proposed(proposed_widget_fp - old_widget_fp); + + QPointF movement(towards_proposed); + + // Horizontal movement. + if (towards_ideal.x() * towards_proposed.x() < 0.0) { + // Wrong direction - no movement at all. + movement.setX(0.0); + } else if (std::fabs(towards_proposed.x()) > std::fabs(towards_ideal.x())) { + // Too much movement - limit it. + movement.setX(towards_ideal.x()); + } + // Vertical movement. + if (towards_ideal.y() * towards_proposed.y() < 0.0) { + // Wrong direction - no movement at all. + movement.setY(0.0); + } else if (std::fabs(towards_proposed.y()) > std::fabs(towards_ideal.y())) { + // Too much movement - limit it. + movement.setY(towards_ideal.y()); + } + + const QPointF adjusted_widget_fp(old_widget_fp + movement); + if (adjusted_widget_fp != m_widgetFocalPoint) { + m_widgetFocalPoint = adjusted_widget_fp; + updateWidgetTransform(); + if (update) { + this->update(); } + } } // ImageViewBase::adjustAndSetNewWidgetFP /** * Returns the center point of the available display area. */ QPointF ImageViewBase::centeredWidgetFocalPoint() const { - return maxViewportRect().center(); + return maxViewportRect().center(); } void ImageViewBase::setWidgetFocalPointWithoutMoving(const QPointF new_widget_fp) { - m_widgetFocalPoint = new_widget_fp; - m_pixmapFocalPoint = m_virtualToImage.map(m_widgetToVirtual.map(m_widgetFocalPoint)); + m_widgetFocalPoint = new_widget_fp; + m_pixmapFocalPoint = m_virtualToImage.map(m_widgetToVirtual.map(m_widgetFocalPoint)); } /** * Returns true if m_hqPixmap is valid and up to date. */ bool ImageViewBase::validateHqPixmap() const { - if (!m_hqTransformEnabled) { - return false; - } + if (!m_hqTransformEnabled) { + return false; + } - if (m_hqPixmap.isNull()) { - return false; - } + if (m_hqPixmap.isNull()) { + return false; + } - if (m_hqSourceId != m_image.cacheKey()) { - return false; - } + if (m_hqSourceId != m_image.cacheKey()) { + return false; + } - if (m_hqXform != m_imageToVirtual * m_virtualToWidget) { - return false; - } + if (m_hqXform != m_imageToVirtual * m_virtualToWidget) { + return false; + } - return true; + return true; } void ImageViewBase::scheduleHqVersionRebuild() { - const QTransform xform(m_imageToVirtual * m_virtualToWidget); - - if (!m_timer.isActive() || (m_potentialHqXform != xform)) { - if (m_ptrHqTransformTask) { - m_ptrHqTransformTask->cancel(); - m_ptrHqTransformTask.reset(); - } - m_potentialHqXform = xform; + const QTransform xform(m_imageToVirtual * m_virtualToWidget); + + if (!m_timer.isActive() || (m_potentialHqXform != xform)) { + if (m_hqTransformTask) { + m_hqTransformTask->cancel(); + m_hqTransformTask.reset(); } - m_timer.start(); + m_potentialHqXform = xform; + } + m_timer.start(); } void ImageViewBase::initiateBuildingHqVersion() { - if (validateHqPixmap()) { - return; - } + if (validateHqPixmap()) { + return; + } - m_hqPixmap = QPixmap(); + m_hqPixmap = QPixmap(); - if (m_ptrHqTransformTask) { - m_ptrHqTransformTask->cancel(); - m_ptrHqTransformTask.reset(); - } + if (m_hqTransformTask) { + m_hqTransformTask->cancel(); + m_hqTransformTask.reset(); + } - const QTransform xform(m_imageToVirtual * m_virtualToWidget); - const auto task = make_intrusive(this, m_image, xform, viewport()->size()); + const QTransform xform(m_imageToVirtual * m_virtualToWidget); + const auto task = make_intrusive(this, m_image, xform, viewport()->size()); - backgroundExecutor().enqueueTask(task); + backgroundExecutor().enqueueTask(task); - m_ptrHqTransformTask = task; - m_hqXform = xform; - m_hqSourceId = m_image.cacheKey(); + m_hqTransformTask = task; + m_hqXform = xform; + m_hqSourceId = m_image.cacheKey(); } /** * Gets called from HqTransformationTask::Result. */ void ImageViewBase::hqVersionBuilt(const QPoint& origin, const QImage& image) { - if (!m_hqTransformEnabled) { - return; - } - - m_hqPixmap = QPixmap::fromImage(image); - m_hqPixmapPos = origin; - m_ptrHqTransformTask.reset(); - update(); + if (!m_hqTransformEnabled) { + return; + } + + m_hqPixmap = QPixmap::fromImage(image); + m_hqPixmapPos = origin; + m_hqTransformTask.reset(); + update(); } void ImageViewBase::updateStatusTipAndCursor() { - updateStatusTip(); - updateCursor(); + updateStatusTip(); + updateCursor(); } void ImageViewBase::updateStatusTip() { - ensureStatusTip(m_interactionState.statusTip()); + ensureStatusTip(m_interactionState.statusTip()); } void ImageViewBase::updateCursor() { - viewport()->setCursor(m_interactionState.cursor()); + viewport()->setCursor(m_interactionState.cursor()); } void ImageViewBase::maybeQueueRedraw() { - if (m_interactionState.redrawRequested()) { - m_interactionState.setRedrawRequested(false); - update(); - } + if (m_interactionState.redrawRequested()) { + m_interactionState.setRedrawRequested(false); + update(); + } } BackgroundExecutor& ImageViewBase::backgroundExecutor() { - static BackgroundExecutor executor; + static BackgroundExecutor executor; - return executor; + return executor; } void ImageViewBase::updateCursorPos(const QPointF& pos) { - if (pos != m_cursorPos) { - m_cursorPos = pos; - if (!m_cursorTrackerTimer.isActive()) { - // report cursor pos once in 150 msec - m_cursorTrackerTimer.start(150); - } + if (pos != m_cursorPos) { + m_cursorPos = pos; + if (!m_cursorTrackerTimer.isActive()) { + // report cursor pos once in 150 msec + m_cursorTrackerTimer.start(150); } + } } void ImageViewBase::updatePhysSize() { - m_infoProvider.setPhysSize(m_virtualImageCropArea.boundingRect().size()); + m_infoProvider.setPhysSize(m_virtualImageCropArea.boundingRect().size()); } ImageViewInfoProvider& ImageViewBase::infoProvider() { - return m_infoProvider; + return m_infoProvider; } /*==================== ImageViewBase::HqTransformTask ======================*/ @@ -1016,79 +1017,77 @@ ImageViewBase::HqTransformTask::HqTransformTask(ImageViewBase* image_view, const QImage& image, const QTransform& xform, const QSize& target_size) - : m_ptrResult(new Result(image_view)), m_image(image), m_xform(xform), m_targetSize(target_size) { -} + : m_result(new Result(image_view)), m_image(image), m_xform(xform), m_targetSize(target_size) {} intrusive_ptr> ImageViewBase::HqTransformTask::operator()() { - if (isCancelled()) { - return nullptr; - } + if (isCancelled()) { + return nullptr; + } - const QRect target_rect( - m_xform.map(QRectF(m_image.rect())).boundingRect().toRect().intersected(QRect(QPoint(0, 0), m_targetSize))); + const QRect target_rect( + m_xform.map(QRectF(m_image.rect())).boundingRect().toRect().intersected(QRect(QPoint(0, 0), m_targetSize))); - QImage hq_image( - transform(m_image, m_xform, target_rect, OutsidePixels::assumeWeakColor(Qt::white), QSizeF(0.0, 0.0))); + QImage hq_image( + transform(m_image, m_xform, target_rect, OutsidePixels::assumeWeakColor(Qt::white), QSizeF(0.0, 0.0))); - // In many cases m_image and therefore hq_image are grayscale with - // a palette, but given that hq_image will be converted to a QPixmap - // on the GUI thread, it's better to convert it to RGB as a preparation - // step while we are still in a background thread. - hq_image = hq_image.convertToFormat(hq_image.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied - : QImage::Format_RGB32); + // In many cases m_image and therefore hq_image are grayscale with + // a palette, but given that hq_image will be converted to a QPixmap + // on the GUI thread, it's better to convert it to RGB as a preparation + // step while we are still in a background thread. + hq_image = hq_image.convertToFormat(hq_image.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32); - m_ptrResult->setData(target_rect.topLeft(), hq_image); + m_result->setData(target_rect.topLeft(), hq_image); - return m_ptrResult; + return m_result; } /*================ ImageViewBase::HqTransformTask::Result ================*/ -ImageViewBase::HqTransformTask::Result::Result(ImageViewBase* image_view) : m_ptrImageView(image_view) { -} +ImageViewBase::HqTransformTask::Result::Result(ImageViewBase* image_view) : m_imageView(image_view) {} void ImageViewBase::HqTransformTask::Result::setData(const QPoint& origin, const QImage& hq_image) { - m_hqImage = hq_image; - m_origin = origin; + m_hqImage = hq_image; + m_origin = origin; } void ImageViewBase::HqTransformTask::Result::operator()() { - if (m_ptrImageView && !isCancelled()) { - m_ptrImageView->hqVersionBuilt(m_origin, m_hqImage); - } + if (m_imageView && !isCancelled()) { + m_imageView->hqVersionBuilt(m_origin, m_hqImage); + } } /*================= ImageViewBase::TempFocalPointAdjuster =================*/ ImageViewBase::TempFocalPointAdjuster::TempFocalPointAdjuster(ImageViewBase& obj) - : m_rObj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { - obj.setWidgetFocalPointWithoutMoving(obj.centeredWidgetFocalPoint()); + : m_obj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { + obj.setWidgetFocalPointWithoutMoving(obj.centeredWidgetFocalPoint()); } ImageViewBase::TempFocalPointAdjuster::TempFocalPointAdjuster(ImageViewBase& obj, const QPointF temp_widget_fp) - : m_rObj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { - obj.setWidgetFocalPointWithoutMoving(temp_widget_fp); + : m_obj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { + obj.setWidgetFocalPointWithoutMoving(temp_widget_fp); } ImageViewBase::TempFocalPointAdjuster::~TempFocalPointAdjuster() { - m_rObj.setWidgetFocalPointWithoutMoving(m_origWidgetFP); + m_obj.setWidgetFocalPointWithoutMoving(m_origWidgetFP); } /*================== ImageViewBase::TransformChangeWatcher ================*/ ImageViewBase::TransformChangeWatcher::TransformChangeWatcher(ImageViewBase& owner) - : m_rOwner(owner), - m_imageToVirtual(owner.m_imageToVirtual), - m_virtualToWidget(owner.m_virtualToWidget), - m_virtualDisplayArea(owner.m_virtualDisplayArea) { - ++m_rOwner.m_transformChangeWatchersActive; + : m_owner(owner), + m_imageToVirtual(owner.m_imageToVirtual), + m_virtualToWidget(owner.m_virtualToWidget), + m_virtualDisplayArea(owner.m_virtualDisplayArea) { + ++m_owner.m_transformChangeWatchersActive; } ImageViewBase::TransformChangeWatcher::~TransformChangeWatcher() { - if (--m_rOwner.m_transformChangeWatchersActive == 0) { - if ((m_imageToVirtual != m_rOwner.m_imageToVirtual) || (m_virtualToWidget != m_rOwner.m_virtualToWidget) - || (m_virtualDisplayArea != m_rOwner.m_virtualDisplayArea)) { - m_rOwner.transformChanged(); - } + if (--m_owner.m_transformChangeWatchersActive == 0) { + if ((m_imageToVirtual != m_owner.m_imageToVirtual) || (m_virtualToWidget != m_owner.m_virtualToWidget) + || (m_virtualDisplayArea != m_owner.m_virtualDisplayArea)) { + m_owner.transformChanged(); } + } } diff --git a/ImageViewBase.h b/ImageViewBase.h index 34e227773..14923f040 100644 --- a/ImageViewBase.h +++ b/ImageViewBase.h @@ -19,24 +19,24 @@ #ifndef IMAGEVIEWBASE_H_ #define IMAGEVIEWBASE_H_ -#include "Margins.h" -#include "intrusive_ptr.h" -#include "InteractionHandler.h" -#include "InteractionState.h" -#include "ImagePixmapUnion.h" -#include "ImageViewInfoProvider.h" -#include -#include #include -#include #include -#include -#include +#include #include #include -#include #include +#include +#include +#include +#include +#include #include +#include "ImagePixmapUnion.h" +#include "ImageViewInfoProvider.h" +#include "InteractionHandler.h" +#include "InteractionState.h" +#include "Margins.h" +#include "intrusive_ptr.h" class QPainter; class BackgroundExecutor; @@ -59,440 +59,412 @@ class ImagePresentation; * \see m_pixmapToImage, m_imageToVirt, m_virtualToWidget, m_widgetToVirtual. */ class ImageViewBase : public QAbstractScrollArea { - Q_OBJECT -public: - enum FocalPointMode { CENTER_IF_FITS, DONT_CENTER }; - - /** - * \brief ImageViewBase constructor. - * - * \param image The image to display. - * \param downscaled_version The downscaled version of \p image. - * If it's null, it will be created automatically. - * The exact scale doesn't matter. - * The whole idea of having a downscaled version is - * to speed up real-time rendering of high-resolution - * images. Note that the delayed high quality transform - * operates on the original image, not the downscaled one. - * \param presentation Specifies transformation from image - * pixel coordinates to virtual image coordinates, along - * with some other properties. - * \param margins Reserve extra space near the widget borders. - * The units are widget pixels. This reserved area may - * be used for custom drawing or custom controls. - */ - ImageViewBase(const QImage& image, - const ImagePixmapUnion& downscaled_version, - const ImagePresentation& presentation, - const Margins& margins = Margins()); - - ~ImageViewBase() override; - - /** - * The idea behind this accessor is being able to share a single - * downscaled pixmap between multiple image views. - */ - const QPixmap& downscaledPixmap() const { - return m_pixmap; - } - - /** - * \brief Enable or disable the high-quality transform. - */ - void hqTransformSetEnabled(bool enabled); - - /** - * \brief A stand-alone function to create a downscaled image - * to be passed to the constructor. - * - * The point of using this function instead of letting - * the constructor do the job is that this function may - * be called from a background thread, while the constructor - * can't. - * - * \param image The input image, not null, and with DPI set correctly. - * \return The image downscaled by an unspecified degree. - */ - static QImage createDownscaledImage(const QImage& image); - - InteractionHandler& rootInteractionHandler() { - return m_rootInteractionHandler; - } - - InteractionState& interactionState() { - return m_interactionState; - } - - const InteractionState& interactionState() const { - return m_interactionState; - } - - const QTransform& imageToVirtual() const { - return m_imageToVirtual; - } - - const QTransform& virtualToImage() const { - return m_virtualToImage; - } - - const QTransform& virtualToWidget() const { - return m_virtualToWidget; - } - - const QTransform& widgetToVirtual() const { - return m_widgetToVirtual; - } - - QTransform imageToWidget() const { - return m_imageToVirtual * m_virtualToWidget; - } - - QTransform widgetToImage() const { - return m_widgetToVirtual * m_virtualToImage; - } - - void update() { - viewport()->update(); - } - - const QRectF& virtualDisplayRect() const { - return m_virtualDisplayArea; - } - - /** - * Get the bounding box of the image as it appears on the screen, - * in widget coordinates. - */ - QRectF getOccupiedWidgetRect() const; - - /** - * \brief A better version of setStatusTip(). - * - * Unlike setStatusTip(), this method will display the tooltip - * immediately, not when the mouse enters the widget next time. - */ - void ensureStatusTip(const QString& status_tip); - - /** - * \brief Get the focal point in widget coordinates. - * - * The typical usage pattern for this function is: - * \code - * QPointF fp(obj.getWidgetFocalPoint()); - * obj.setWidgetFocalPoint(fp + delta); - * \endcode - * As a result, the image will be moved by delta widget pixels. - */ - QPointF getWidgetFocalPoint() const { - return m_widgetFocalPoint; - } - - /** - * \brief Set the focal point in widget coordinates. - * - * This one may be used for unrestricted dragging (with Shift button). - * - * \see getWidgetFocalPoint() - */ - void setWidgetFocalPoint(const QPointF& widget_fp); - - /** - * \brief Set the focal point in widget coordinates, after adjustring - * it to avoid wasting of widget space. - * - * This one may be used for resticted dragging (the default one in ST). - * - * \see getWidgetFocalPoint() - * \see setWidgetFocalPoint() - */ - void adjustAndSetWidgetFocalPoint(const QPointF& widget_fp); - - /** - * \brief Sets the widget focal point and recalculates the pixmap focal - * focal point so that the image is not moved on screen. - */ - void setWidgetFocalPointWithoutMoving(QPointF new_widget_fp); - - /** - * \brief Updates image-to-virtual and recalculates - * virtual-to-widget transformations. - */ - void updateTransform(const ImagePresentation& presentation); + Q_OBJECT + public: + enum FocalPointMode { CENTER_IF_FITS, DONT_CENTER }; + + /** + * \brief ImageViewBase constructor. + * + * \param image The image to display. + * \param downscaled_version The downscaled version of \p image. + * If it's null, it will be created automatically. + * The exact scale doesn't matter. + * The whole idea of having a downscaled version is + * to speed up real-time rendering of high-resolution + * images. Note that the delayed high quality transform + * operates on the original image, not the downscaled one. + * \param presentation Specifies transformation from image + * pixel coordinates to virtual image coordinates, along + * with some other properties. + * \param margins Reserve extra space near the widget borders. + * The units are widget pixels. This reserved area may + * be used for custom drawing or custom controls. + */ + ImageViewBase(const QImage& image, + const ImagePixmapUnion& downscaled_version, + const ImagePresentation& presentation, + const Margins& margins = Margins()); + + ~ImageViewBase() override; + + /** + * The idea behind this accessor is being able to share a single + * downscaled pixmap between multiple image views. + */ + const QPixmap& downscaledPixmap() const { return m_pixmap; } + + /** + * \brief Enable or disable the high-quality transform. + */ + void hqTransformSetEnabled(bool enabled); + + /** + * \brief A stand-alone function to create a downscaled image + * to be passed to the constructor. + * + * The point of using this function instead of letting + * the constructor do the job is that this function may + * be called from a background thread, while the constructor + * can't. + * + * \param image The input image, not null, and with DPI set correctly. + * \return The image downscaled by an unspecified degree. + */ + static QImage createDownscaledImage(const QImage& image); + + InteractionHandler& rootInteractionHandler() { return m_rootInteractionHandler; } + + InteractionState& interactionState() { return m_interactionState; } + + const InteractionState& interactionState() const { return m_interactionState; } + + const QTransform& imageToVirtual() const { return m_imageToVirtual; } + + const QTransform& virtualToImage() const { return m_virtualToImage; } + + const QTransform& virtualToWidget() const { return m_virtualToWidget; } + + const QTransform& widgetToVirtual() const { return m_widgetToVirtual; } + + QTransform imageToWidget() const { return m_imageToVirtual * m_virtualToWidget; } + + QTransform widgetToImage() const { return m_widgetToVirtual * m_virtualToImage; } + + void update() { viewport()->update(); } + + const QRectF& virtualDisplayRect() const { return m_virtualDisplayArea; } + + /** + * Get the bounding box of the image as it appears on the screen, + * in widget coordinates. + */ + QRectF getOccupiedWidgetRect() const; + + /** + * \brief A better version of setStatusTip(). + * + * Unlike setStatusTip(), this method will display the tooltip + * immediately, not when the mouse enters the widget next time. + */ + void ensureStatusTip(const QString& status_tip); + + /** + * \brief Get the focal point in widget coordinates. + * + * The typical usage pattern for this function is: + * \code + * QPointF fp(obj.getWidgetFocalPoint()); + * obj.setWidgetFocalPoint(fp + delta); + * \endcode + * As a result, the image will be moved by delta widget pixels. + */ + QPointF getWidgetFocalPoint() const { return m_widgetFocalPoint; } + + /** + * \brief Set the focal point in widget coordinates. + * + * This one may be used for unrestricted dragging (with Shift button). + * + * \see getWidgetFocalPoint() + */ + void setWidgetFocalPoint(const QPointF& widget_fp); + + /** + * \brief Set the focal point in widget coordinates, after adjustring + * it to avoid wasting of widget space. + * + * This one may be used for resticted dragging (the default one in ST). + * + * \see getWidgetFocalPoint() + * \see setWidgetFocalPoint() + */ + void adjustAndSetWidgetFocalPoint(const QPointF& widget_fp); + + /** + * \brief Sets the widget focal point and recalculates the pixmap focal + * focal point so that the image is not moved on screen. + */ + void setWidgetFocalPointWithoutMoving(QPointF new_widget_fp); + + /** + * \brief Updates image-to-virtual and recalculates + * virtual-to-widget transformations. + */ + void updateTransform(const ImagePresentation& presentation); - /** - * \brief Same as updateTransform(), but adjusts the focal point - * to improve screen space usage. - */ - void updateTransformAndFixFocalPoint(const ImagePresentation& presentation, FocalPointMode mode); + /** + * \brief Same as updateTransform(), but adjusts the focal point + * to improve screen space usage. + */ + void updateTransformAndFixFocalPoint(const ImagePresentation& presentation, FocalPointMode mode); - /** - * \brief Same as updateTransform(), but preserves the visual image scale. - */ - void updateTransformPreservingScale(const ImagePresentation& presentation); + /** + * \brief Same as updateTransform(), but preserves the visual image scale. + */ + void updateTransformPreservingScale(const ImagePresentation& presentation); - /** - * \brief Sets the zoom level. - * - * Zoom level 1.0 means such a zoom that makes the image fit the widget. - * Zooming will take into account the current widget and pixmap focal - * points. To zoom to a specific point, for example the mouse position, - * call setWidgetFocalPointWithoutMoving() first. - */ - void setZoomLevel(double zoom); + /** + * \brief Sets the zoom level. + * + * Zoom level 1.0 means such a zoom that makes the image fit the widget. + * Zooming will take into account the current widget and pixmap focal + * points. To zoom to a specific point, for example the mouse position, + * call setWidgetFocalPointWithoutMoving() first. + */ + void setZoomLevel(double zoom); - /** - * \brief Returns the current zoom level. - * \see setZoomLevel() - */ - double zoomLevel() const { - return m_zoom; - } + /** + * \brief Returns the current zoom level. + * \see setZoomLevel() + */ + double zoomLevel() const { return m_zoom; } - /** - * The image is considered ideally positioned when as little as possible - * screen space is wasted. - * - * \param pixel_length The euclidean distance in widget pixels to move the image. - * Will be clipped if it's more than required to reach the ideal position. - */ - void moveTowardsIdealPosition(double pixel_length); + /** + * The image is considered ideally positioned when as little as possible + * screen space is wasted. + * + * \param pixel_length The euclidean distance in widget pixels to move the image. + * Will be clipped if it's more than required to reach the ideal position. + */ + void moveTowardsIdealPosition(double pixel_length); - static BackgroundExecutor& backgroundExecutor(); + static BackgroundExecutor& backgroundExecutor(); - ImageViewInfoProvider& infoProvider(); + ImageViewInfoProvider& infoProvider(); -protected: - void paintEvent(QPaintEvent* event) override; + protected: + void paintEvent(QPaintEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; - void mouseDoubleClickEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void wheelEvent(QWheelEvent* event) override; + void wheelEvent(QWheelEvent* event) override; - void contextMenuEvent(QContextMenuEvent* event) override; + void contextMenuEvent(QContextMenuEvent* event) override; - void resizeEvent(QResizeEvent* event) override; + void resizeEvent(QResizeEvent* event) override; - void enterEvent(QEvent* event) override; + void enterEvent(QEvent* event) override; - void leaveEvent(QEvent* event) override; + void leaveEvent(QEvent* event) override; - void showEvent(QShowEvent* event) override; + void showEvent(QShowEvent* event) override; - /** - * Returns the maximum viewport size (as if scrollbars are hidden) - * reduced by margins. - */ - QRectF maxViewportRect() const; + /** + * Returns the maximum viewport size (as if scrollbars are hidden) + * reduced by margins. + */ + QRectF maxViewportRect() const; - virtual void updateCursorPos(const QPointF& pos); + virtual void updateCursorPos(const QPointF& pos); - virtual void updatePhysSize(); + virtual void updatePhysSize(); -private slots: + private slots: - void initiateBuildingHqVersion(); + void initiateBuildingHqVersion(); - void updateScrollBars(); + void updateScrollBars(); - void reactToScrollBars(); + void reactToScrollBars(); -private: - class HqTransformTask; - class TempFocalPointAdjuster; + private: + class HqTransformTask; + class TempFocalPointAdjuster; - class TransformChangeWatcher; + class TransformChangeWatcher; - QRectF dynamicViewportRect() const; + QRectF dynamicViewportRect() const; - void transformChanged(); + void transformChanged(); - void updateWidgetTransform(); + void updateWidgetTransform(); - void updateWidgetTransformAndFixFocalPoint(FocalPointMode mode); + void updateWidgetTransformAndFixFocalPoint(FocalPointMode mode); - QPointF getIdealWidgetFocalPoint(FocalPointMode mode) const; + QPointF getIdealWidgetFocalPoint(FocalPointMode mode) const; - void setNewWidgetFP(QPointF widget_fp, bool update = false); + void setNewWidgetFP(QPointF widget_fp, bool update = false); - void adjustAndSetNewWidgetFP(QPointF proposed_widget_fp, bool update = false); + void adjustAndSetNewWidgetFP(QPointF proposed_widget_fp, bool update = false); - QPointF centeredWidgetFocalPoint() const; + QPointF centeredWidgetFocalPoint() const; - bool validateHqPixmap() const; + bool validateHqPixmap() const; - void scheduleHqVersionRebuild(); + void scheduleHqVersionRebuild(); - void hqVersionBuilt(const QPoint& origin, const QImage& image); + void hqVersionBuilt(const QPoint& origin, const QImage& image); - void updateStatusTipAndCursor(); + void updateStatusTipAndCursor(); - void updateStatusTip(); + void updateStatusTip(); - void updateCursor(); + void updateCursor(); - void maybeQueueRedraw(); + void maybeQueueRedraw(); - InteractionHandler m_rootInteractionHandler; + InteractionHandler m_rootInteractionHandler; - InteractionState m_interactionState; + InteractionState m_interactionState; - /** - * The client-side image. Used to build a high-quality version - * for delayed rendering. - */ - QImage m_image; + /** + * The client-side image. Used to build a high-quality version + * for delayed rendering. + */ + QImage m_image; - /** - * This timer is used for delaying the construction of - * a high quality image version. - */ - QTimer m_timer; + /** + * This timer is used for delaying the construction of + * a high quality image version. + */ + QTimer m_timer; - /** - * The image handle. Note that the actual data of a QPixmap lives - * in another process on most platforms. - */ - QPixmap m_pixmap; + /** + * The image handle. Note that the actual data of a QPixmap lives + * in another process on most platforms. + */ + QPixmap m_pixmap; - /** - * The high quality, pre-transformed version of m_pixmap. - */ - QPixmap m_hqPixmap; + /** + * The high quality, pre-transformed version of m_pixmap. + */ + QPixmap m_hqPixmap; - /** - * The position, in widget coordinates, where m_hqPixmap is to be drawn. - */ - QPoint m_hqPixmapPos; + /** + * The position, in widget coordinates, where m_hqPixmap is to be drawn. + */ + QPoint m_hqPixmapPos; - /** - * The transformation used to build m_hqPixmap. - * It's used to detect if m_hqPixmap needs to be rebuild. - */ - QTransform m_hqXform; + /** + * The transformation used to build m_hqPixmap. + * It's used to detect if m_hqPixmap needs to be rebuild. + */ + QTransform m_hqXform; - /** - * Used to check if we need to extend the delay before building m_hqPixmap. - */ - QTransform m_potentialHqXform; + /** + * Used to check if we need to extend the delay before building m_hqPixmap. + */ + QTransform m_potentialHqXform; - /** - * The ID (QImage::cacheKey()) of the image that was used - * to build m_hqPixmap. It's used to detect if m_hqPixmap - * needs to be rebuilt. - */ - qint64 m_hqSourceId; + /** + * The ID (QImage::cacheKey()) of the image that was used + * to build m_hqPixmap. It's used to detect if m_hqPixmap + * needs to be rebuilt. + */ + qint64 m_hqSourceId; - /** - * The pending (if any) high quality transformation task. - */ - intrusive_ptr m_ptrHqTransformTask; + /** + * The pending (if any) high quality transformation task. + */ + intrusive_ptr m_hqTransformTask; - /** - * Transformation from m_pixmap coordinates to m_image coordinates. - */ - QTransform m_pixmapToImage; + /** + * Transformation from m_pixmap coordinates to m_image coordinates. + */ + QTransform m_pixmapToImage; - /** - * The area of the virtual image to be displayed. - * Everything outside of it will be cropped. - */ - QPolygonF m_virtualImageCropArea; + /** + * The area of the virtual image to be displayed. + * Everything outside of it will be cropped. + */ + QPolygonF m_virtualImageCropArea; - /** - * The area in virtual image coordinates to be displayed. - * The idea is that it can be larger than m_virtualImageCropArea - * to reserve space for custom drawing or controls. - */ - QRectF m_virtualDisplayArea; + /** + * The area in virtual image coordinates to be displayed. + * The idea is that it can be larger than m_virtualImageCropArea + * to reserve space for custom drawing or controls. + */ + QRectF m_virtualDisplayArea; - /** - * A transformation from original to virtual image coordinates. - */ - QTransform m_imageToVirtual; - - /** - * A transformation from virtual to original image coordinates. - */ - QTransform m_virtualToImage; - - /** - * Transformation from virtual image coordinates to widget coordinates. - */ - QTransform m_virtualToWidget; - - /** - * Transformation from widget coordinates to virtual image coordinates. - */ - QTransform m_widgetToVirtual; - - /** - * Transforms scroll bar values to corresponding positions of the display - * area (its central point) in widget coordinates. - */ - QTransform m_scrollTransform; - - /** - * An arbitrary point in widget coordinates that corresponds - * to m_pixmapFocalPoint in m_pixmap coordinates. - * Moving m_widgetFocalPoint followed by updateWidgetTransform() - * will cause the image to move on screen. - */ - QPointF m_widgetFocalPoint; - - /** - * An arbitrary point in m_pixmap coordinates that corresponds - * to m_widgetFocalPoint in widget coordinates. - * Unlike m_widgetFocalPoint, this one is not supposed to be - * moved independently. It's supposed to moved together with - * m_widgetFocalPoint for zooming into a specific position. - */ - QPointF m_pixmapFocalPoint; - - /** - * Used to distinguish between resizes induced by scrollbars (dis)appearing - * and other factors. - */ - QSize m_lastMaximumViewportSize; - - /** - * The number of pixels to be left blank at each side of the widget. - */ - Margins m_margins; - - /** - * The zoom factor. A value of 1.0 corresponds to fit-to-widget zoom. - */ - double m_zoom; - - /** - * This timer is used for tracking the cursor position. - */ - QTimer m_cursorTrackerTimer; - - /** - * Current mouse pos in widget coordinates. - */ - QPointF m_cursorPos; - - int m_transformChangeWatchersActive; - - int m_ignoreScrollEvents; - - int m_ignoreResizeEvents; - - bool m_hqTransformEnabled; - - ImageViewInfoProvider m_infoProvider; + /** + * A transformation from original to virtual image coordinates. + */ + QTransform m_imageToVirtual; + + /** + * A transformation from virtual to original image coordinates. + */ + QTransform m_virtualToImage; + + /** + * Transformation from virtual image coordinates to widget coordinates. + */ + QTransform m_virtualToWidget; + + /** + * Transformation from widget coordinates to virtual image coordinates. + */ + QTransform m_widgetToVirtual; + + /** + * Transforms scroll bar values to corresponding positions of the display + * area (its central point) in widget coordinates. + */ + QTransform m_scrollTransform; + + /** + * An arbitrary point in widget coordinates that corresponds + * to m_pixmapFocalPoint in m_pixmap coordinates. + * Moving m_widgetFocalPoint followed by updateWidgetTransform() + * will cause the image to move on screen. + */ + QPointF m_widgetFocalPoint; + + /** + * An arbitrary point in m_pixmap coordinates that corresponds + * to m_widgetFocalPoint in widget coordinates. + * Unlike m_widgetFocalPoint, this one is not supposed to be + * moved independently. It's supposed to moved together with + * m_widgetFocalPoint for zooming into a specific position. + */ + QPointF m_pixmapFocalPoint; + + /** + * Used to distinguish between resizes induced by scrollbars (dis)appearing + * and other factors. + */ + QSize m_lastMaximumViewportSize; + + /** + * The number of pixels to be left blank at each side of the widget. + */ + Margins m_margins; + + /** + * The zoom factor. A value of 1.0 corresponds to fit-to-widget zoom. + */ + double m_zoom; + + /** + * This timer is used for tracking the cursor position. + */ + QTimer m_cursorTrackerTimer; + + /** + * Current mouse pos in widget coordinates. + */ + QPointF m_cursorPos; + + int m_transformChangeWatchersActive; + + int m_ignoreScrollEvents; + + int m_ignoreResizeEvents; + + bool m_hqTransformEnabled; + + ImageViewInfoProvider m_infoProvider; }; diff --git a/ImageViewInfoObserver.h b/ImageViewInfoObserver.h index f595abc0c..983c33767 100644 --- a/ImageViewInfoObserver.h +++ b/ImageViewInfoObserver.h @@ -10,19 +10,18 @@ class ImageViewInfoProvider; class Dpi; class ImageViewInfoObserver { -public: - virtual ~ImageViewInfoObserver() = default; + public: + virtual ~ImageViewInfoObserver() = default; - virtual void updateMousePos(const QPointF& mousePos) = 0; + virtual void updateMousePos(const QPointF& mousePos) = 0; - virtual void updatePhysSize(const QSizeF& physSize) = 0; + virtual void updatePhysSize(const QSizeF& physSize) = 0; - virtual void updateDpi(const Dpi& dpi) = 0; + virtual void updateDpi(const Dpi& dpi) = 0; - virtual void clearImageViewInfo() = 0; + virtual void clearImageViewInfo() = 0; - virtual void setInfoProvider(ImageViewInfoProvider* infoProvider) { - } + virtual void setInfoProvider(ImageViewInfoProvider* infoProvider) {} }; diff --git a/ImageViewInfoProvider.cpp b/ImageViewInfoProvider.cpp index 019d470a9..2b03df669 100644 --- a/ImageViewInfoProvider.cpp +++ b/ImageViewInfoProvider.cpp @@ -1,61 +1,61 @@ -#include -#include #include "ImageViewInfoProvider.h" +#include +#include #include "Units.h" -ImageViewInfoProvider::ImageViewInfoProvider(const Dpi& dpi) : dpi(dpi){}; +ImageViewInfoProvider::ImageViewInfoProvider(const Dpi& dpi) : m_dpi(dpi){}; ImageViewInfoProvider::~ImageViewInfoProvider() { - for (ImageViewInfoObserver* observer : observers) { - observer->clearImageViewInfo(); - } + for (ImageViewInfoObserver* observer : m_observers) { + observer->clearImageViewInfo(); + } } void ImageViewInfoProvider::attachObserver(ImageViewInfoObserver* observer) { - observer->updateDpi(dpi); - observer->updatePhysSize(physSize); - observer->updateMousePos(mousePos); + observer->updateDpi(m_dpi); + observer->updatePhysSize(m_physSize); + observer->updateMousePos(m_mousePos); - observers.push_back(observer); + m_observers.push_back(observer); } void ImageViewInfoProvider::detachObserver(ImageViewInfoObserver* observer) { - observer->clearImageViewInfo(); + observer->clearImageViewInfo(); - observers.remove(observer); + m_observers.remove(observer); } void ImageViewInfoProvider::setPhysSize(const QSizeF& physSize) { - ImageViewInfoProvider::physSize = physSize; - physSizeChanged(physSize); + ImageViewInfoProvider::m_physSize = physSize; + physSizeChanged(physSize); } void ImageViewInfoProvider::setMousePos(const QPointF& mousePos) { - ImageViewInfoProvider::mousePos = mousePos; - mousePosChanged(mousePos); + ImageViewInfoProvider::m_mousePos = mousePos; + mousePosChanged(mousePos); } void ImageViewInfoProvider::physSizeChanged(const QSizeF& physSize) const { - for (ImageViewInfoObserver* observer : observers) { - observer->updatePhysSize(physSize); - } + for (ImageViewInfoObserver* observer : m_observers) { + observer->updatePhysSize(physSize); + } } void ImageViewInfoProvider::mousePosChanged(const QPointF& mousePos) const { - for (ImageViewInfoObserver* observer : observers) { - observer->updateMousePos(mousePos); - } + for (ImageViewInfoObserver* observer : m_observers) { + observer->updateMousePos(mousePos); + } } const Dpi& ImageViewInfoProvider::getDpi() const { - return dpi; + return m_dpi; } const QPointF& ImageViewInfoProvider::getMousePos() const { - return mousePos; + return m_mousePos; } const QSizeF& ImageViewInfoProvider::getPhysSize() const { - return physSize; + return m_physSize; } diff --git a/ImageViewInfoProvider.h b/ImageViewInfoProvider.h index c52ff66d2..2d475e9bb 100644 --- a/ImageViewInfoProvider.h +++ b/ImageViewInfoProvider.h @@ -2,46 +2,45 @@ #ifndef SCANTAILOR_STATUSBARPROVIDER_H #define SCANTAILOR_STATUSBARPROVIDER_H -#include -#include +#include #include #include -#include "ImageViewInfoObserver.h" +#include +#include #include "Dpi.h" +#include "ImageViewInfoObserver.h" #include "NonCopyable.h" class ImageViewInfoProvider { - DECLARE_NON_COPYABLE(ImageViewInfoProvider) - -private: - std::list observers; - Dpi dpi; - QPointF mousePos; - QSizeF physSize; + DECLARE_NON_COPYABLE(ImageViewInfoProvider) + public: + explicit ImageViewInfoProvider(const Dpi& dpi); -public: - explicit ImageViewInfoProvider(const Dpi& dpi); + ~ImageViewInfoProvider(); - ~ImageViewInfoProvider(); + void attachObserver(ImageViewInfoObserver* observer); - void attachObserver(ImageViewInfoObserver* observer); + void detachObserver(ImageViewInfoObserver* observer); - void detachObserver(ImageViewInfoObserver* observer); + void setPhysSize(const QSizeF& physSize); - void setPhysSize(const QSizeF& physSize); + void setMousePos(const QPointF& mousePos); - void setMousePos(const QPointF& mousePos); + const Dpi& getDpi() const; - const Dpi& getDpi() const; + const QPointF& getMousePos() const; - const QPointF& getMousePos() const; + const QSizeF& getPhysSize() const; - const QSizeF& getPhysSize() const; + private: + void physSizeChanged(const QSizeF& physSize) const; -private: - void physSizeChanged(const QSizeF& physSize) const; + void mousePosChanged(const QPointF& mousePos) const; - void mousePosChanged(const QPointF& mousePos) const; + std::list m_observers; + Dpi m_dpi; + QPointF m_mousePos; + QSizeF m_physSize; }; diff --git a/IncompleteThumbnail.cpp b/IncompleteThumbnail.cpp index dbeb1e988..abcca947f 100644 --- a/IncompleteThumbnail.cpp +++ b/IncompleteThumbnail.cpp @@ -17,8 +17,8 @@ */ #include "IncompleteThumbnail.h" -#include #include +#include #include QPainterPath IncompleteThumbnail::m_sCachedPath; @@ -27,75 +27,74 @@ IncompleteThumbnail::IncompleteThumbnail(intrusive_ptr thu const QSizeF& max_size, const ImageId& image_id, const ImageTransformation& image_xform) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, image_xform) { -} + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, image_xform) {} IncompleteThumbnail::~IncompleteThumbnail() = default; void IncompleteThumbnail::drawQuestionMark(QPainter& painter, const QRectF& bounding_rect) { - const QString text(QString::fromLatin1("?")); - // Because painting happens only from the main thread, we don't - // need to care about concurrent access. - if (m_sCachedPath.isEmpty()) { + const QString text(QString::fromLatin1("?")); + // Because painting happens only from the main thread, we don't + // need to care about concurrent access. + if (m_sCachedPath.isEmpty()) { #if 0 QFont font(painter.font()); font.setWeight(QFont::DemiBold); font.setStyleStrategy(QFont::ForceOutline); m_sCachedPath.addText(0, 0, font, text); #else - m_sCachedPath.moveTo(QPointF(4.42188, -2.40625)); - m_sCachedPath.cubicTo(QPointF(4.42188, -3.20312), QPointF(4.51562, -3.32812), QPointF(5.23438, -3.84375)); - m_sCachedPath.cubicTo(QPointF(6.34375, -4.625), QPointF(6.67188, -5.15625), QPointF(6.67188, -6.17188)); - m_sCachedPath.cubicTo(QPointF(6.67188, -7.79688), QPointF(5.4375, -8.92188), QPointF(3.6875, -8.92188)); - m_sCachedPath.cubicTo(QPointF(2.65625, -8.92188), QPointF(1.84375, -8.5625), QPointF(1.32812, -7.85938)); - m_sCachedPath.cubicTo(QPointF(0.9375, -7.32812), QPointF(0.78125, -6.75), QPointF(0.765625, -5.76562)); - m_sCachedPath.lineTo(QPointF(2.40625, -5.76562)); - m_sCachedPath.lineTo(QPointF(2.40625, -5.79688)); - m_sCachedPath.cubicTo(QPointF(2.34375, -6.76562), QPointF(2.92188, -7.51562), QPointF(3.71875, -7.51562)); - m_sCachedPath.cubicTo(QPointF(4.4375, -7.51562), QPointF(4.98438, -6.90625), QPointF(4.98438, -6.125)); - m_sCachedPath.cubicTo(QPointF(4.98438, -5.59375), QPointF(4.82812, -5.35938), QPointF(4.125, -4.78125)); - m_sCachedPath.cubicTo(QPointF(3.17188, -3.96875), QPointF(2.90625, -3.4375), QPointF(2.9375, -2.40625)); - m_sCachedPath.lineTo(QPointF(4.42188, -2.40625)); - m_sCachedPath.moveTo(QPointF(4.625, -1.75)); - m_sCachedPath.lineTo(QPointF(2.8125, -1.75)); - m_sCachedPath.lineTo(QPointF(2.8125, 0.0)); - m_sCachedPath.lineTo(QPointF(4.625, 0.0)); - m_sCachedPath.lineTo(QPointF(4.625, -1.75)); + m_sCachedPath.moveTo(QPointF(4.42188, -2.40625)); + m_sCachedPath.cubicTo(QPointF(4.42188, -3.20312), QPointF(4.51562, -3.32812), QPointF(5.23438, -3.84375)); + m_sCachedPath.cubicTo(QPointF(6.34375, -4.625), QPointF(6.67188, -5.15625), QPointF(6.67188, -6.17188)); + m_sCachedPath.cubicTo(QPointF(6.67188, -7.79688), QPointF(5.4375, -8.92188), QPointF(3.6875, -8.92188)); + m_sCachedPath.cubicTo(QPointF(2.65625, -8.92188), QPointF(1.84375, -8.5625), QPointF(1.32812, -7.85938)); + m_sCachedPath.cubicTo(QPointF(0.9375, -7.32812), QPointF(0.78125, -6.75), QPointF(0.765625, -5.76562)); + m_sCachedPath.lineTo(QPointF(2.40625, -5.76562)); + m_sCachedPath.lineTo(QPointF(2.40625, -5.79688)); + m_sCachedPath.cubicTo(QPointF(2.34375, -6.76562), QPointF(2.92188, -7.51562), QPointF(3.71875, -7.51562)); + m_sCachedPath.cubicTo(QPointF(4.4375, -7.51562), QPointF(4.98438, -6.90625), QPointF(4.98438, -6.125)); + m_sCachedPath.cubicTo(QPointF(4.98438, -5.59375), QPointF(4.82812, -5.35938), QPointF(4.125, -4.78125)); + m_sCachedPath.cubicTo(QPointF(3.17188, -3.96875), QPointF(2.90625, -3.4375), QPointF(2.9375, -2.40625)); + m_sCachedPath.lineTo(QPointF(4.42188, -2.40625)); + m_sCachedPath.moveTo(QPointF(4.625, -1.75)); + m_sCachedPath.lineTo(QPointF(2.8125, -1.75)); + m_sCachedPath.lineTo(QPointF(2.8125, 0.0)); + m_sCachedPath.lineTo(QPointF(4.625, 0.0)); + m_sCachedPath.lineTo(QPointF(4.625, -1.75)); #endif // if 0 - } + } - const QRectF text_rect(m_sCachedPath.boundingRect()); + const QRectF text_rect(m_sCachedPath.boundingRect()); - QTransform xform1; - xform1.translate(-text_rect.left(), -text_rect.top()); + QTransform xform1; + xform1.translate(-text_rect.left(), -text_rect.top()); - const QSizeF unscaled_size(text_rect.size()); - QSizeF scaled_size(unscaled_size); - scaled_size.scale(bounding_rect.size() * 0.9, Qt::KeepAspectRatio); + const QSizeF unscaled_size(text_rect.size()); + QSizeF scaled_size(unscaled_size); + scaled_size.scale(bounding_rect.size() * 0.9, Qt::KeepAspectRatio); - const double hscale = scaled_size.width() / unscaled_size.width(); - const double vscale = scaled_size.height() / unscaled_size.height(); - QTransform xform2; - xform2.scale(hscale, vscale); + const double hscale = scaled_size.width() / unscaled_size.width(); + const double vscale = scaled_size.height() / unscaled_size.height(); + QTransform xform2; + xform2.scale(hscale, vscale); - // Position the text at the center of our bounding rect. - const QSizeF translation(bounding_rect.size() * 0.5 - scaled_size * 0.5); - QTransform xform3; - xform3.translate(translation.width(), translation.height()); + // Position the text at the center of our bounding rect. + const QSizeF translation(bounding_rect.size() * 0.5 - scaled_size * 0.5); + QTransform xform3; + xform3.translate(translation.width(), translation.height()); - painter.setWorldTransform(xform1 * xform2 * xform3, true); - painter.setRenderHint(QPainter::Antialiasing); + painter.setWorldTransform(xform1 * xform2 * xform3, true); + painter.setRenderHint(QPainter::Antialiasing); - QPen pen(QColor(0xff, 0x00, 0x00, 80)); - pen.setWidth(2); - pen.setCosmetic(true); - painter.setPen(pen); + QPen pen(QColor(0xff, 0x00, 0x00, 80)); + pen.setWidth(2); + pen.setCosmetic(true); + painter.setPen(pen); - painter.drawPath(m_sCachedPath); + painter.drawPath(m_sCachedPath); } // IncompleteThumbnail::drawQuestionMark void IncompleteThumbnail::paintOverImage(QPainter& painter, const QTransform& image_to_display, const QTransform& thumb_to_display) { - drawQuestionMark(painter, boundingRect()); + drawQuestionMark(painter, boundingRect()); } diff --git a/IncompleteThumbnail.h b/IncompleteThumbnail.h index e6d3d501e..926c2efec 100644 --- a/IncompleteThumbnail.h +++ b/IncompleteThumbnail.h @@ -19,9 +19,9 @@ #ifndef INCOMPLETETHUMBNAIL_H_ #define INCOMPLETETHUMBNAIL_H_ +#include #include "ThumbnailBase.h" #include "intrusive_ptr.h" -#include class ThumbnailPixmapCache; class QSizeF; @@ -40,23 +40,23 @@ class QRectF; * This class implements drawing of such thumbnails with question marks. */ class IncompleteThumbnail : public ThumbnailBase { -public: - IncompleteThumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& image_xform); + public: + IncompleteThumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& image_xform); - ~IncompleteThumbnail() override; + ~IncompleteThumbnail() override; - static void drawQuestionMark(QPainter& painter, const QRectF& bounding_rect); + static void drawQuestionMark(QPainter& painter, const QRectF& bounding_rect); -protected: - void paintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) override; + protected: + void paintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) override; -private: - static QPainterPath m_sCachedPath; + private: + static QPainterPath m_sCachedPath; }; diff --git a/JpegMetadataLoader.cpp b/JpegMetadataLoader.cpp index bd2301723..8b13495be 100644 --- a/JpegMetadataLoader.cpp +++ b/JpegMetadataLoader.cpp @@ -17,13 +17,13 @@ */ #include "JpegMetadataLoader.h" -#include "ImageMetadata.h" -#include "NonCopyable.h" -#include "Dpm.h" -#include #include -#include +#include #include +#include +#include "Dpm.h" +#include "ImageMetadata.h" +#include "NonCopyable.h" extern "C" { #include @@ -33,217 +33,211 @@ namespace { /*======================== JpegDecompressionHandle =======================*/ class JpegDecompressHandle { - DECLARE_NON_COPYABLE(JpegDecompressHandle) + DECLARE_NON_COPYABLE(JpegDecompressHandle) -public: - JpegDecompressHandle(jpeg_error_mgr* err_mgr, jpeg_source_mgr* src_mgr); + public: + JpegDecompressHandle(jpeg_error_mgr* err_mgr, jpeg_source_mgr* src_mgr); - ~JpegDecompressHandle(); + ~JpegDecompressHandle(); - jpeg_decompress_struct* ptr() { - return &m_info; - } + jpeg_decompress_struct* ptr() { return &m_info; } - jpeg_decompress_struct* operator->() { - return &m_info; - } + jpeg_decompress_struct* operator->() { return &m_info; } -private: - jpeg_decompress_struct m_info{}; + private: + jpeg_decompress_struct m_info{}; }; JpegDecompressHandle::JpegDecompressHandle(jpeg_error_mgr* err_mgr, jpeg_source_mgr* src_mgr) { - m_info.err = err_mgr; - jpeg_create_decompress(&m_info); - m_info.src = src_mgr; + m_info.err = err_mgr; + jpeg_create_decompress(&m_info); + m_info.src = src_mgr; } JpegDecompressHandle::~JpegDecompressHandle() { - jpeg_destroy_decompress(&m_info); + jpeg_destroy_decompress(&m_info); } /*============================ JpegSourceManager =========================*/ class JpegSourceManager : public jpeg_source_mgr { - DECLARE_NON_COPYABLE(JpegSourceManager) + DECLARE_NON_COPYABLE(JpegSourceManager) -public: - explicit JpegSourceManager(QIODevice& io_device); + public: + explicit JpegSourceManager(QIODevice& io_device); -private: - static void initSource(j_decompress_ptr cinfo); + private: + static void initSource(j_decompress_ptr cinfo); - static boolean fillInputBuffer(j_decompress_ptr cinfo); + static boolean fillInputBuffer(j_decompress_ptr cinfo); - boolean fillInputBufferImpl(); + boolean fillInputBufferImpl(); - static void skipInputData(j_decompress_ptr cinfo, long num_bytes); + static void skipInputData(j_decompress_ptr cinfo, long num_bytes); - void skipInputDataImpl(long num_bytes); + void skipInputDataImpl(long num_bytes); - static void termSource(j_decompress_ptr cinfo); + static void termSource(j_decompress_ptr cinfo); - static JpegSourceManager* object(j_decompress_ptr cinfo); + static JpegSourceManager* object(j_decompress_ptr cinfo); - QIODevice& m_rDevice; - JOCTET m_buf[4096]{}; + QIODevice& m_device; + JOCTET m_buf[4096]{}; }; -JpegSourceManager::JpegSourceManager(QIODevice& io_device) : jpeg_source_mgr(), m_rDevice(io_device) { - init_source = &JpegSourceManager::initSource; - fill_input_buffer = &JpegSourceManager::fillInputBuffer; - skip_input_data = &JpegSourceManager::skipInputData; - resync_to_restart = &jpeg_resync_to_restart; - term_source = &JpegSourceManager::termSource; - bytes_in_buffer = 0; - next_input_byte = m_buf; +JpegSourceManager::JpegSourceManager(QIODevice& io_device) : jpeg_source_mgr(), m_device(io_device) { + init_source = &JpegSourceManager::initSource; + fill_input_buffer = &JpegSourceManager::fillInputBuffer; + skip_input_data = &JpegSourceManager::skipInputData; + resync_to_restart = &jpeg_resync_to_restart; + term_source = &JpegSourceManager::termSource; + bytes_in_buffer = 0; + next_input_byte = m_buf; } void JpegSourceManager::initSource(j_decompress_ptr cinfo) { - // No-op. + // No-op. } boolean JpegSourceManager::fillInputBuffer(j_decompress_ptr cinfo) { - return object(cinfo)->fillInputBufferImpl(); + return object(cinfo)->fillInputBufferImpl(); } boolean JpegSourceManager::fillInputBufferImpl() { - const qint64 bytes_read = m_rDevice.read((char*) m_buf, sizeof(m_buf)); - if (bytes_read > 0) { - bytes_in_buffer = bytes_read; - } else { - // Insert a fake EOI marker. - m_buf[0] = 0xFF; - m_buf[1] = JPEG_EOI; - bytes_in_buffer = 2; - } - next_input_byte = m_buf; - - return 1; + const qint64 bytes_read = m_device.read((char*) m_buf, sizeof(m_buf)); + if (bytes_read > 0) { + bytes_in_buffer = bytes_read; + } else { + // Insert a fake EOI marker. + m_buf[0] = 0xFF; + m_buf[1] = JPEG_EOI; + bytes_in_buffer = 2; + } + next_input_byte = m_buf; + + return 1; } void JpegSourceManager::skipInputData(j_decompress_ptr cinfo, long num_bytes) { - object(cinfo)->skipInputDataImpl(num_bytes); + object(cinfo)->skipInputDataImpl(num_bytes); } void JpegSourceManager::skipInputDataImpl(long num_bytes) { - if (num_bytes <= 0) { - return; - } - - while (num_bytes > (long) bytes_in_buffer) { - num_bytes -= (long) bytes_in_buffer; - fillInputBufferImpl(); - } - next_input_byte += num_bytes; - bytes_in_buffer -= num_bytes; + if (num_bytes <= 0) { + return; + } + + while (num_bytes > (long) bytes_in_buffer) { + num_bytes -= (long) bytes_in_buffer; + fillInputBufferImpl(); + } + next_input_byte += num_bytes; + bytes_in_buffer -= num_bytes; } void JpegSourceManager::termSource(j_decompress_ptr cinfo) { - // No-op. + // No-op. } JpegSourceManager* JpegSourceManager::object(j_decompress_ptr cinfo) { - return static_cast(cinfo->src); + return static_cast(cinfo->src); } /*============================= JpegErrorManager ===========================*/ class JpegErrorManager : public jpeg_error_mgr { - DECLARE_NON_COPYABLE(JpegErrorManager) + DECLARE_NON_COPYABLE(JpegErrorManager) -public: - JpegErrorManager(); + public: + JpegErrorManager(); - jmp_buf& jmpBuf() { - return m_jmpBuf; - } + jmp_buf& jmpBuf() { return m_jmpBuf; } -private: - static void errorExit(j_common_ptr cinfo); + private: + static void errorExit(j_common_ptr cinfo); - static JpegErrorManager* object(j_common_ptr cinfo); + static JpegErrorManager* object(j_common_ptr cinfo); - jmp_buf m_jmpBuf{}; + jmp_buf m_jmpBuf{}; }; JpegErrorManager::JpegErrorManager() : jpeg_error_mgr() { - jpeg_std_error(this); - error_exit = &JpegErrorManager::errorExit; + jpeg_std_error(this); + error_exit = &JpegErrorManager::errorExit; } void JpegErrorManager::errorExit(j_common_ptr cinfo) { - longjmp(object(cinfo)->jmpBuf(), 1); + longjmp(object(cinfo)->jmpBuf(), 1); } JpegErrorManager* JpegErrorManager::object(j_common_ptr cinfo) { - return static_cast(cinfo->err); + return static_cast(cinfo->err); } } // namespace /*============================= JpegMetadataLoader ==========================*/ void JpegMetadataLoader::registerMyself() { - static bool registered = false; - if (!registered) { - ImageMetadataLoader::registerLoader(intrusive_ptr(new JpegMetadataLoader)); - registered = true; - } + static bool registered = false; + if (!registered) { + ImageMetadataLoader::registerLoader(make_intrusive()); + registered = true; + } } ImageMetadataLoader::Status JpegMetadataLoader::loadMetadata(QIODevice& io_device, const VirtualFunction& out) { - if (!io_device.isReadable()) { - return GENERIC_ERROR; - } - - static const unsigned char jpeg_signature[] = {0xff, 0xd8, 0xff}; - static const int sig_size = sizeof(jpeg_signature); - - unsigned char signature[sig_size]; - if (io_device.peek((char*) signature, sig_size) != sig_size) { - return FORMAT_NOT_RECOGNIZED; - } - if (memcmp(jpeg_signature, signature, sig_size) != 0) { - return FORMAT_NOT_RECOGNIZED; - } - - JpegErrorManager err_mgr; - if (setjmp(err_mgr.jmpBuf())) { - // Returning from longjmp(). - return GENERIC_ERROR; - } - - JpegSourceManager src_mgr(io_device); - JpegDecompressHandle cinfo(&err_mgr, &src_mgr); - - const int header_status = jpeg_read_header(cinfo.ptr(), 0); - if (header_status == JPEG_HEADER_TABLES_ONLY) { - return NO_IMAGES; - } - - // The other possible value is JPEG_SUSPENDED, but we never suspend it. - assert(header_status == JPEG_HEADER_OK); - - if (!jpeg_start_decompress(cinfo.ptr())) { - // libjpeg doesn't support all compression types. - return GENERIC_ERROR; - } - - const QSize size(cinfo->image_width, cinfo->image_height); - Dpi dpi; - if (cinfo->density_unit == 1) { - // Dots per inch. - dpi = Dpi(cinfo->X_density, cinfo->Y_density); - } else if (cinfo->density_unit == 2) { - // Dots per centimeter. - dpi = Dpm(cinfo->X_density * 100, cinfo->Y_density * 100); - } - - out(ImageMetadata(size, dpi)); - - return LOADED; + if (!io_device.isReadable()) { + return GENERIC_ERROR; + } + + static const unsigned char jpeg_signature[] = {0xff, 0xd8, 0xff}; + static const int sig_size = sizeof(jpeg_signature); + + unsigned char signature[sig_size]; + if (io_device.peek((char*) signature, sig_size) != sig_size) { + return FORMAT_NOT_RECOGNIZED; + } + if (memcmp(jpeg_signature, signature, sig_size) != 0) { + return FORMAT_NOT_RECOGNIZED; + } + + JpegErrorManager err_mgr; + if (setjmp(err_mgr.jmpBuf())) { + // Returning from longjmp(). + return GENERIC_ERROR; + } + + JpegSourceManager src_mgr(io_device); + JpegDecompressHandle cinfo(&err_mgr, &src_mgr); + + const int header_status = jpeg_read_header(cinfo.ptr(), 0); + if (header_status == JPEG_HEADER_TABLES_ONLY) { + return NO_IMAGES; + } + + // The other possible value is JPEG_SUSPENDED, but we never suspend it. + assert(header_status == JPEG_HEADER_OK); + + if (!jpeg_start_decompress(cinfo.ptr())) { + // libjpeg doesn't support all compression types. + return GENERIC_ERROR; + } + + const QSize size(cinfo->image_width, cinfo->image_height); + Dpi dpi; + if (cinfo->density_unit == 1) { + // Dots per inch. + dpi = Dpi(cinfo->X_density, cinfo->Y_density); + } else if (cinfo->density_unit == 2) { + // Dots per centimeter. + dpi = Dpm(cinfo->X_density * 100, cinfo->Y_density * 100); + } + + out(ImageMetadata(size, dpi)); + + return LOADED; } // JpegMetadataLoader::loadMetadata diff --git a/JpegMetadataLoader.h b/JpegMetadataLoader.h index 569690dba..4b7656c02 100644 --- a/JpegMetadataLoader.h +++ b/JpegMetadataLoader.h @@ -19,25 +19,25 @@ #ifndef JPEGMETADATALOADER_H_ #define JPEGMETADATALOADER_H_ +#include #include "ImageMetadataLoader.h" #include "VirtualFunction.h" -#include class QIODevice; class ImageMetadata; class JpegMetadataLoader : public ImageMetadataLoader { -public: - /** - * \brief Register this loader in the global registry. - * - * The same restrictions apply here as for - * ImageMetadataLoader::registerLoader() - */ - static void registerMyself(); - -protected: - Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; + public: + /** + * \brief Register this loader in the global registry. + * + * The same restrictions apply here as for + * ImageMetadataLoader::registerLoader() + */ + static void registerMyself(); + + protected: + Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; }; diff --git a/LightScheme.cpp b/LightScheme.cpp index 79225b5fc..83efb197c 100644 --- a/LightScheme.cpp +++ b/LightScheme.cpp @@ -1,75 +1,81 @@ -#include -#include #include "LightScheme.h" +#include +#include +#include -std::unique_ptr LightScheme::getPalette() const { - std::unique_ptr lightPalette(new QPalette()); +QPalette LightScheme::getPalette() const { + QPalette lightPalette; - lightPalette->setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0)); - lightPalette->setColor(QPalette::WindowText, QColor(0x30, 0x30, 0x30)); - lightPalette->setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x90, 0x90, 0x90)); - lightPalette->setColor(QPalette::Base, QColor(0xFC, 0xFC, 0xFC)); - lightPalette->setColor(QPalette::Disabled, QPalette::Base, QColor(0xFA, 0xFA, 0xFA)); - lightPalette->setColor(QPalette::AlternateBase, lightPalette->color(QPalette::Window)); - lightPalette->setColor(QPalette::Disabled, QPalette::AlternateBase, - lightPalette->color(QPalette::Disabled, QPalette::Window)); - lightPalette->setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xCD)); - lightPalette->setColor(QPalette::ToolTipText, Qt::black); - lightPalette->setColor(QPalette::Text, lightPalette->color(QPalette::WindowText)); - lightPalette->setColor(QPalette::Disabled, QPalette::Text, - lightPalette->color(QPalette::Disabled, QPalette::WindowText)); - lightPalette->setColor(QPalette::Light, Qt::white); - lightPalette->setColor(QPalette::Midlight, QColor(0xF0, 0xF0, 0xF0)); - lightPalette->setColor(QPalette::Dark, QColor(0xDA, 0xDA, 0xDA)); - lightPalette->setColor(QPalette::Mid, QColor(0xCC, 0xCC, 0xCC)); - lightPalette->setColor(QPalette::Shadow, QColor(0xBE, 0xBE, 0xBE)); - lightPalette->setColor(QPalette::Button, lightPalette->color(QPalette::Base)); - lightPalette->setColor(QPalette::Disabled, QPalette::Button, - lightPalette->color(QPalette::Disabled, QPalette::Base)); - lightPalette->setColor(QPalette::ButtonText, lightPalette->color(QPalette::WindowText)); - lightPalette->setColor(QPalette::Disabled, QPalette::ButtonText, - lightPalette->color(QPalette::Disabled, QPalette::WindowText)); - lightPalette->setColor(QPalette::BrightText, QColor(0xF4, 0x00, 0x00)); - lightPalette->setColor(QPalette::Link, QColor(0x00, 0x00, 0xFF)); - lightPalette->setColor(QPalette::Highlight, QColor(0xB5, 0xB5, 0xB5)); - lightPalette->setColor(QPalette::Disabled, QPalette::Highlight, QColor(0xDF, 0xDF, 0xDF)); - lightPalette->setColor(QPalette::HighlightedText, lightPalette->color(QPalette::WindowText)); - lightPalette->setColor(QPalette::Disabled, QPalette::HighlightedText, - lightPalette->color(QPalette::Disabled, QPalette::WindowText)); + lightPalette.setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0)); + lightPalette.setColor(QPalette::WindowText, QColor(0x30, 0x30, 0x30)); + lightPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x90, 0x90, 0x90)); + lightPalette.setColor(QPalette::Base, QColor(0xFC, 0xFC, 0xFC)); + lightPalette.setColor(QPalette::Disabled, QPalette::Base, QColor(0xFA, 0xFA, 0xFA)); + lightPalette.setColor(QPalette::AlternateBase, lightPalette.color(QPalette::Window)); + lightPalette.setColor(QPalette::Disabled, QPalette::AlternateBase, + lightPalette.color(QPalette::Disabled, QPalette::Window)); + lightPalette.setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xCD)); + lightPalette.setColor(QPalette::ToolTipText, Qt::black); + lightPalette.setColor(QPalette::Text, lightPalette.color(QPalette::WindowText)); + lightPalette.setColor(QPalette::Disabled, QPalette::Text, + lightPalette.color(QPalette::Disabled, QPalette::WindowText)); + lightPalette.setColor(QPalette::Light, Qt::white); + lightPalette.setColor(QPalette::Midlight, QColor(0xF0, 0xF0, 0xF0)); + lightPalette.setColor(QPalette::Dark, QColor(0xDA, 0xDA, 0xDA)); + lightPalette.setColor(QPalette::Mid, QColor(0xCC, 0xCC, 0xCC)); + lightPalette.setColor(QPalette::Shadow, QColor(0xBE, 0xBE, 0xBE)); + lightPalette.setColor(QPalette::Button, lightPalette.color(QPalette::Base)); + lightPalette.setColor(QPalette::Disabled, QPalette::Button, lightPalette.color(QPalette::Disabled, QPalette::Base)); + lightPalette.setColor(QPalette::ButtonText, lightPalette.color(QPalette::WindowText)); + lightPalette.setColor(QPalette::Disabled, QPalette::ButtonText, + lightPalette.color(QPalette::Disabled, QPalette::WindowText)); + lightPalette.setColor(QPalette::BrightText, QColor(0xF4, 0x00, 0x00)); + lightPalette.setColor(QPalette::Link, QColor(0x00, 0x00, 0xFF)); + lightPalette.setColor(QPalette::Highlight, QColor(0xB5, 0xB5, 0xB5)); + lightPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(0xDF, 0xDF, 0xDF)); + lightPalette.setColor(QPalette::HighlightedText, lightPalette.color(QPalette::WindowText)); + lightPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, + lightPalette.color(QPalette::Disabled, QPalette::WindowText)); - return lightPalette; + return lightPalette; } std::unique_ptr LightScheme::getStyleSheet() const { - std::unique_ptr qsStylesheet = nullptr; + std::unique_ptr styleSheet = nullptr; - QFile qfDarkStyle(QString(":/light_scheme/stylesheet.qss")); - if (qfDarkStyle.open(QIODevice::ReadOnly | QIODevice::Text)) { - qsStylesheet = std::make_unique(qfDarkStyle.readAll()); +#ifdef _WIN32 + QFile styleSheetFile(QString(":/light_scheme/qss/stylesheet_win.qss")); +#else + QFile styleSheetFile(QString(":/light_scheme/qss/stylesheet.qss")); +#endif + if (styleSheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + styleSheet = std::make_unique(styleSheetFile.readAll()); - qfDarkStyle.close(); - } + styleSheetFile.close(); + } - return qsStylesheet; + return styleSheet; } -std::unique_ptr LightScheme::getColorParams() const { - std::unique_ptr customColors(new ColorParams()); +ColorScheme::ColorParams LightScheme::getColorParams() const { + ColorScheme::ColorParams customColors; + + customColors[ThumbnailSequenceSelectedItemBackground] = QColor(0x72, 0x72, 0x72); + customColors[ThumbnailSequenceSelectedItemText] = Qt::white; + customColors[ThumbnailSequenceItemText] = Qt::black; + customColors[ThumbnailSequenceSelectionLeaderBackground] = QColor(0x5E, 0x5E, 0x5E); + customColors[OpenNewProjectBorder] = QColor(0xCC, 0xCC, 0xCC); + customColors[ProcessingIndicationFade] = QColor(0x93, 0x93, 0x93); + customColors[ProcessingIndicationHeadColor] = QColor(0x30, 0x30, 0x30); + customColors[ProcessingIndicationTail] = QColor(0xB5, 0xB5, 0xB5); + customColors[StageListHead] = customColors.at(ProcessingIndicationHeadColor); + customColors[StageListTail] = customColors.at(ProcessingIndicationTail); + customColors[FixDpiDialogErrorText] = QColor(0xFB, 0x00, 0x00); - customColors->insert( - ColorParams::value_type("thumbnail_sequence_selected_item_background", QColor(0x72, 0x72, 0x72))); - customColors->insert(ColorParams::value_type("thumbnail_sequence_selected_item_text", Qt::white)); - customColors->insert(ColorParams::value_type("thumbnail_sequence_item_text", Qt::black)); - customColors->insert(ColorParams::value_type("open_new_project_border_color", QColor(0xCC, 0xCC, 0xCC))); - customColors->insert(ColorParams::value_type("processing_indication_fade_color", QColor(0x93, 0x93, 0x93))); - customColors->insert(ColorParams::value_type("processing_indication_head_color", QColor(0x30, 0x30, 0x30))); - customColors->insert(ColorParams::value_type("processing_indication_tail_color", QColor(0xB5, 0xB5, 0xB5))); - customColors->insert( - ColorParams::value_type("stage_list_head_color", customColors->at("processing_indication_head_color"))); - customColors->insert( - ColorParams::value_type("stage_list_tail_color", customColors->at("processing_indication_tail_color"))); - customColors->insert(ColorParams::value_type("fix_dpi_dialog_error_text_color", QColor(0xFB, 0x00, 0x00))); + return customColors; +} - return customColors; +QStyle* LightScheme::getStyle() const { + return QStyleFactory::create("Fusion"); } diff --git a/LightScheme.h b/LightScheme.h index 914d2d8f8..a7baf5a2d 100644 --- a/LightScheme.h +++ b/LightScheme.h @@ -4,17 +4,18 @@ #include #include -#include #include #include "ColorScheme.h" class LightScheme : public ColorScheme { -public: - std::unique_ptr getPalette() const override; + public: + QStyle* getStyle() const override; - std::unique_ptr getStyleSheet() const override; + QPalette getPalette() const override; - std::unique_ptr getColorParams() const override; + std::unique_ptr getStyleSheet() const override; + + ColorParams getColorParams() const override; }; diff --git a/LoadFileTask.cpp b/LoadFileTask.cpp index cd43f7e80..59e887c96 100644 --- a/LoadFileTask.cpp +++ b/LoadFileTask.cpp @@ -17,36 +17,34 @@ */ #include "LoadFileTask.h" -#include "filters/fix_orientation/Task.h" -#include "ErrorWidget.h" -#include "FilterUiInterface.h" +#include +#include +#include #include "AbstractFilter.h" -#include "FilterOptionsWidget.h" -#include "ThumbnailPixmapCache.h" -#include "ProjectPages.h" #include "Dpm.h" +#include "ErrorWidget.h" #include "FilterData.h" +#include "FilterOptionsWidget.h" +#include "FilterUiInterface.h" #include "ImageLoader.h" -#include -#include -#include +#include "ProjectPages.h" +#include "ThumbnailPixmapCache.h" +#include "filters/fix_orientation/Task.h" using namespace imageproc; class LoadFileTask::ErrorResult : public FilterResult { - Q_DECLARE_TR_FUNCTIONS(LoadFileTask) -public: - explicit ErrorResult(const QString& file_path); + Q_DECLARE_TR_FUNCTIONS(LoadFileTask) + public: + explicit ErrorResult(const QString& file_path); - void updateUI(FilterUiInterface* ui) override; + void updateUI(FilterUiInterface* ui) override; - intrusive_ptr filter() override { - return nullptr; - } + intrusive_ptr filter() override { return nullptr; } -private: - QString m_filePath; - bool m_fileExists; + private: + QString m_filePath; + bool m_fileExists; }; @@ -55,96 +53,92 @@ LoadFileTask::LoadFileTask(Type type, intrusive_ptr thumbnail_cache, intrusive_ptr pages, intrusive_ptr next_task) - : BackgroundTask(type), - m_ptrThumbnailCache(std::move(thumbnail_cache)), - m_imageId(page.imageId()), - m_imageMetadata(page.metadata()), - m_ptrPages(std::move(pages)), - m_ptrNextTask(std::move(next_task)) { - assert(m_ptrNextTask); + : BackgroundTask(type), + m_thumbnailCache(std::move(thumbnail_cache)), + m_imageId(page.imageId()), + m_imageMetadata(page.metadata()), + m_pages(std::move(pages)), + m_nextTask(std::move(next_task)) { + assert(m_nextTask); } LoadFileTask::~LoadFileTask() = default; FilterResultPtr LoadFileTask::operator()() { - QImage image(ImageLoader::load(m_imageId)); - - try { - throwIfCancelled(); - - if (image.isNull()) { - return make_intrusive(m_imageId.filePath()); - } else { - updateImageSizeIfChanged(image); - overrideDpi(image); - m_ptrThumbnailCache->ensureThumbnailExists(m_imageId, image); - - return m_ptrNextTask->process(*this, FilterData(image)); - } - } catch (const CancelledException&) { - return nullptr; + QImage image(ImageLoader::load(m_imageId)); + + try { + throwIfCancelled(); + + if (image.isNull()) { + return make_intrusive(m_imageId.filePath()); + } else { + updateImageSizeIfChanged(image); + overrideDpi(image); + m_thumbnailCache->ensureThumbnailExists(m_imageId, image); + + return m_nextTask->process(*this, FilterData(image)); } + } catch (const CancelledException&) { + return nullptr; + } } void LoadFileTask::updateImageSizeIfChanged(const QImage& image) { - // The user might just replace a file with another one. - // In that case, we update its size that we store. - // Note that we don't do the same about DPI, because - // a DPI mismatch between the image and the stored value - // may indicate that the DPI was overridden. - // TODO: do something about DPIs when we have the ability - // to change DPIs at any point in time (not just when - // creating a project). - if (image.size() != m_imageMetadata.size()) { - m_imageMetadata.setSize(image.size()); - m_ptrPages->updateImageMetadata(m_imageId, m_imageMetadata); - } + // The user might just replace a file with another one. + // In that case, we update its size that we store. + // Note that we don't do the same about DPI, because + // a DPI mismatch between the image and the stored value + // may indicate that the DPI was overridden. + // TODO: do something about DPIs when we have the ability + // to change DPIs at any point in time (not just when + // creating a project). + if (image.size() != m_imageMetadata.size()) { + m_imageMetadata.setSize(image.size()); + m_pages->updateImageMetadata(m_imageId, m_imageMetadata); + } } void LoadFileTask::overrideDpi(QImage& image) const { - // Beware: QImage will have a default DPI when loading - // an image that doesn't specify one. - const Dpm dpm(m_imageMetadata.dpi()); - image.setDotsPerMeterX(dpm.horizontal()); - image.setDotsPerMeterY(dpm.vertical()); + // Beware: QImage will have a default DPI when loading + // an image that doesn't specify one. + const Dpm dpm(m_imageMetadata.dpi()); + image.setDotsPerMeterX(dpm.horizontal()); + image.setDotsPerMeterY(dpm.vertical()); } /*======================= LoadFileTask::ErrorResult ======================*/ LoadFileTask::ErrorResult::ErrorResult(const QString& file_path) - : m_filePath(QDir::toNativeSeparators(file_path)), m_fileExists(QFile::exists(file_path)) { -} + : m_filePath(QDir::toNativeSeparators(file_path)), m_fileExists(QFile::exists(file_path)) {} void LoadFileTask::ErrorResult::updateUI(FilterUiInterface* ui) { - class ErrWidget : public ErrorWidget { - public: - ErrWidget(intrusive_ptr> relinking_dialog_requester, - const QString& text, - Qt::TextFormat fmt = Qt::AutoText) - : ErrorWidget(text, fmt), m_ptrRelinkingDialogRequester(std::move(relinking_dialog_requester)) { - } - - private: - void linkActivated(const QString&) override { - (*m_ptrRelinkingDialogRequester)(); - } - - intrusive_ptr> m_ptrRelinkingDialogRequester; - }; - - - QString err_msg; - Qt::TextFormat fmt = Qt::AutoText; - if (m_fileExists) { - err_msg = tr("The following file could not be loaded:\n%1").arg(m_filePath); - fmt = Qt::PlainText; - } else { - err_msg = tr("The following file doesn't exist:
%1
" - "
" - "Use the Relinking Tool to locate it.") - .arg(m_filePath.toHtmlEscaped()); - fmt = Qt::RichText; - } - ui->setImageWidget(new ErrWidget(ui->relinkingDialogRequester(), err_msg, fmt), ui->TRANSFER_OWNERSHIP); - ui->setOptionsWidget(new FilterOptionsWidget, ui->TRANSFER_OWNERSHIP); + class ErrWidget : public ErrorWidget { + public: + ErrWidget(intrusive_ptr> relinking_dialog_requester, + const QString& text, + Qt::TextFormat fmt = Qt::AutoText) + : ErrorWidget(text, fmt), m_relinkingDialogRequester(std::move(relinking_dialog_requester)) {} + + private: + void linkActivated(const QString&) override { (*m_relinkingDialogRequester)(); } + + intrusive_ptr> m_relinkingDialogRequester; + }; + + + QString err_msg; + Qt::TextFormat fmt = Qt::AutoText; + if (m_fileExists) { + err_msg = tr("The following file could not be loaded:\n%1").arg(m_filePath); + fmt = Qt::PlainText; + } else { + err_msg = tr("The following file doesn't exist:
%1
" + "
" + "Use the Relinking Tool to locate it.") + .arg(m_filePath.toHtmlEscaped()); + fmt = Qt::RichText; + } + ui->setImageWidget(new ErrWidget(ui->relinkingDialogRequester(), err_msg, fmt), ui->TRANSFER_OWNERSHIP); + ui->setOptionsWidget(new FilterOptionsWidget, ui->TRANSFER_OWNERSHIP); } // LoadFileTask::ErrorResult::updateUI diff --git a/LoadFileTask.h b/LoadFileTask.h index 8d84bf907..9c6bd5d1a 100644 --- a/LoadFileTask.h +++ b/LoadFileTask.h @@ -19,12 +19,12 @@ #ifndef LOADFILETASK_H_ #define LOADFILETASK_H_ -#include "NonCopyable.h" #include "BackgroundTask.h" #include "FilterResult.h" -#include "intrusive_ptr.h" #include "ImageId.h" #include "ImageMetadata.h" +#include "NonCopyable.h" +#include "intrusive_ptr.h" class ThumbnailPixmapCache; class PageInfo; @@ -36,31 +36,31 @@ class Task; } class LoadFileTask : public BackgroundTask { - DECLARE_NON_COPYABLE(LoadFileTask) + DECLARE_NON_COPYABLE(LoadFileTask) -public: - LoadFileTask(Type type, - const PageInfo& page, - intrusive_ptr thumbnail_cache, - intrusive_ptr pages, - intrusive_ptr next_task); + public: + LoadFileTask(Type type, + const PageInfo& page, + intrusive_ptr thumbnail_cache, + intrusive_ptr pages, + intrusive_ptr next_task); - ~LoadFileTask() override; + ~LoadFileTask() override; - FilterResultPtr operator()() override; + FilterResultPtr operator()() override; -private: - class ErrorResult; + private: + class ErrorResult; - void updateImageSizeIfChanged(const QImage& image); + void updateImageSizeIfChanged(const QImage& image); - void overrideDpi(QImage& image) const; + void overrideDpi(QImage& image) const; - intrusive_ptr m_ptrThumbnailCache; - ImageId m_imageId; - ImageMetadata m_imageMetadata; - const intrusive_ptr m_ptrPages; - const intrusive_ptr m_ptrNextTask; + intrusive_ptr m_thumbnailCache; + ImageId m_imageId; + ImageMetadata m_imageMetadata; + const intrusive_ptr m_pages; + const intrusive_ptr m_nextTask; }; diff --git a/LoadFilesStatusDialog.cpp b/LoadFilesStatusDialog.cpp index 028b3b717..02e1008fb 100644 --- a/LoadFilesStatusDialog.cpp +++ b/LoadFilesStatusDialog.cpp @@ -20,40 +20,40 @@ #include LoadFilesStatusDialog::LoadFilesStatusDialog(QWidget* parent) : QDialog(parent) { - ui.setupUi(this); - ui.tabWidget->setCurrentWidget(ui.failedTab); + ui.setupUi(this); + ui.tabWidget->setCurrentWidget(ui.failedTab); - m_loadedTabNameTemplate = ui.tabWidget->tabText(0); - m_failedTabNameTemplate = ui.tabWidget->tabText(1); + m_loadedTabNameTemplate = ui.tabWidget->tabText(0); + m_failedTabNameTemplate = ui.tabWidget->tabText(1); - setLoadedFiles(std::vector()); - setFailedFiles(std::vector()); + setLoadedFiles(std::vector()); + setFailedFiles(std::vector()); } void LoadFilesStatusDialog::setLoadedFiles(const std::vector& files) { - ui.tabWidget->setTabText(0, m_loadedTabNameTemplate.arg(files.size())); + ui.tabWidget->setTabText(0, m_loadedTabNameTemplate.arg(files.size())); - QString text; - for (const QString& file : files) { - text.append(file); - text.append(QChar('\n')); - } + QString text; + for (const QString& file : files) { + text.append(file); + text.append(QChar('\n')); + } - ui.loadedFiles->setPlainText(text); + ui.loadedFiles->setPlainText(text); } void LoadFilesStatusDialog::setFailedFiles(const std::vector& files) { - ui.tabWidget->setTabText(1, m_failedTabNameTemplate.arg(files.size())); + ui.tabWidget->setTabText(1, m_failedTabNameTemplate.arg(files.size())); - QString text; - for (const QString& file : files) { - text.append(file); - text.append(QChar('\n')); - } + QString text; + for (const QString& file : files) { + text.append(file); + text.append(QChar('\n')); + } - ui.failedFiles->setPlainText(text); + ui.failedFiles->setPlainText(text); } void LoadFilesStatusDialog::setOkButtonName(const QString& name) { - ui.buttonBox->button(QDialogButtonBox::Ok)->setText(name); + ui.buttonBox->button(QDialogButtonBox::Ok)->setText(name); } diff --git a/LoadFilesStatusDialog.h b/LoadFilesStatusDialog.h index fd75fcb8b..e3d206386 100644 --- a/LoadFilesStatusDialog.h +++ b/LoadFilesStatusDialog.h @@ -19,24 +19,24 @@ #ifndef LOAD_FILES_STATUS_DIALOG_H_ #define LOAD_FILES_STATUS_DIALOG_H_ -#include "ui_LoadFilesStatusDialog.h" #include #include +#include "ui_LoadFilesStatusDialog.h" class LoadFilesStatusDialog : public QDialog { -public: - explicit LoadFilesStatusDialog(QWidget* parent = nullptr); + public: + explicit LoadFilesStatusDialog(QWidget* parent = nullptr); - void setLoadedFiles(const std::vector& files); + void setLoadedFiles(const std::vector& files); - void setFailedFiles(const std::vector& failed); + void setFailedFiles(const std::vector& failed); - void setOkButtonName(const QString& name); + void setOkButtonName(const QString& name); -private: - Ui::LoadFilesStatusDialog ui; - QString m_loadedTabNameTemplate; - QString m_failedTabNameTemplate; + private: + Ui::LoadFilesStatusDialog ui; + QString m_loadedTabNameTemplate; + QString m_failedTabNameTemplate; }; diff --git a/MainWindow.cpp b/MainWindow.cpp index 916a1b107..8bea34374 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -16,1542 +16,1561 @@ along with this program. If not, see . */ -#include "CommandLine.h" #include "MainWindow.h" -#include "NewOpenProjectPanel.h" -#include "RecentProjects.h" -#include "WorkerThreadPool.h" -#include "ProjectPages.h" -#include "PageSequence.h" -#include "PageSelectionAccessor.h" -#include "StageSequence.h" -#include "ProcessingTaskQueue.h" -#include "ImageInfo.h" -#include "Utils.h" -#include "FilterOptionsWidget.h" -#include "ErrorWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AbstractRelinker.h" +#include "Application.h" #include "AutoRemovingFile.h" -#include "DebugImages.h" -#include "DebugImageView.h" -#include "TabbedDebugImages.h" #include "BasicImageView.h" -#include "ProjectWriter.h" -#include "ProjectReader.h" -#include "ThumbnailFactory.h" +#include "CommandLine.h" #include "ContentBoxPropagator.h" +#include "DebugImageView.h" +#include "DebugImages.h" +#include "DefaultParamsDialog.h" +#include "ErrorWidget.h" +#include "FilterOptionsWidget.h" +#include "FixDpiDialog.h" +#include "ImageInfo.h" +#include "ImageMetadataLoader.h" +#include "LoadFileTask.h" +#include "LoadFilesStatusDialog.h" +#include "NewOpenProjectPanel.h" +#include "OutOfMemoryDialog.h" +#include "OutOfMemoryHandler.h" #include "PageOrientationPropagator.h" +#include "PageSelectionAccessor.h" +#include "PageSequence.h" +#include "ProcessingIndicationWidget.h" +#include "ProcessingTaskQueue.h" #include "ProjectCreationContext.h" #include "ProjectOpeningContext.h" +#include "ProjectPages.h" +#include "ProjectReader.h" +#include "ProjectWriter.h" +#include "RecentProjects.h" +#include "RelinkingDialog.h" +#include "ScopedIncDec.h" +#include "SettingsDialog.h" #include "SkinnedButton.h" -#include "SystemLoadWidget.h" -#include "ProcessingIndicationWidget.h" -#include "ImageMetadataLoader.h" #include "SmartFilenameOrdering.h" -#include "FixDpiDialog.h" -#include "LoadFilesStatusDialog.h" -#include "SettingsDialog.h" -#include "AbstractRelinker.h" -#include "RelinkingDialog.h" -#include "OutOfMemoryHandler.h" -#include "OutOfMemoryDialog.h" -#include "filters/fix_orientation/Task.h" +#include "StageSequence.h" +#include "SystemLoadWidget.h" +#include "TabbedDebugImages.h" +#include "ThumbnailFactory.h" +#include "UnitsProvider.h" +#include "Utils.h" +#include "WorkerThreadPool.h" +#include "filters/deskew/CacheDrivenTask.h" +#include "filters/deskew/Task.h" #include "filters/fix_orientation/CacheDrivenTask.h" -#include "filters/page_split/Task.h" +#include "filters/fix_orientation/Task.h" +#include "filters/output/CacheDrivenTask.h" +#include "filters/output/TabbedImageView.h" +#include "filters/output/Task.h" +#include "filters/page_layout/CacheDrivenTask.h" +#include "filters/page_layout/Task.h" #include "filters/page_split/CacheDrivenTask.h" -#include "filters/deskew/Task.h" -#include "filters/deskew/CacheDrivenTask.h" -#include "filters/select_content/Task.h" +#include "filters/page_split/Task.h" #include "filters/select_content/CacheDrivenTask.h" -#include "filters/page_layout/Task.h" -#include "filters/page_layout/CacheDrivenTask.h" -#include "filters/output/Task.h" -#include "filters/output/TabbedImageView.h" -#include "filters/output/CacheDrivenTask.h" -#include "LoadFileTask.h" -#include "ScopedIncDec.h" +#include "filters/select_content/Task.h" #include "ui_AboutDialog.h" -#include "ui_RemovePagesDialog.h" #include "ui_BatchProcessingLowerPanel.h" +#include "ui_RemovePagesDialog.h" #include "version.h" -#include "Application.h" -#include "UnitsProvider.h" -#include "DefaultParamsDialog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include class MainWindow::PageSelectionProviderImpl : public PageSelectionProvider { -public: - PageSelectionProviderImpl(MainWindow* wnd) : m_ptrWnd(wnd) { - } + public: + PageSelectionProviderImpl(MainWindow* wnd) : m_wnd(wnd) {} - virtual PageSequence allPages() const { - return m_ptrWnd ? m_ptrWnd->allPages() : PageSequence(); - } + virtual PageSequence allPages() const { return m_wnd ? m_wnd->allPages() : PageSequence(); } - virtual std::set selectedPages() const { - return m_ptrWnd ? m_ptrWnd->selectedPages() : std::set(); - } + virtual std::set selectedPages() const { return m_wnd ? m_wnd->selectedPages() : std::set(); } - std::vector selectedRanges() const { - return m_ptrWnd ? m_ptrWnd->selectedRanges() : std::vector(); - } + std::vector selectedRanges() const { return m_wnd ? m_wnd->selectedRanges() : std::vector(); } -private: - QPointer m_ptrWnd; + private: + QPointer m_wnd; }; MainWindow::MainWindow() - : m_ptrPages(new ProjectPages), - m_ptrStages(new StageSequence(m_ptrPages, newPageSelectionAccessor())), - m_ptrWorkerThreadPool(new WorkerThreadPool), - m_ptrInteractiveQueue(new ProcessingTaskQueue()), - m_ptrOutOfMemoryDialog(new OutOfMemoryDialog), - m_curFilter(0), - m_ignoreSelectionChanges(0), - m_ignorePageOrderingChanges(0), - m_debug(false), - m_closing(false) { - m_maxLogicalThumbSize = QSize(250, 160); - m_ptrThumbSequence = std::make_unique(m_maxLogicalThumbSize); - - m_thumbResizeTimer.setSingleShot(true); - connect(&m_thumbResizeTimer, SIGNAL(timeout()), SLOT(invalidateAllThumbnails())); - - m_autoSaveTimer.setSingleShot(true); - connect(&m_autoSaveTimer, SIGNAL(timeout()), SLOT(autoSaveProject())); - - setupUi(this); - sortOptions->setVisible(false); - - createBatchProcessingWidget(); - m_ptrProcessingIndicationWidget.reset(new ProcessingIndicationWidget); - - filterList->setStages(m_ptrStages); - filterList->selectRow(0); + : m_pages(new ProjectPages), + m_stages(new StageSequence(m_pages, newPageSelectionAccessor())), + m_workerThreadPool(new WorkerThreadPool), + m_interactiveQueue(new ProcessingTaskQueue()), + m_outOfMemoryDialog(new OutOfMemoryDialog), + m_curFilter(0), + m_ignoreSelectionChanges(0), + m_ignorePageOrderingChanges(0), + m_debug(false), + m_closing(false) { + QSettings app_settings; - setupThumbView(); // Expects m_ptrThumbSequence to be initialized. - m_ptrTabbedDebugImages.reset(new TabbedDebugImages); + m_maxLogicalThumbSize = app_settings.value("settings/max_logical_thumb_size", QSize(250, 160)).toSizeF(); + m_thumbSequence = std::make_unique(m_maxLogicalThumbSize); - m_debug = actionDebug->isChecked(); + m_autoSaveTimer.setSingleShot(true); + connect(&m_autoSaveTimer, SIGNAL(timeout()), SLOT(autoSaveProject())); - m_pImageFrameLayout = new QStackedLayout(imageViewFrame); - m_pImageFrameLayout->setStackingMode(QStackedLayout::StackAll); + setupUi(this); + sortOptions->setVisible(false); - m_pOptionsFrameLayout = new QStackedLayout(filterOptions); + createBatchProcessingWidget(); + m_processingIndicationWidget.reset(new ProcessingIndicationWidget); - m_statusBarPanel = std::make_unique(); - QMainWindow::statusBar()->addPermanentWidget(m_statusBarPanel.get()); - connect(m_ptrThumbSequence.get(), &ThumbnailSequence::newSelectionLeader, [this](const PageInfo& page_info) { - PageSequence pageSequence = m_ptrThumbSequence->toPageSequence(); - if (pageSequence.numPages() > 0) { - m_statusBarPanel->updatePage(pageSequence.pageNo(page_info.id()) + 1, pageSequence.numPages(), - page_info.id()); - } else { - m_statusBarPanel->clear(); - } - }); + filterList->setStages(m_stages); + filterList->selectRow(0); + + setupThumbView(); // Expects m_thumbSequence to be initialized. + m_tabbedDebugImages.reset(new TabbedDebugImages); + + m_debug = actionDebug->isChecked(); - m_unitsMenuActionGroup = std::make_unique(this); - for (QAction* action : menuUnits->actions()) { - m_unitsMenuActionGroup->addAction(action); + m_imageFrameLayout = new QStackedLayout(imageViewFrame); + m_imageFrameLayout->setStackingMode(QStackedLayout::StackAll); + + m_optionsFrameLayout = new QStackedLayout(filterOptions); + + m_statusBarPanel = std::make_unique(); + QMainWindow::statusBar()->addPermanentWidget(m_statusBarPanel.get()); + connect(m_thumbSequence.get(), &ThumbnailSequence::newSelectionLeader, [this](const PageInfo& page_info) { + PageSequence pageSequence = m_thumbSequence->toPageSequence(); + if (pageSequence.numPages() > 0) { + m_statusBarPanel->updatePage(pageSequence.pageNo(page_info.id()) + 1, pageSequence.numPages(), page_info.id()); + } else { + m_statusBarPanel->clear(); + } + }); + + m_unitsMenuActionGroup = std::make_unique(this); + for (QAction* action : menuUnits->actions()) { + m_unitsMenuActionGroup->addAction(action); + } + switch (unitsFromString(QSettings().value("settings/units", "mm").toString())) { + case PIXELS: + actionPixels->setChecked(true); + break; + case MILLIMETRES: + actionMilimeters->setChecked(true); + break; + case CENTIMETRES: + actionCentimetres->setChecked(true); + break; + case INCHES: + actionInches->setChecked(true); + break; + } + connect(actionPixels, &QAction::toggled, [this](bool checked) { + if (checked) { + UnitsProvider::getInstance()->setUnits(PIXELS); + QSettings().setValue("settings/units", unitsToString(PIXELS)); } - switch (unitsFromString(QSettings().value("settings/units", "mm").toString())) { - case PIXELS: - actionPixels->setChecked(true); - break; - case MILLIMETRES: - actionMilimeters->setChecked(true); - break; - case CENTIMETRES: - actionCentimetres->setChecked(true); - break; - case INCHES: - actionInches->setChecked(true); - break; + }); + connect(actionMilimeters, &QAction::toggled, [this](bool checked) { + if (checked) { + UnitsProvider::getInstance()->setUnits(MILLIMETRES); + QSettings().setValue("settings/units", unitsToString(MILLIMETRES)); } - connect(actionPixels, &QAction::toggled, [this](bool checked) { - if (checked) { - UnitsProvider::getInstance()->setUnits(PIXELS); - QSettings().setValue("settings/units", unitsToString(PIXELS)); - } - }); - connect(actionMilimeters, &QAction::toggled, [this](bool checked) { - if (checked) { - UnitsProvider::getInstance()->setUnits(MILLIMETRES); - QSettings().setValue("settings/units", unitsToString(MILLIMETRES)); - } - }); - connect(actionCentimetres, &QAction::toggled, [this](bool checked) { - if (checked) { - UnitsProvider::getInstance()->setUnits(CENTIMETRES); - QSettings().setValue("settings/units", unitsToString(CENTIMETRES)); - } - }); - connect(actionInches, &QAction::toggled, [this](bool checked) { - if (checked) { - UnitsProvider::getInstance()->setUnits(INCHES); - QSettings().setValue("settings/units", unitsToString(INCHES)); - } - }); - - addAction(actionFirstPage); - addAction(actionLastPage); - addAction(actionNextPage); - addAction(actionPrevPage); - addAction(actionPrevPageQ); - addAction(actionNextPageW); - - addAction(actionSwitchFilter1); - addAction(actionSwitchFilter2); - addAction(actionSwitchFilter3); - addAction(actionSwitchFilter4); - addAction(actionSwitchFilter5); - addAction(actionSwitchFilter6); - // Should be enough to save a project. - OutOfMemoryHandler::instance().allocateEmergencyMemory(3 * 1024 * 1024); - - connect(actionFirstPage, SIGNAL(triggered(bool)), SLOT(goFirstPage())); - connect(actionLastPage, SIGNAL(triggered(bool)), SLOT(goLastPage())); - connect(actionPrevPage, SIGNAL(triggered(bool)), SLOT(goPrevPage())); - connect(actionNextPage, SIGNAL(triggered(bool)), SLOT(goNextPage())); - connect(actionPrevPageQ, SIGNAL(triggered(bool)), this, SLOT(goPrevPage())); - connect(actionNextPageW, SIGNAL(triggered(bool)), this, SLOT(goNextPage())); - connect(actionAbout, SIGNAL(triggered(bool)), this, SLOT(showAboutDialog())); - connect(&OutOfMemoryHandler::instance(), SIGNAL(outOfMemory()), SLOT(handleOutOfMemorySituation())); - - connect(actionSwitchFilter1, SIGNAL(triggered(bool)), SLOT(switchFilter1())); - connect(actionSwitchFilter2, SIGNAL(triggered(bool)), SLOT(switchFilter2())); - connect(actionSwitchFilter3, SIGNAL(triggered(bool)), SLOT(switchFilter3())); - connect(actionSwitchFilter4, SIGNAL(triggered(bool)), SLOT(switchFilter4())); - connect(actionSwitchFilter5, SIGNAL(triggered(bool)), SLOT(switchFilter5())); - connect(actionSwitchFilter6, SIGNAL(triggered(bool)), SLOT(switchFilter6())); - - connect(filterList->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, - SLOT(filterSelectionChanged(const QItemSelection&))); - connect(filterList, SIGNAL(launchBatchProcessing()), this, SLOT(startBatchProcessing())); - - connect(m_ptrWorkerThreadPool.get(), SIGNAL(taskResult(const BackgroundTaskPtr&, const FilterResultPtr&)), this, - SLOT(filterResult(const BackgroundTaskPtr&, const FilterResultPtr&))); - - connect(m_ptrThumbSequence.get(), - SIGNAL(newSelectionLeader(const PageInfo&, const QRectF&, ThumbnailSequence::SelectionFlags)), this, - SLOT(currentPageChanged(const PageInfo&, const QRectF&, ThumbnailSequence::SelectionFlags))); - connect(m_ptrThumbSequence.get(), SIGNAL(pageContextMenuRequested(const PageInfo&, const QPoint&, bool)), this, - SLOT(pageContextMenuRequested(const PageInfo&, const QPoint&, bool))); - connect(m_ptrThumbSequence.get(), SIGNAL(pastLastPageContextMenuRequested(const QPoint&)), - SLOT(pastLastPageContextMenuRequested(const QPoint&))); - - connect(thumbView->verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(thumbViewScrolled())); - connect(thumbView->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(thumbViewScrolled())); - connect(focusButton, SIGNAL(clicked(bool)), this, SLOT(thumbViewFocusToggled(bool))); - connect(sortOptions, SIGNAL(currentIndexChanged(int)), this, SLOT(pageOrderingChanged(int))); - - connect(actionFixDpi, SIGNAL(triggered(bool)), SLOT(fixDpiDialogRequested())); - connect(actionRelinking, SIGNAL(triggered(bool)), SLOT(showRelinkingDialog())); + }); + connect(actionCentimetres, &QAction::toggled, [this](bool checked) { + if (checked) { + UnitsProvider::getInstance()->setUnits(CENTIMETRES); + QSettings().setValue("settings/units", unitsToString(CENTIMETRES)); + } + }); + connect(actionInches, &QAction::toggled, [this](bool checked) { + if (checked) { + UnitsProvider::getInstance()->setUnits(INCHES); + QSettings().setValue("settings/units", unitsToString(INCHES)); + } + }); + + addAction(actionFirstPage); + addAction(actionLastPage); + addAction(actionNextPage); + addAction(actionPrevPage); + addAction(actionPrevPageQ); + addAction(actionNextPageW); + addAction(actionNextSelectedPage); + addAction(actionPrevSelectedPage); + addAction(actionNextSelectedPageW); + addAction(actionPrevSelectedPageQ); + + addAction(actionSwitchFilter1); + addAction(actionSwitchFilter2); + addAction(actionSwitchFilter3); + addAction(actionSwitchFilter4); + addAction(actionSwitchFilter5); + addAction(actionSwitchFilter6); + // Should be enough to save a project. + OutOfMemoryHandler::instance().allocateEmergencyMemory(3 * 1024 * 1024); + + connect(actionFirstPage, SIGNAL(triggered(bool)), SLOT(goFirstPage())); + connect(actionLastPage, SIGNAL(triggered(bool)), SLOT(goLastPage())); + connect(actionPrevPage, SIGNAL(triggered(bool)), SLOT(goPrevPage())); + connect(actionNextPage, SIGNAL(triggered(bool)), SLOT(goNextPage())); + connect(actionPrevPageQ, SIGNAL(triggered(bool)), this, SLOT(goPrevPage())); + connect(actionNextPageW, SIGNAL(triggered(bool)), this, SLOT(goNextPage())); + connect(actionPrevSelectedPage, SIGNAL(triggered(bool)), SLOT(goPrevSelectedPage())); + connect(actionNextSelectedPage, SIGNAL(triggered(bool)), SLOT(goNextSelectedPage())); + connect(actionPrevSelectedPageQ, SIGNAL(triggered(bool)), this, SLOT(goPrevSelectedPage())); + connect(actionNextSelectedPageW, SIGNAL(triggered(bool)), this, SLOT(goNextSelectedPage())); + connect(actionAbout, SIGNAL(triggered(bool)), this, SLOT(showAboutDialog())); + connect(&OutOfMemoryHandler::instance(), SIGNAL(outOfMemory()), SLOT(handleOutOfMemorySituation())); + + connect(actionSwitchFilter1, SIGNAL(triggered(bool)), SLOT(switchFilter1())); + connect(actionSwitchFilter2, SIGNAL(triggered(bool)), SLOT(switchFilter2())); + connect(actionSwitchFilter3, SIGNAL(triggered(bool)), SLOT(switchFilter3())); + connect(actionSwitchFilter4, SIGNAL(triggered(bool)), SLOT(switchFilter4())); + connect(actionSwitchFilter5, SIGNAL(triggered(bool)), SLOT(switchFilter5())); + connect(actionSwitchFilter6, SIGNAL(triggered(bool)), SLOT(switchFilter6())); + + connect(filterList->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, + SLOT(filterSelectionChanged(const QItemSelection&))); + connect(filterList, SIGNAL(launchBatchProcessing()), this, SLOT(startBatchProcessing())); + + connect(m_workerThreadPool.get(), SIGNAL(taskResult(const BackgroundTaskPtr&, const FilterResultPtr&)), this, + SLOT(filterResult(const BackgroundTaskPtr&, const FilterResultPtr&))); + + connect(m_thumbSequence.get(), + SIGNAL(newSelectionLeader(const PageInfo&, const QRectF&, ThumbnailSequence::SelectionFlags)), this, + SLOT(currentPageChanged(const PageInfo&, const QRectF&, ThumbnailSequence::SelectionFlags))); + connect(m_thumbSequence.get(), SIGNAL(pageContextMenuRequested(const PageInfo&, const QPoint&, bool)), this, + SLOT(pageContextMenuRequested(const PageInfo&, const QPoint&, bool))); + connect(m_thumbSequence.get(), SIGNAL(pastLastPageContextMenuRequested(const QPoint&)), + SLOT(pastLastPageContextMenuRequested(const QPoint&))); + + connect(thumbView->verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(thumbViewScrolled())); + connect(thumbView->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(thumbViewScrolled())); + connect(focusButton, SIGNAL(clicked(bool)), this, SLOT(thumbViewFocusToggled(bool))); + connect(sortOptions, SIGNAL(currentIndexChanged(int)), this, SLOT(pageOrderingChanged(int))); + + connect(actionFixDpi, SIGNAL(triggered(bool)), SLOT(fixDpiDialogRequested())); + connect(actionRelinking, SIGNAL(triggered(bool)), SLOT(showRelinkingDialog())); #ifndef NDEBUG - connect(actionDebug, SIGNAL(toggled(bool)), SLOT(debugToggled(bool))); + connect(actionDebug, SIGNAL(toggled(bool)), SLOT(debugToggled(bool))); #else - actionDebug->setVisible(false); + actionDebug->setVisible(false); #endif - connect(actionSettings, SIGNAL(triggered(bool)), this, SLOT(openSettingsDialog())); - connect(actionDefaults, SIGNAL(triggered(bool)), this, SLOT(openDefaultParamsDialog())); + connect(actionSettings, SIGNAL(triggered(bool)), this, SLOT(openSettingsDialog())); + connect(actionDefaults, SIGNAL(triggered(bool)), this, SLOT(openDefaultParamsDialog())); - connect(actionNewProject, SIGNAL(triggered(bool)), this, SLOT(newProject())); - connect(actionOpenProject, SIGNAL(triggered(bool)), this, SLOT(openProject())); - connect(actionSaveProject, SIGNAL(triggered(bool)), this, SLOT(saveProjectTriggered())); - connect(actionSaveProjectAs, SIGNAL(triggered(bool)), this, SLOT(saveProjectAsTriggered())); - connect(actionCloseProject, SIGNAL(triggered(bool)), this, SLOT(closeProject())); - connect(actionQuit, SIGNAL(triggered(bool)), this, SLOT(close())); + connect(actionNewProject, SIGNAL(triggered(bool)), this, SLOT(newProject())); + connect(actionOpenProject, SIGNAL(triggered(bool)), this, SLOT(openProject())); + connect(actionSaveProject, SIGNAL(triggered(bool)), this, SLOT(saveProjectTriggered())); + connect(actionSaveProjectAs, SIGNAL(triggered(bool)), this, SLOT(saveProjectAsTriggered())); + connect(actionCloseProject, SIGNAL(triggered(bool)), this, SLOT(closeProject())); + connect(actionQuit, SIGNAL(triggered(bool)), this, SLOT(close())); - updateProjectActions(); - updateWindowTitle(); - updateMainArea(); + updateProjectActions(); + updateWindowTitle(); + updateMainArea(); - QSettings settings; - if (settings.value("mainWindow/maximized") == false) { - const QVariant geom(settings.value("mainWindow/nonMaximizedGeometry")); - if (!restoreGeometry(geom.toByteArray())) { - resize(1014, 689); // A sensible value. - } + QSettings settings; + if (settings.value("mainWindow/maximized") == false) { + const QVariant geom(settings.value("mainWindow/nonMaximizedGeometry")); + if (!restoreGeometry(geom.toByteArray())) { + resize(1014, 689); // A sensible value. } - m_autoSaveProject = settings.value("settings/auto_save_project").toBool(); + } + m_autoSaveProject = settings.value("settings/auto_save_project").toBool(); } MainWindow::~MainWindow() { - m_ptrInteractiveQueue->cancelAndClear(); - if (m_ptrBatchQueue) { - m_ptrBatchQueue->cancelAndClear(); - } - m_ptrWorkerThreadPool->shutdown(); + m_interactiveQueue->cancelAndClear(); + if (m_batchQueue) { + m_batchQueue->cancelAndClear(); + } + m_workerThreadPool->shutdown(); - removeWidgetsFromLayout(m_pImageFrameLayout); - removeWidgetsFromLayout(m_pOptionsFrameLayout); - m_ptrTabbedDebugImages->clear(); + removeWidgetsFromLayout(m_imageFrameLayout); + removeWidgetsFromLayout(m_optionsFrameLayout); + m_tabbedDebugImages->clear(); } PageSequence MainWindow::allPages() const { - return m_ptrThumbSequence->toPageSequence(); + return m_thumbSequence->toPageSequence(); } std::set MainWindow::selectedPages() const { - return m_ptrThumbSequence->selectedItems(); + return m_thumbSequence->selectedItems(); } std::vector MainWindow::selectedRanges() const { - return m_ptrThumbSequence->selectedRanges(); + return m_thumbSequence->selectedRanges(); } void MainWindow::switchToNewProject(const intrusive_ptr& pages, const QString& out_dir, const QString& project_file_path, const ProjectReader* project_reader) { - stopBatchProcessing(CLEAR_MAIN_AREA); - m_ptrInteractiveQueue->cancelAndClear(); - - if (!out_dir.isEmpty()) { - Utils::maybeCreateCacheDir(out_dir); - } - m_ptrPages = pages; - m_projectFile = project_file_path; - - if (project_reader) { - m_selectedPage = project_reader->selectedPage(); - } - - intrusive_ptr disambiguator; - if (project_reader) { - disambiguator = project_reader->namingDisambiguator(); - } else { - disambiguator.reset(new FileNameDisambiguator); - } - - m_outFileNameGen = OutputFileNameGenerator(disambiguator, out_dir, pages->layoutDirection()); - // These two need to go in this order. - updateDisambiguationRecords(pages->toPageSequence(IMAGE_VIEW)); - - // Recreate the stages and load their state. - m_ptrStages = make_intrusive(pages, newPageSelectionAccessor()); - if (project_reader) { - project_reader->readFilterSettings(m_ptrStages->filters()); - } - - // Connect the filter list model to the view and select - // the first item. - { - ScopedIncDec guard(m_ignoreSelectionChanges); - filterList->setStages(m_ptrStages); - filterList->selectRow(0); - m_curFilter = 0; - // Setting a data model also implicitly sets a new - // selection model, so we have to reconnect to it. - connect(filterList->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - this, SLOT(filterSelectionChanged(const QItemSelection&))); - } + stopBatchProcessing(CLEAR_MAIN_AREA); + m_interactiveQueue->cancelAndClear(); + + if (!out_dir.isEmpty()) { + Utils::maybeCreateCacheDir(out_dir); + } + m_pages = pages; + m_projectFile = project_file_path; + + if (project_reader) { + m_selectedPage = project_reader->selectedPage(); + } + + intrusive_ptr disambiguator; + if (project_reader) { + disambiguator = project_reader->namingDisambiguator(); + } else { + disambiguator.reset(new FileNameDisambiguator); + } + + m_outFileNameGen = OutputFileNameGenerator(disambiguator, out_dir, pages->layoutDirection()); + // These two need to go in this order. + updateDisambiguationRecords(pages->toPageSequence(IMAGE_VIEW)); + + // Recreate the stages and load their state. + m_stages = make_intrusive(pages, newPageSelectionAccessor()); + if (project_reader) { + project_reader->readFilterSettings(m_stages->filters()); + } + + // Connect the filter list model to the view and select + // the first item. + { + ScopedIncDec guard(m_ignoreSelectionChanges); + filterList->setStages(m_stages); + filterList->selectRow(0); + m_curFilter = 0; + // Setting a data model also implicitly sets a new + // selection model, so we have to reconnect to it. + connect(filterList->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, + SLOT(filterSelectionChanged(const QItemSelection&))); + } - updateSortOptions(); + updateSortOptions(); - m_ptrContentBoxPropagator = std::make_unique( - m_ptrStages->pageLayoutFilter(), createCompositeCacheDrivenTask(m_ptrStages->selectContentFilterIdx())); + m_contentBoxPropagator = std::make_unique( + m_stages->pageLayoutFilter(), createCompositeCacheDrivenTask(m_stages->selectContentFilterIdx())); - m_ptrPageOrientationPropagator = std::make_unique( - m_ptrStages->pageSplitFilter(), createCompositeCacheDrivenTask(m_ptrStages->fixOrientationFilterIdx())); + m_pageOrientationPropagator = std::make_unique( + m_stages->pageSplitFilter(), createCompositeCacheDrivenTask(m_stages->fixOrientationFilterIdx())); - // Thumbnails are stored relative to the output directory, - // so recreate the thumbnail cache. - if (out_dir.isEmpty()) { - m_ptrThumbnailCache.reset(); - } else { - m_ptrThumbnailCache = Utils::createThumbnailCache(m_outFileNameGen.outDir()); - } - resetThumbSequence(currentPageOrderProvider()); + // Thumbnails are stored relative to the output directory, + // so recreate the thumbnail cache. + if (out_dir.isEmpty()) { + m_thumbnailCache.reset(); + } else { + m_thumbnailCache = Utils::createThumbnailCache(m_outFileNameGen.outDir()); + } + resetThumbSequence(currentPageOrderProvider()); - removeFilterOptionsWidget(); - updateProjectActions(); - updateWindowTitle(); - updateMainArea(); + removeFilterOptionsWidget(); + updateProjectActions(); + updateWindowTitle(); + updateMainArea(); - if (!QDir(out_dir).exists()) { - showRelinkingDialog(); - } + if (!QDir(out_dir).exists()) { + showRelinkingDialog(); + } } // MainWindow::switchToNewProject void MainWindow::showNewOpenProjectPanel() { - std::unique_ptr outer_widget(new QWidget); - QGridLayout* layout = new QGridLayout(outer_widget.get()); - outer_widget->setLayout(layout); - - NewOpenProjectPanel* nop = new NewOpenProjectPanel(outer_widget.get()); - // We use asynchronous connections because otherwise we - // would be deleting a widget from its event handler, which - // Qt doesn't like. - connect(nop, SIGNAL(newProject()), this, SLOT(newProject()), Qt::QueuedConnection); - connect(nop, SIGNAL(openProject()), this, SLOT(openProject()), Qt::QueuedConnection); - connect(nop, SIGNAL(openRecentProject(const QString&)), this, SLOT(openProject(const QString&)), - Qt::QueuedConnection); - - layout->addWidget(nop, 1, 1); - layout->setColumnStretch(0, 1); - layout->setColumnStretch(2, 1); - layout->setRowStretch(0, 1); - layout->setRowStretch(2, 1); - setImageWidget(outer_widget.release(), TRANSFER_OWNERSHIP); - - filterList->setBatchProcessingPossible(false); + std::unique_ptr outer_widget(new QWidget); + QGridLayout* layout = new QGridLayout(outer_widget.get()); + outer_widget->setLayout(layout); + + NewOpenProjectPanel* nop = new NewOpenProjectPanel(outer_widget.get()); + // We use asynchronous connections because otherwise we + // would be deleting a widget from its event handler, which + // Qt doesn't like. + connect(nop, SIGNAL(newProject()), this, SLOT(newProject()), Qt::QueuedConnection); + connect(nop, SIGNAL(openProject()), this, SLOT(openProject()), Qt::QueuedConnection); + connect(nop, SIGNAL(openRecentProject(const QString&)), this, SLOT(openProject(const QString&)), + Qt::QueuedConnection); + + layout->addWidget(nop, 1, 1); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(2, 1); + layout->setRowStretch(0, 1); + layout->setRowStretch(2, 1); + setImageWidget(outer_widget.release(), TRANSFER_OWNERSHIP); + + filterList->setBatchProcessingPossible(false); } // MainWindow::showNewOpenProjectPanel void MainWindow::createBatchProcessingWidget() { - m_ptrBatchProcessingWidget.reset(new QWidget); - QGridLayout* layout = new QGridLayout(m_ptrBatchProcessingWidget.get()); - m_ptrBatchProcessingWidget->setLayout(layout); - - SkinnedButton* stop_btn = new SkinnedButton(":/icons/stop-big.png", ":/icons/stop-big-hovered.png", - ":/icons/stop-big-pressed.png", m_ptrBatchProcessingWidget.get()); - stop_btn->setStatusTip(tr("Stop batch processing")); - - class LowerPanel : public QWidget { - public: - LowerPanel(QWidget* parent = 0) : QWidget(parent) { - ui.setupUi(this); - } + m_batchProcessingWidget.reset(new QWidget); + QGridLayout* layout = new QGridLayout(m_batchProcessingWidget.get()); + m_batchProcessingWidget->setLayout(layout); - Ui::BatchProcessingLowerPanel ui; - }; + SkinnedButton* stop_btn = new SkinnedButton(":/icons/stop-big.png", ":/icons/stop-big-hovered.png", + ":/icons/stop-big-pressed.png", m_batchProcessingWidget.get()); + stop_btn->setStatusTip(tr("Stop batch processing")); + class LowerPanel : public QWidget { + public: + LowerPanel(QWidget* parent = 0) : QWidget(parent) { ui.setupUi(this); } - LowerPanel* lower_panel = new LowerPanel(m_ptrBatchProcessingWidget.get()); - m_checkBeepWhenFinished = [lower_panel]() { return lower_panel->ui.beepWhenFinished->isChecked(); }; + Ui::BatchProcessingLowerPanel ui; + }; - int row = 0; // Row 0 is reserved. - layout->addWidget(stop_btn, ++row, 1, Qt::AlignCenter); - layout->addWidget(lower_panel, ++row, 0, 1, 3, Qt::AlignHCenter | Qt::AlignTop); - layout->setColumnStretch(0, 1); - layout->setColumnStretch(2, 1); - layout->setRowStretch(0, 1); - layout->setRowStretch(row, 1); - connect(stop_btn, SIGNAL(clicked()), SLOT(stopBatchProcessing())); + LowerPanel* lower_panel = new LowerPanel(m_batchProcessingWidget.get()); + m_checkBeepWhenFinished = [lower_panel]() { return lower_panel->ui.beepWhenFinished->isChecked(); }; + + int row = 0; // Row 0 is reserved. + layout->addWidget(stop_btn, ++row, 1, Qt::AlignCenter); + layout->addWidget(lower_panel, ++row, 0, 1, 3, Qt::AlignHCenter | Qt::AlignTop); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(2, 1); + layout->setRowStretch(0, 1); + layout->setRowStretch(row, 1); + + connect(stop_btn, SIGNAL(clicked()), SLOT(stopBatchProcessing())); } // MainWindow::createBatchProcessingWidget void MainWindow::setupThumbView() { - const int sb = thumbView->style()->pixelMetric(QStyle::PM_ScrollBarExtent); - int inner_width = thumbView->maximumViewportSize().width() - sb; - if (thumbView->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, thumbView)) { - inner_width -= thumbView->frameWidth() * 2; - } - const int delta_x = thumbView->size().width() - inner_width; - thumbView->setMinimumWidth((int) std::ceil(m_maxLogicalThumbSize.width() + delta_x)); + const int sb = thumbView->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + int inner_width = thumbView->maximumViewportSize().width() - sb; + if (thumbView->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, thumbView)) { + inner_width -= thumbView->frameWidth() * 2; + } + const int delta_x = thumbView->size().width() - inner_width; + thumbView->setMinimumWidth((int) std::ceil(m_maxLogicalThumbSize.width() + delta_x)); - m_ptrThumbSequence->attachView(thumbView); + m_thumbSequence->attachView(thumbView); - thumbView->installEventFilter(this); + thumbView->installEventFilter(this); } bool MainWindow::eventFilter(QObject* obj, QEvent* ev) { - if ((obj == thumbView) && (ev->type() == QEvent::Resize)) { - m_thumbResizeTimer.start(200); - } + if ((obj == thumbView) && (ev->type() == QEvent::Resize)) { + emit invalidateAllThumbnails(); + } - return false; + if ((obj == thumbView || obj == thumbView->verticalScrollBar()) && (ev->type() == QEvent::Wheel)) { + QWheelEvent* wheel_event = static_cast(ev); + if (wheel_event->modifiers() == Qt::AltModifier) { + scaleThumbnails(wheel_event); + wheel_event->accept(); + return true; + } + } + return false; } void MainWindow::closeEvent(QCloseEvent* const event) { - if (m_closing) { - event->accept(); - } else { - event->ignore(); - startTimer(0); - } + if (m_closing) { + event->accept(); + } else { + event->ignore(); + startTimer(0); + } } void MainWindow::timerEvent(QTimerEvent* const event) { - // We only use the timer event for delayed closing of the window. - killTimer(event->timerId()); + // 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(); + if (closeProjectInteractive()) { + m_closing = true; + QSettings settings; + settings.setValue("mainWindow/maximized", isMaximized()); + if (!isMaximized()) { + settings.setValue("mainWindow/nonMaximizedGeometry", saveGeometry()); } + close(); + } } MainWindow::SavePromptResult MainWindow::promptProjectSave() { - QMessageBox msgBox(QMessageBox::Question, tr("Save Project"), tr("Save the project?"), - QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, this); - msgBox.setDefaultButton(QMessageBox::Save); - - msgBox.setButtonText(QMessageBox::Save, tr("Save")); - msgBox.setButtonText(QMessageBox::Discard, tr("Discard")); - msgBox.setButtonText(QMessageBox::Cancel, tr("Cancel")); - - switch (msgBox.exec()) { - case QMessageBox::Save: - return SAVE; - case QMessageBox::Discard: - return DONT_SAVE; - default: - return CANCEL; - } + QMessageBox msgBox(QMessageBox::Question, tr("Save Project"), tr("Save the project?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this); + msgBox.setDefaultButton(QMessageBox::Yes); + + switch (msgBox.exec()) { + case QMessageBox::Yes: + return SAVE; + case QMessageBox::No: + return DONT_SAVE; + default: + return CANCEL; + } } bool MainWindow::compareFiles(const QString& fpath1, const QString& fpath2) { - QFile file1(fpath1); - QFile file2(fpath2); + QFile file1(fpath1); + QFile file2(fpath2); - if (!file1.open(QIODevice::ReadOnly)) { - return false; - } - if (!file2.open(QIODevice::ReadOnly)) { - return false; - } + if (!file1.open(QIODevice::ReadOnly)) { + return false; + } + if (!file2.open(QIODevice::ReadOnly)) { + return false; + } - if (!file1.isSequential() && !file2.isSequential()) { - if (file1.size() != file2.size()) { - return false; - } + if (!file1.isSequential() && !file2.isSequential()) { + if (file1.size() != file2.size()) { + return false; } + } - const int chunk_size = 4096; - for (;;) { - const QByteArray chunk1(file1.read(chunk_size)); - const QByteArray chunk2(file2.read(chunk_size)); - if (chunk1.size() != chunk2.size()) { - return false; - } else if (chunk1.size() == 0) { - return true; - } + const int chunk_size = 4096; + while (true) { + const QByteArray chunk1(file1.read(chunk_size)); + const QByteArray chunk2(file2.read(chunk_size)); + if (chunk1.size() != chunk2.size()) { + return false; + } else if (chunk1.size() == 0) { + return true; } + } } intrusive_ptr MainWindow::currentPageOrderProvider() const { - const int idx = sortOptions->currentIndex(); - if (idx < 0) { - return nullptr; - } + const int idx = sortOptions->currentIndex(); + if (idx < 0) { + return nullptr; + } - const intrusive_ptr filter(m_ptrStages->filterAt(m_curFilter)); + const intrusive_ptr filter(m_stages->filterAt(m_curFilter)); - return filter->pageOrderOptions()[idx].provider(); + return filter->pageOrderOptions()[idx].provider(); } void MainWindow::updateSortOptions() { - const ScopedIncDec guard(m_ignorePageOrderingChanges); + const ScopedIncDec guard(m_ignorePageOrderingChanges); - const intrusive_ptr filter(m_ptrStages->filterAt(m_curFilter)); + const intrusive_ptr filter(m_stages->filterAt(m_curFilter)); - sortOptions->clear(); + sortOptions->clear(); - for (const PageOrderOption& opt : filter->pageOrderOptions()) { - sortOptions->addItem(opt.name()); - } + for (const PageOrderOption& opt : filter->pageOrderOptions()) { + sortOptions->addItem(opt.name()); + } - sortOptions->setVisible(sortOptions->count() > 0); + sortOptions->setVisible(sortOptions->count() > 0); - if (sortOptions->count() > 0) { - sortOptions->setCurrentIndex(filter->selectedPageOrder()); - } + if (sortOptions->count() > 0) { + sortOptions->setCurrentIndex(filter->selectedPageOrder()); + } } -void MainWindow::resetThumbSequence(const intrusive_ptr& page_order_provider) { - if (m_ptrThumbnailCache) { - const intrusive_ptr task(createCompositeCacheDrivenTask(m_curFilter)); +void MainWindow::resetThumbSequence(const intrusive_ptr& page_order_provider, + const ThumbnailSequence::SelectionAction selection_action) { + if (m_thumbnailCache) { + const intrusive_ptr task(createCompositeCacheDrivenTask(m_curFilter)); - m_ptrThumbSequence->setThumbnailFactory( - make_intrusive(m_ptrThumbnailCache, m_maxLogicalThumbSize, task)); - } + m_thumbSequence->setThumbnailFactory( + make_intrusive(m_thumbnailCache, m_maxLogicalThumbSize, task)); + } - m_ptrThumbSequence->reset(m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::RESET_SELECTION, - page_order_provider); + m_thumbSequence->reset(m_pages->toPageSequence(getCurrentView()), selection_action, page_order_provider); - if (!m_ptrThumbnailCache) { - // Empty project. - assert(m_ptrPages->numImages() == 0); - m_ptrThumbSequence->setThumbnailFactory(nullptr); - } + if (!m_thumbnailCache) { + // Empty project. + assert(m_pages->numImages() == 0); + m_thumbSequence->setThumbnailFactory(nullptr); + } + if (selection_action != ThumbnailSequence::KEEP_SELECTION) { const PageId page(m_selectedPage.get(getCurrentView())); - if (m_ptrThumbSequence->setSelection(page)) { - // OK - } else if (m_ptrThumbSequence->setSelection(PageId(page.imageId(), PageId::LEFT_PAGE))) { - // OK - } else if (m_ptrThumbSequence->setSelection(PageId(page.imageId(), PageId::RIGHT_PAGE))) { - // OK - } else if (m_ptrThumbSequence->setSelection(PageId(page.imageId(), PageId::SINGLE_PAGE))) { - // OK + if (m_thumbSequence->setSelection(page)) { + // OK + } else if (m_thumbSequence->setSelection(PageId(page.imageId(), PageId::LEFT_PAGE))) { + // OK + } else if (m_thumbSequence->setSelection(PageId(page.imageId(), PageId::RIGHT_PAGE))) { + // OK + } else if (m_thumbSequence->setSelection(PageId(page.imageId(), PageId::SINGLE_PAGE))) { + // OK } else { - // Last resort. - m_ptrThumbSequence->setSelection(m_ptrThumbSequence->firstPage().id()); + // Last resort. + m_thumbSequence->setSelection(m_thumbSequence->firstPage().id()); } -} // MainWindow::resetThumbSequence + } +} void MainWindow::setOptionsWidget(FilterOptionsWidget* widget, const Ownership ownership) { - if (isBatchProcessingInProgress()) { - if (ownership == TRANSFER_OWNERSHIP) { - delete widget; - } - - return; - } - - if (m_ptrOptionsWidget != widget) { - removeWidgetsFromLayout(m_pOptionsFrameLayout); - } - // Delete the old widget we were owning, if any. - m_optionsWidgetCleanup.clear(); - + if (isBatchProcessingInProgress()) { if (ownership == TRANSFER_OWNERSHIP) { - m_optionsWidgetCleanup.add(widget); - } - - if (m_ptrOptionsWidget == widget) { - return; - } - - if (m_ptrOptionsWidget) { - disconnect(m_ptrOptionsWidget, SIGNAL(reloadRequested()), this, SLOT(reloadRequested())); - disconnect(m_ptrOptionsWidget, SIGNAL(invalidateThumbnail(const PageId&)), this, - SLOT(invalidateThumbnail(const PageId&))); - disconnect(m_ptrOptionsWidget, SIGNAL(invalidateThumbnail(const PageInfo&)), this, - SLOT(invalidateThumbnail(const PageInfo&))); - disconnect(m_ptrOptionsWidget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails())); - disconnect(m_ptrOptionsWidget, SIGNAL(goToPage(const PageId&)), this, SLOT(goToPage(const PageId&))); - } - - m_pOptionsFrameLayout->addWidget(widget); - m_ptrOptionsWidget = widget; - - // We use an asynchronous connection here, because the slot - // will probably delete the options panel, which could be - // responsible for the emission of this signal. Qt doesn't - // like when we delete an object while it's emitting a singal. - connect(widget, SIGNAL(reloadRequested()), this, SLOT(reloadRequested()), Qt::QueuedConnection); - connect(widget, SIGNAL(invalidateThumbnail(const PageId&)), this, SLOT(invalidateThumbnail(const PageId&))); - connect(widget, SIGNAL(invalidateThumbnail(const PageInfo&)), this, SLOT(invalidateThumbnail(const PageInfo&))); - connect(widget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails())); - connect(widget, SIGNAL(goToPage(const PageId&)), this, SLOT(goToPage(const PageId&))); + delete widget; + } + + return; + } + + if (m_optionsWidget != widget) { + removeWidgetsFromLayout(m_optionsFrameLayout); + } + // Delete the old widget we were owning, if any. + m_optionsWidgetCleanup.clear(); + + if (ownership == TRANSFER_OWNERSHIP) { + m_optionsWidgetCleanup.add(widget); + } + + if (m_optionsWidget == widget) { + return; + } + + if (m_optionsWidget) { + disconnect(m_optionsWidget, SIGNAL(reloadRequested()), this, SLOT(reloadRequested())); + disconnect(m_optionsWidget, SIGNAL(invalidateThumbnail(const PageId&)), this, + SLOT(invalidateThumbnail(const PageId&))); + disconnect(m_optionsWidget, SIGNAL(invalidateThumbnail(const PageInfo&)), this, + SLOT(invalidateThumbnail(const PageInfo&))); + disconnect(m_optionsWidget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails())); + disconnect(m_optionsWidget, SIGNAL(goToPage(const PageId&)), this, SLOT(goToPage(const PageId&))); + } + + m_optionsFrameLayout->addWidget(widget); + m_optionsWidget = widget; + + // We use an asynchronous connection here, because the slot + // will probably delete the options panel, which could be + // responsible for the emission of this signal. Qt doesn't + // like when we delete an object while it's emitting a singal. + connect(widget, SIGNAL(reloadRequested()), this, SLOT(reloadRequested()), Qt::QueuedConnection); + connect(widget, SIGNAL(invalidateThumbnail(const PageId&)), this, SLOT(invalidateThumbnail(const PageId&))); + connect(widget, SIGNAL(invalidateThumbnail(const PageInfo&)), this, SLOT(invalidateThumbnail(const PageInfo&))); + connect(widget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails())); + connect(widget, SIGNAL(goToPage(const PageId&)), this, SLOT(goToPage(const PageId&))); } // MainWindow::setOptionsWidget -void MainWindow::setImageWidget(QWidget* widget, - const Ownership ownership, - DebugImages* debug_images, - bool clear_image_widget) { - if (isBatchProcessingInProgress() && (widget != m_ptrBatchProcessingWidget.get())) { - if (ownership == TRANSFER_OWNERSHIP) { - delete widget; - } - - return; - } - - QWidget* current_widget = m_pImageFrameLayout->currentWidget(); - if (dynamic_cast(current_widget) != nullptr) { - if (!clear_image_widget) { - return; - } - } - - bool current_widget_is_image = (Utils::castOrFindChild(current_widget) != nullptr); - - if (clear_image_widget || !current_widget_is_image) { - removeImageWidget(); - } - +void MainWindow::setImageWidget(QWidget* widget, const Ownership ownership, DebugImages* debug_images, bool overlay) { + if (isBatchProcessingInProgress() && (widget != m_batchProcessingWidget.get())) { if (ownership == TRANSFER_OWNERSHIP) { - m_imageWidgetCleanup.add(widget); - } - - if (!debug_images || debug_images->empty()) { - m_pImageFrameLayout->addWidget(widget); - if (!clear_image_widget && current_widget_is_image) { - m_pImageFrameLayout->setCurrentWidget(widget); - } - } else { - m_ptrTabbedDebugImages->addTab(widget, "Main"); - AutoRemovingFile file; - QString label; - while (!(file = debug_images->retrieveNext(&label)).get().isNull()) { - QWidget* widget = new DebugImageView(file); - m_imageWidgetCleanup.add(widget); - m_ptrTabbedDebugImages->addTab(widget, label); - } - m_pImageFrameLayout->addWidget(m_ptrTabbedDebugImages.get()); - } + delete widget; + } + return; + } + + if (!overlay) { + removeImageWidget(); + } + + if (ownership == TRANSFER_OWNERSHIP) { + m_imageWidgetCleanup.add(widget); + } + + if (!debug_images || debug_images->empty()) { + if (widget != m_imageFrameLayout->currentWidget()) { + m_imageFrameLayout->addWidget(widget); + if (overlay) { + m_imageFrameLayout->setCurrentWidget(widget); + } + } + } else { + m_tabbedDebugImages->addTab(widget, "Main"); + AutoRemovingFile file; + QString label; + while (!(file = debug_images->retrieveNext(&label)).get().isNull()) { + QWidget* view = new DebugImageView(file); + m_imageWidgetCleanup.add(view); + m_tabbedDebugImages->addTab(view, label); + } + m_imageFrameLayout->addWidget(m_tabbedDebugImages.get()); + } } // MainWindow::setImageWidget void MainWindow::removeImageWidget() { - removeWidgetsFromLayout(m_pImageFrameLayout); + removeWidgetsFromLayout(m_imageFrameLayout); - m_ptrTabbedDebugImages->clear(); - // Delete the old widget we were owning, if any. - m_imageWidgetCleanup.clear(); + m_tabbedDebugImages->clear(); + // Delete the old widget we were owning, if any. + m_imageWidgetCleanup.clear(); } void MainWindow::invalidateThumbnail(const PageId& page_id) { - m_ptrThumbSequence->invalidateThumbnail(page_id); + m_thumbSequence->invalidateThumbnail(page_id); } void MainWindow::invalidateThumbnail(const PageInfo& page_info) { - m_ptrThumbSequence->invalidateThumbnail(page_info); + m_thumbSequence->invalidateThumbnail(page_info); } void MainWindow::invalidateAllThumbnails() { - m_ptrThumbSequence->invalidateAllThumbnails(); + m_thumbSequence->invalidateAllThumbnails(); } intrusive_ptr> MainWindow::relinkingDialogRequester() { - class Requester : public AbstractCommand { - public: - Requester(MainWindow* wnd) : m_ptrWnd(wnd) { - } + class Requester : public AbstractCommand { + public: + Requester(MainWindow* wnd) : m_wnd(wnd) {} - virtual void operator()() { - if (MainWindow* wnd = m_ptrWnd) { - wnd->showRelinkingDialog(); - } - } + virtual void operator()() { + if (!m_wnd.isNull()) { + m_wnd->showRelinkingDialog(); + } + } - private: - QPointer m_ptrWnd; - }; + private: + QPointer m_wnd; + }; - return make_intrusive(this); + return make_intrusive(this); } void MainWindow::showRelinkingDialog() { - if (!isProjectLoaded()) { - return; - } + if (!isProjectLoaded()) { + return; + } - RelinkingDialog* dialog = new RelinkingDialog(m_projectFile, this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowModality(Qt::WindowModal); + RelinkingDialog* dialog = new RelinkingDialog(m_projectFile, this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowModality(Qt::WindowModal); - m_ptrPages->listRelinkablePaths(dialog->pathCollector()); - dialog->pathCollector()(RelinkablePath(m_outFileNameGen.outDir(), RelinkablePath::Dir)); + m_pages->listRelinkablePaths(dialog->pathCollector()); + dialog->pathCollector()(RelinkablePath(m_outFileNameGen.outDir(), RelinkablePath::Dir)); - connect(dialog, &QDialog::accepted, [this, dialog]() { this->performRelinking(dialog->relinker()); }); + connect(dialog, &QDialog::accepted, [this, dialog]() { this->performRelinking(dialog->relinker()); }); - dialog->show(); + dialog->show(); } void MainWindow::performRelinking(const intrusive_ptr& relinker) { - assert(relinker); + assert(relinker); - if (!isProjectLoaded()) { - return; - } + if (!isProjectLoaded()) { + return; + } - m_ptrPages->performRelinking(*relinker); - m_ptrStages->performRelinking(*relinker); - m_outFileNameGen.performRelinking(*relinker); + m_pages->performRelinking(*relinker); + m_stages->performRelinking(*relinker); + m_outFileNameGen.performRelinking(*relinker); - Utils::maybeCreateCacheDir(m_outFileNameGen.outDir()); + Utils::maybeCreateCacheDir(m_outFileNameGen.outDir()); - m_ptrThumbnailCache->setThumbDir(Utils::outputDirToThumbDir(m_outFileNameGen.outDir())); - resetThumbSequence(currentPageOrderProvider()); - m_selectedPage.set(m_ptrThumbSequence->selectionLeader().id(), getCurrentView()); + m_thumbnailCache->setThumbDir(Utils::outputDirToThumbDir(m_outFileNameGen.outDir())); + resetThumbSequence(currentPageOrderProvider()); + m_selectedPage.set(m_thumbSequence->selectionLeader().id(), getCurrentView()); - reloadRequested(); + reloadRequested(); } void MainWindow::goFirstPage() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - const PageInfo first_page(m_ptrThumbSequence->firstPage()); - if (!first_page.isNull()) { - goToPage(first_page.id()); - } + const PageInfo first_page(m_thumbSequence->firstPage()); + if (!first_page.isNull()) { + goToPage(first_page.id()); + } } void MainWindow::goLastPage() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - const PageInfo last_page(m_ptrThumbSequence->lastPage()); - if (!last_page.isNull()) { - goToPage(last_page.id()); - } + const PageInfo last_page(m_thumbSequence->lastPage()); + if (!last_page.isNull()) { + goToPage(last_page.id()); + } } void MainWindow::goNextPage() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - const PageInfo next_page(m_ptrThumbSequence->nextPage(m_ptrThumbSequence->selectionLeader().id())); - if (!next_page.isNull()) { - goToPage(next_page.id()); - } + const PageInfo next_page(m_thumbSequence->nextPage(m_thumbSequence->selectionLeader().id())); + if (!next_page.isNull()) { + goToPage(next_page.id()); + } } void MainWindow::goPrevPage() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - const PageInfo prev_page(m_ptrThumbSequence->prevPage(m_ptrThumbSequence->selectionLeader().id())); - if (!prev_page.isNull()) { - goToPage(prev_page.id()); - } + const PageInfo prev_page(m_thumbSequence->prevPage(m_thumbSequence->selectionLeader().id())); + if (!prev_page.isNull()) { + goToPage(prev_page.id()); + } } -void MainWindow::goToPage(const PageId& page_id) { - focusButton->setChecked(true); +void MainWindow::goNextSelectedPage() { + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - m_ptrThumbSequence->setSelection(page_id); + const PageInfo next_selected_page(m_thumbSequence->nextSelectedPage(m_thumbSequence->selectionLeader().id())); + if (!next_selected_page.isNull()) { + goToPage(next_selected_page.id(), ThumbnailSequence::KEEP_SELECTION); + } +} - // If the page was already selected, it will be reloaded. - // That's by design. - updateMainArea(); +void MainWindow::goPrevSelectedPage() { + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - if (m_autoSaveTimer.remainingTime() <= 0) { - m_autoSaveTimer.start(30000); - } + const PageInfo prev_selected_page(m_thumbSequence->prevSelectedPage(m_thumbSequence->selectionLeader().id())); + if (!prev_selected_page.isNull()) { + goToPage(prev_selected_page.id(), ThumbnailSequence::KEEP_SELECTION); + } +} + +void MainWindow::goToPage(const PageId& page_id, const ThumbnailSequence::SelectionAction selection_action) { + focusButton->setChecked(true); + + m_thumbSequence->setSelection(page_id, selection_action); + + // If the page was already selected, it will be reloaded. + // That's by design. + updateMainArea(); + + if (m_autoSaveTimer.remainingTime() <= 0) { + m_autoSaveTimer.start(30000); + } } void MainWindow::currentPageChanged(const PageInfo& page_info, const QRectF& thumb_rect, const ThumbnailSequence::SelectionFlags flags) { - m_selectedPage.set(page_info.id(), getCurrentView()); + m_selectedPage.set(page_info.id(), getCurrentView()); - if ((flags & ThumbnailSequence::SELECTED_BY_USER) || focusButton->isChecked()) { - if (!(flags & ThumbnailSequence::AVOID_SCROLLING_TO)) { - thumbView->ensureVisible(thumb_rect, 0, 0); - } + if ((flags & ThumbnailSequence::SELECTED_BY_USER) || focusButton->isChecked()) { + if (!(flags & ThumbnailSequence::AVOID_SCROLLING_TO)) { + thumbView->ensureVisible(thumb_rect, 0, 0); } + } - if (flags & ThumbnailSequence::SELECTED_BY_USER) { - if (isBatchProcessingInProgress()) { - stopBatchProcessing(); - } else if (!(flags & ThumbnailSequence::REDUNDANT_SELECTION)) { - // Start loading / processing the newly selected page. - updateMainArea(); - } + if (flags & ThumbnailSequence::SELECTED_BY_USER) { + if (isBatchProcessingInProgress()) { + stopBatchProcessing(); + } else if (!(flags & ThumbnailSequence::REDUNDANT_SELECTION)) { + // Start loading / processing the newly selected page. + updateMainArea(); } + } - if (flags & ThumbnailSequence::SELECTED_BY_USER) { - if (m_autoSaveTimer.remainingTime() <= 0) { - m_autoSaveTimer.start(30000); - } + if (flags & ThumbnailSequence::SELECTED_BY_USER) { + if (m_autoSaveTimer.remainingTime() <= 0) { + m_autoSaveTimer.start(30000); } + } } void MainWindow::autoSaveProject() { - if (m_projectFile.isEmpty()) { - return; - } + if (m_projectFile.isEmpty()) { + return; + } - if (!m_autoSaveProject) { - return; - } + if (!m_autoSaveProject) { + return; + } - saveProjectWithFeedback(m_projectFile); + saveProjectWithFeedback(m_projectFile); } void MainWindow::pageContextMenuRequested(const PageInfo& page_info_, const QPoint& screen_pos, bool selected) { - if (isBatchProcessingInProgress()) { - return; - } - // Make a copy to prevent it from being invalidated. - const PageInfo page_info(page_info_); + if (isBatchProcessingInProgress()) { + return; + } + // Make a copy to prevent it from being invalidated. + const PageInfo page_info(page_info_); - if (!selected) { - goToPage(page_info.id()); - } + if (!selected) { + goToPage(page_info.id()); + } - QMenu menu; + QMenu menu; - QAction* ins_before = menu.addAction(QIcon(":/icons/insert-before-16.png"), tr("Insert before ...")); - QAction* ins_after = menu.addAction(QIcon(":/icons/insert-after-16.png"), tr("Insert after ...")); + QAction* ins_before = menu.addAction(QIcon(":/icons/insert-before-16.png"), tr("Insert before ...")); + QAction* ins_after = menu.addAction(QIcon(":/icons/insert-after-16.png"), tr("Insert after ...")); - menu.addSeparator(); + menu.addSeparator(); - QAction* remove = menu.addAction(QIcon(":/icons/user-trash.png"), tr("Remove from project ...")); + QAction* remove = menu.addAction(QIcon(":/icons/user-trash.png"), tr("Remove from project ...")); - QAction* action = menu.exec(screen_pos); - if (action == ins_before) { - showInsertFileDialog(BEFORE, page_info.imageId()); - } else if (action == ins_after) { - showInsertFileDialog(AFTER, page_info.imageId()); - } else if (action == remove) { - showRemovePagesDialog(m_ptrThumbSequence->selectedItems()); - } + QAction* action = menu.exec(screen_pos); + if (action == ins_before) { + showInsertFileDialog(BEFORE, page_info.imageId()); + } else if (action == ins_after) { + showInsertFileDialog(AFTER, page_info.imageId()); + } else if (action == remove) { + showRemovePagesDialog(m_thumbSequence->selectedItems()); + } } // MainWindow::pageContextMenuRequested void MainWindow::pastLastPageContextMenuRequested(const QPoint& screen_pos) { - if (!isProjectLoaded()) { - return; - } + if (!isProjectLoaded()) { + return; + } - QMenu menu; - menu.addAction(QIcon(":/icons/insert-here-16.png"), tr("Insert here ...")); + QMenu menu; + menu.addAction(QIcon(":/icons/insert-here-16.png"), tr("Insert here ...")); - if (menu.exec(screen_pos)) { - showInsertFileDialog(BEFORE, ImageId()); - } + if (menu.exec(screen_pos)) { + showInsertFileDialog(BEFORE, ImageId()); + } } void MainWindow::thumbViewFocusToggled(const bool checked) { - const QRectF rect(m_ptrThumbSequence->selectionLeaderSceneRect()); - if (rect.isNull()) { - // No selected items. - return; - } + const QRectF rect(m_thumbSequence->selectionLeaderSceneRect()); + if (rect.isNull()) { + // No selected items. + return; + } - if (checked) { - thumbView->ensureVisible(rect, 0, 0); - } + if (checked) { + thumbView->ensureVisible(rect, 0, 0); + } } void MainWindow::thumbViewScrolled() { - const QRectF rect(m_ptrThumbSequence->selectionLeaderSceneRect()); - if (rect.isNull()) { - // No items selected. - return; - } - - const QRectF viewport_rect(thumbView->viewport()->rect()); - const QRectF viewport_item_rect(thumbView->viewportTransform().mapRect(rect)); - - const double intersection_threshold = 0.5; - if ((viewport_item_rect.top() >= viewport_rect.top()) - && (viewport_item_rect.top() + viewport_item_rect.height() * intersection_threshold - <= viewport_rect.bottom())) { - // Item is visible. - } else if ((viewport_item_rect.bottom() <= viewport_rect.bottom()) - && (viewport_item_rect.bottom() - viewport_item_rect.height() * intersection_threshold - >= viewport_rect.top())) { - // Item is visible. - } else { - focusButton->setChecked(false); - } + const QRectF rect(m_thumbSequence->selectionLeaderSceneRect()); + if (rect.isNull()) { + // No items selected. + return; + } + + const QRectF viewport_rect(thumbView->viewport()->rect()); + const QRectF viewport_item_rect(thumbView->viewportTransform().mapRect(rect)); + + const double intersection_threshold = 0.5; + if ((viewport_item_rect.top() >= viewport_rect.top()) + && (viewport_item_rect.top() + viewport_item_rect.height() * intersection_threshold <= viewport_rect.bottom())) { + // Item is visible. + } else if ((viewport_item_rect.bottom() <= viewport_rect.bottom()) + && (viewport_item_rect.bottom() - viewport_item_rect.height() * intersection_threshold + >= viewport_rect.top())) { + // Item is visible. + } else { + focusButton->setChecked(false); + } } void MainWindow::filterSelectionChanged(const QItemSelection& selected) { - if (m_ignoreSelectionChanges) { - return; - } - - if (selected.empty()) { - return; - } - - m_ptrInteractiveQueue->cancelAndClear(); - if (m_ptrBatchQueue) { - // Should not happen, but just in case. - m_ptrBatchQueue->cancelAndClear(); - } - - const bool was_below_fix_orientation = isBelowFixOrientation(m_curFilter); - const bool was_below_select_content = isBelowSelectContent(m_curFilter); - m_curFilter = selected.front().top(); - const bool now_below_fix_orientation = isBelowFixOrientation(m_curFilter); - const bool now_below_select_content = isBelowSelectContent(m_curFilter); - - m_ptrStages->filterAt(m_curFilter)->selected(); - - updateSortOptions(); - - // Propagate context boxes down the stage list, if necessary. - if (!was_below_select_content && now_below_select_content) { - // IMPORTANT: this needs to go before resetting thumbnails, - // because it may affect them. - if (m_ptrContentBoxPropagator) { - m_ptrContentBoxPropagator->propagate(*m_ptrPages); - } // Otherwise probably no project is loaded. - } - // Propagate page orientations (that might have changed) to the "Split Pages" stage. - if (!was_below_fix_orientation && now_below_fix_orientation) { - // IMPORTANT: this needs to go before resetting thumbnails, - // because it may affect them. - if (m_ptrPageOrientationPropagator) { - m_ptrPageOrientationPropagator->propagate(*m_ptrPages); - } // Otherwise probably no project is loaded. - } - - const int hor_scroll_bar_pos = thumbView->horizontalScrollBar()->value(); - const int ver_scroll_bar_pos = thumbView->verticalScrollBar()->value(); - - resetThumbSequence(currentPageOrderProvider()); - - if (!focusButton->isChecked()) { - thumbView->horizontalScrollBar()->setValue(hor_scroll_bar_pos); - thumbView->verticalScrollBar()->setValue(ver_scroll_bar_pos); - } - - // load default settings for all the pages - for (const PageInfo& pageInfo : m_ptrThumbSequence->toPageSequence()) { - for (int i = 0; i < m_ptrStages->count(); i++) { - m_ptrStages->filterAt(i)->loadDefaultSettings(pageInfo); - } - } - - updateMainArea(); + if (m_ignoreSelectionChanges) { + return; + } + + if (selected.empty()) { + return; + } + + m_interactiveQueue->cancelAndClear(); + if (m_batchQueue) { + // Should not happen, but just in case. + m_batchQueue->cancelAndClear(); + } + + const bool was_below_fix_orientation = isBelowFixOrientation(m_curFilter); + const bool was_below_select_content = isBelowSelectContent(m_curFilter); + m_curFilter = selected.front().top(); + const bool now_below_fix_orientation = isBelowFixOrientation(m_curFilter); + const bool now_below_select_content = isBelowSelectContent(m_curFilter); + + m_stages->filterAt(m_curFilter)->selected(); + + updateSortOptions(); + + // Propagate context boxes down the stage list, if necessary. + if (!was_below_select_content && now_below_select_content) { + // IMPORTANT: this needs to go before resetting thumbnails, + // because it may affect them. + if (m_contentBoxPropagator) { + m_contentBoxPropagator->propagate(*m_pages); + } // Otherwise probably no project is loaded. + } + // Propagate page orientations (that might have changed) to the "Split Pages" stage. + if (!was_below_fix_orientation && now_below_fix_orientation) { + // IMPORTANT: this needs to go before resetting thumbnails, + // because it may affect them. + if (m_pageOrientationPropagator) { + m_pageOrientationPropagator->propagate(*m_pages); + } // Otherwise probably no project is loaded. + } + + const int hor_scroll_bar_pos = thumbView->horizontalScrollBar()->value(); + const int ver_scroll_bar_pos = thumbView->verticalScrollBar()->value(); + + resetThumbSequence(currentPageOrderProvider(), ThumbnailSequence::KEEP_SELECTION); + + if (!focusButton->isChecked()) { + thumbView->horizontalScrollBar()->setValue(hor_scroll_bar_pos); + thumbView->verticalScrollBar()->setValue(ver_scroll_bar_pos); + } + + // load default settings for all the pages + for (const PageInfo& pageInfo : m_thumbSequence->toPageSequence()) { + for (int i = 0; i < m_stages->count(); i++) { + m_stages->filterAt(i)->loadDefaultSettings(pageInfo); + } + } + + updateMainArea(); } // MainWindow::filterSelectionChanged void MainWindow::switchFilter1() { - filterList->selectRow(0); + filterList->selectRow(0); } void MainWindow::switchFilter2() { - filterList->selectRow(1); + filterList->selectRow(1); } void MainWindow::switchFilter3() { - filterList->selectRow(2); + filterList->selectRow(2); } void MainWindow::switchFilter4() { - filterList->selectRow(3); + filterList->selectRow(3); } void MainWindow::switchFilter5() { - filterList->selectRow(4); + filterList->selectRow(4); } void MainWindow::switchFilter6() { - filterList->selectRow(5); + filterList->selectRow(5); } void MainWindow::pageOrderingChanged(int idx) { - if (m_ignorePageOrderingChanges) { - return; - } + if (m_ignorePageOrderingChanges) { + return; + } - const int hor_scroll_bar_pos = thumbView->horizontalScrollBar()->value(); - const int ver_scroll_bar_pos = thumbView->verticalScrollBar()->value(); + const int hor_scroll_bar_pos = thumbView->horizontalScrollBar()->value(); + const int ver_scroll_bar_pos = thumbView->verticalScrollBar()->value(); - m_ptrStages->filterAt(m_curFilter)->selectPageOrder(idx); + m_stages->filterAt(m_curFilter)->selectPageOrder(idx); - m_ptrThumbSequence->reset(m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, - currentPageOrderProvider()); + m_thumbSequence->reset(m_pages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, + currentPageOrderProvider()); - if (!focusButton->isChecked()) { - thumbView->horizontalScrollBar()->setValue(hor_scroll_bar_pos); - thumbView->verticalScrollBar()->setValue(ver_scroll_bar_pos); - } + if (!focusButton->isChecked()) { + thumbView->horizontalScrollBar()->setValue(hor_scroll_bar_pos); + thumbView->verticalScrollBar()->setValue(ver_scroll_bar_pos); + } } void MainWindow::reloadRequested() { - // Start loading / processing the current page. - updateMainArea(); + // Start loading / processing the current page. + updateMainArea(); } void MainWindow::startBatchProcessing() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } - - m_ptrInteractiveQueue->cancelAndClear(); - - m_ptrBatchQueue.reset(new ProcessingTaskQueue); - PageInfo page(m_ptrThumbSequence->selectionLeader()); - for (; !page.isNull(); page = m_ptrThumbSequence->nextPage(page.id())) { - for (int i = 0; i < m_ptrStages->count(); i++) { - m_ptrStages->filterAt(i)->loadDefaultSettings(page); - } - m_ptrBatchQueue->addProcessingTask(page, createCompositeTask(page, m_curFilter, /*batch=*/true, m_debug)); - } - - focusButton->setChecked(true); - - removeFilterOptionsWidget(); - filterList->setBatchProcessingInProgress(true); - filterList->setEnabled(false); - - BackgroundTaskPtr task(m_ptrBatchQueue->takeForProcessing()); - if (task) { - do { - m_ptrWorkerThreadPool->submitTask(task); - if (!m_ptrWorkerThreadPool->hasSpareCapacity()) { - break; - } - } while ((task = m_ptrBatchQueue->takeForProcessing())); - } else { - stopBatchProcessing(); - } - - page = m_ptrBatchQueue->selectedPage(); - if (!page.isNull()) { - m_ptrThumbSequence->setSelection(page.id()); - } - // Display the batch processing screen. - updateMainArea(); + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } + + m_interactiveQueue->cancelAndClear(); + + m_batchQueue.reset(new ProcessingTaskQueue); + PageInfo page(m_thumbSequence->selectionLeader()); + for (; !page.isNull(); page = m_thumbSequence->nextPage(page.id())) { + for (int i = 0; i < m_stages->count(); i++) { + m_stages->filterAt(i)->loadDefaultSettings(page); + } + m_batchQueue->addProcessingTask(page, createCompositeTask(page, m_curFilter, /*batch=*/true, m_debug)); + } + + focusButton->setChecked(true); + + removeFilterOptionsWidget(); + filterList->setBatchProcessingInProgress(true); + filterList->setEnabled(false); + + BackgroundTaskPtr task(m_batchQueue->takeForProcessing()); + if (task) { + do { + m_workerThreadPool->submitTask(task); + if (!m_workerThreadPool->hasSpareCapacity()) { + break; + } + } while ((task = m_batchQueue->takeForProcessing())); + } else { + stopBatchProcessing(); + } + + page = m_batchQueue->selectedPage(); + if (!page.isNull()) { + m_thumbSequence->setSelection(page.id()); + } + // Display the batch processing screen. + updateMainArea(); } // MainWindow::startBatchProcessing void MainWindow::stopBatchProcessing(MainAreaAction main_area) { - if (!isBatchProcessingInProgress()) { - return; - } + if (!isBatchProcessingInProgress()) { + return; + } - const PageInfo page(m_ptrBatchQueue->selectedPage()); - if (!page.isNull()) { - m_ptrThumbSequence->setSelection(page.id()); - } + const PageInfo page(m_batchQueue->selectedPage()); + if (!page.isNull()) { + m_thumbSequence->setSelection(page.id()); + } - m_ptrBatchQueue->cancelAndClear(); - m_ptrBatchQueue.reset(); + m_batchQueue->cancelAndClear(); + m_batchQueue.reset(); - filterList->setBatchProcessingInProgress(false); - filterList->setEnabled(true); + filterList->setBatchProcessingInProgress(false); + filterList->setEnabled(true); - switch (main_area) { - case UPDATE_MAIN_AREA: - updateMainArea(); - break; - case CLEAR_MAIN_AREA: - removeImageWidget(); - break; - } + switch (main_area) { + case UPDATE_MAIN_AREA: + updateMainArea(); + break; + case CLEAR_MAIN_AREA: + removeImageWidget(); + break; + } - resetThumbSequence(currentPageOrderProvider()); + resetThumbSequence(currentPageOrderProvider()); } void MainWindow::filterResult(const BackgroundTaskPtr& task, const FilterResultPtr& result) { - // Cancelled or not, we must mark it as finished. - m_ptrInteractiveQueue->processingFinished(task); - if (m_ptrBatchQueue) { - m_ptrBatchQueue->processingFinished(task); - } - - if (task->isCancelled()) { - return; - } - - if (!isBatchProcessingInProgress()) { - if (!result->filter()) { - // Error loading file. No special action is necessary. - } else if (result->filter() != m_ptrStages->filterAt(m_curFilter)) { - // Error from one of the previous filters. - const int idx = m_ptrStages->findFilter(result->filter()); - assert(idx >= 0); - m_curFilter = idx; - - ScopedIncDec selection_guard(m_ignoreSelectionChanges); - filterList->selectRow(idx); - } - } - - // This needs to be done even if batch processing is taking place, - // for instance because thumbnail invalidation is done from here. - result->updateUI(this); - - if (isBatchProcessingInProgress()) { - if (m_ptrBatchQueue->allProcessed()) { - stopBatchProcessing(); - - QApplication::alert(this); // Flash the taskbar entry. - if (m_checkBeepWhenFinished()) { + // Cancelled or not, we must mark it as finished. + m_interactiveQueue->processingFinished(task); + if (m_batchQueue) { + m_batchQueue->processingFinished(task); + } + + if (task->isCancelled()) { + return; + } + + if (!isBatchProcessingInProgress()) { + if (!result->filter()) { + // Error loading file. No special action is necessary. + } else if (result->filter() != m_stages->filterAt(m_curFilter)) { + // Error from one of the previous filters. + const int idx = m_stages->findFilter(result->filter()); + assert(idx >= 0); + m_curFilter = idx; + + ScopedIncDec selection_guard(m_ignoreSelectionChanges); + filterList->selectRow(idx); + } + } + + // This needs to be done even if batch processing is taking place, + // for instance because thumbnail invalidation is done from here. + result->updateUI(this); + + if (isBatchProcessingInProgress()) { + if (m_batchQueue->allProcessed()) { + stopBatchProcessing(); + + QApplication::alert(this); // Flash the taskbar entry. + if (m_checkBeepWhenFinished()) { #if defined(Q_OS_UNIX) - QString ext_play_cmd("play /usr/share/sounds/freedesktop/stereo/bell.oga"); + QString ext_play_cmd("play /usr/share/sounds/freedesktop/stereo/bell.oga"); #else - QString ext_play_cmd; + QString ext_play_cmd; #endif - QSettings settings; - QString cmd = settings.value("main_window/external_alarm_cmd", ext_play_cmd).toString(); - if (cmd.isEmpty()) { - QApplication::beep(); - } else { - Q_UNUSED(std::system(cmd.toStdString().c_str())); - } - } - - if (m_selectedPage.get(getCurrentView()) == m_ptrThumbSequence->lastPage().id()) { - // If batch processing finished at the last page, jump to the first one. - goFirstPage(); - } - - return; + QSettings settings; + QString cmd = settings.value("main_window/external_alarm_cmd", ext_play_cmd).toString(); + if (cmd.isEmpty()) { + QApplication::beep(); + } else { + Q_UNUSED(std::system(cmd.toStdString().c_str())); } + } - do { - const BackgroundTaskPtr task(m_ptrBatchQueue->takeForProcessing()); - if (!task) { - break; - } - m_ptrWorkerThreadPool->submitTask(task); - } while (m_ptrWorkerThreadPool->hasSpareCapacity()); - - const PageInfo page(m_ptrBatchQueue->selectedPage()); - if (!page.isNull()) { - m_ptrThumbSequence->setSelection(page.id()); - } + if (m_selectedPage.get(getCurrentView()) == m_thumbSequence->lastPage().id()) { + // If batch processing finished at the last page, jump to the first one. + goFirstPage(); + } + + return; } + + do { + const BackgroundTaskPtr task(m_batchQueue->takeForProcessing()); + if (!task) { + break; + } + m_workerThreadPool->submitTask(task); + } while (m_workerThreadPool->hasSpareCapacity()); + + const PageInfo page(m_batchQueue->selectedPage()); + if (!page.isNull()) { + m_thumbSequence->setSelection(page.id()); + } + } } // MainWindow::filterResult void MainWindow::debugToggled(const bool enabled) { - m_debug = enabled; + m_debug = enabled; } void MainWindow::fixDpiDialogRequested() { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } - assert(!m_ptrFixDpiDialog); - m_ptrFixDpiDialog = new FixDpiDialog(m_ptrPages->toImageFileInfo(), this); - m_ptrFixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); - m_ptrFixDpiDialog->setWindowModality(Qt::WindowModal); + assert(!m_fixDpiDialog); + m_fixDpiDialog = new FixDpiDialog(m_pages->toImageFileInfo(), this); + m_fixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); + m_fixDpiDialog->setWindowModality(Qt::WindowModal); - connect(m_ptrFixDpiDialog, SIGNAL(accepted()), SLOT(fixedDpiSubmitted())); + connect(m_fixDpiDialog, SIGNAL(accepted()), SLOT(fixedDpiSubmitted())); - m_ptrFixDpiDialog->show(); + m_fixDpiDialog->show(); } void MainWindow::fixedDpiSubmitted() { - assert(m_ptrFixDpiDialog); - assert(m_ptrPages); - assert(m_ptrThumbSequence); + assert(m_fixDpiDialog); + assert(m_pages); + assert(m_thumbSequence); - const PageInfo selected_page_before(m_ptrThumbSequence->selectionLeader()); + const PageInfo selected_page_before(m_thumbSequence->selectionLeader()); - m_ptrPages->updateMetadataFrom(m_ptrFixDpiDialog->files()); + m_pages->updateMetadataFrom(m_fixDpiDialog->files()); - // The thumbnail list also stores page metadata, including the DPI. - m_ptrThumbSequence->reset(m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, - m_ptrThumbSequence->pageOrderProvider()); + // The thumbnail list also stores page metadata, including the DPI. + m_thumbSequence->reset(m_pages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, + m_thumbSequence->pageOrderProvider()); - const PageInfo selected_page_after(m_ptrThumbSequence->selectionLeader()); + const PageInfo selected_page_after(m_thumbSequence->selectionLeader()); - // Reload if the current page was affected. - // Note that imageId() isn't supposed to change - we check just in case. - if ((selected_page_before.imageId() != selected_page_after.imageId()) - || (selected_page_before.metadata() != selected_page_after.metadata())) { - reloadRequested(); - } + // Reload if the current page was affected. + // Note that imageId() isn't supposed to change - we check just in case. + if ((selected_page_before.imageId() != selected_page_after.imageId()) + || (selected_page_before.metadata() != selected_page_after.metadata())) { + reloadRequested(); + } } void MainWindow::saveProjectTriggered() { - if (m_projectFile.isEmpty()) { - saveProjectAsTriggered(); + if (m_projectFile.isEmpty()) { + saveProjectAsTriggered(); - return; - } + return; + } - if (saveProjectWithFeedback(m_projectFile)) { - updateWindowTitle(); - } + if (saveProjectWithFeedback(m_projectFile)) { + updateWindowTitle(); + } } void MainWindow::saveProjectAsTriggered() { - // XXX: this function is duplicated in OutOfMemoryDialog. + // XXX: this function is duplicated in OutOfMemoryDialog. - QString project_dir; - if (!m_projectFile.isEmpty()) { - project_dir = QFileInfo(m_projectFile).absolutePath(); - } else { - QSettings settings; - project_dir = settings.value("project/lastDir").toString(); - } + QString project_dir; + if (!m_projectFile.isEmpty()) { + project_dir = QFileInfo(m_projectFile).absolutePath(); + } else { + QSettings settings; + project_dir = settings.value("project/lastDir").toString(); + } - QString project_file( - QFileDialog::getSaveFileName(this, QString(), project_dir, tr("Scan Tailor Projects") + " (*.ScanTailor)")); - if (project_file.isEmpty()) { - return; - } + QString project_file( + QFileDialog::getSaveFileName(this, QString(), project_dir, tr("Scan Tailor Projects") + " (*.ScanTailor)")); + if (project_file.isEmpty()) { + return; + } - if (!project_file.endsWith(".ScanTailor", Qt::CaseInsensitive)) { - project_file += ".ScanTailor"; - } + if (!project_file.endsWith(".ScanTailor", Qt::CaseInsensitive)) { + project_file += ".ScanTailor"; + } - if (saveProjectWithFeedback(project_file)) { - m_projectFile = project_file; - updateWindowTitle(); + if (saveProjectWithFeedback(project_file)) { + m_projectFile = project_file; + updateWindowTitle(); - QSettings settings; - settings.setValue("project/lastDir", QFileInfo(m_projectFile).absolutePath()); + QSettings settings; + settings.setValue("project/lastDir", QFileInfo(m_projectFile).absolutePath()); - RecentProjects rp; - rp.read(); - rp.setMostRecent(m_projectFile); - rp.write(); - } + RecentProjects rp; + rp.read(); + rp.setMostRecent(m_projectFile); + rp.write(); + } } // MainWindow::saveProjectAsTriggered void MainWindow::newProject() { - if (!closeProjectInteractive()) { - return; - } + if (!closeProjectInteractive()) { + return; + } - // It will delete itself when it's done. - ProjectCreationContext* context = new ProjectCreationContext(this); - connect(context, SIGNAL(done(ProjectCreationContext*)), this, SLOT(newProjectCreated(ProjectCreationContext*))); + // It will delete itself when it's done. + ProjectCreationContext* context = new ProjectCreationContext(this); + connect(context, SIGNAL(done(ProjectCreationContext*)), this, SLOT(newProjectCreated(ProjectCreationContext*))); } void MainWindow::newProjectCreated(ProjectCreationContext* context) { - auto pages = make_intrusive(context->files(), ProjectPages::AUTO_PAGES, context->layoutDirection()); - switchToNewProject(pages, context->outDir()); + auto pages = make_intrusive(context->files(), ProjectPages::AUTO_PAGES, context->layoutDirection()); + switchToNewProject(pages, context->outDir()); } void MainWindow::openProject() { - if (!closeProjectInteractive()) { - return; - } + if (!closeProjectInteractive()) { + return; + } - QSettings settings; - const QString project_dir(settings.value("project/lastDir").toString()); + QSettings settings; + const QString project_dir(settings.value("project/lastDir").toString()); - const QString project_file(QFileDialog::getOpenFileName(this, tr("Open Project"), project_dir, - tr("Scan Tailor Projects") + " (*.ScanTailor)")); - if (project_file.isEmpty()) { - // Cancelled by user. - return; - } + const QString project_file(QFileDialog::getOpenFileName(this, tr("Open Project"), project_dir, + tr("Scan Tailor Projects") + " (*.ScanTailor)")); + if (project_file.isEmpty()) { + // Cancelled by user. + return; + } - openProject(project_file); + openProject(project_file); } void MainWindow::openProject(const QString& project_file) { - QFile file(project_file); - if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Unable to open the project file.")); + QFile file(project_file); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, tr("Error"), tr("Unable to open the project file.")); - return; - } + return; + } - QDomDocument doc; - if (!doc.setContent(&file)) { - QMessageBox::warning(this, tr("Error"), tr("The project file is broken.")); + QDomDocument doc; + if (!doc.setContent(&file)) { + QMessageBox::warning(this, tr("Error"), tr("The project file is broken.")); - return; - } + return; + } - file.close(); + file.close(); - ProjectOpeningContext* context = new ProjectOpeningContext(this, project_file, doc); - connect(context, SIGNAL(done(ProjectOpeningContext*)), SLOT(projectOpened(ProjectOpeningContext*))); - context->proceed(); + ProjectOpeningContext* context = new ProjectOpeningContext(this, project_file, doc); + connect(context, SIGNAL(done(ProjectOpeningContext*)), SLOT(projectOpened(ProjectOpeningContext*))); + context->proceed(); } void MainWindow::projectOpened(ProjectOpeningContext* context) { - RecentProjects rp; - rp.read(); - rp.setMostRecent(context->projectFile()); - rp.write(); + RecentProjects rp; + rp.read(); + rp.setMostRecent(context->projectFile()); + rp.write(); - QSettings settings; - settings.setValue("project/lastDir", QFileInfo(context->projectFile()).absolutePath()); + QSettings settings; + settings.setValue("project/lastDir", QFileInfo(context->projectFile()).absolutePath()); - switchToNewProject(context->projectReader()->pages(), context->projectReader()->outputDirectory(), - context->projectFile(), context->projectReader()); + switchToNewProject(context->projectReader()->pages(), context->projectReader()->outputDirectory(), + context->projectFile(), context->projectReader()); } void MainWindow::closeProject() { - closeProjectInteractive(); + closeProjectInteractive(); } void MainWindow::openSettingsDialog() { - SettingsDialog* dialog = new SettingsDialog(this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowModality(Qt::WindowModal); - connect(dialog, SIGNAL(settingsChanged()), this, SLOT(onSettingsChanged())); - dialog->show(); + SettingsDialog* dialog = new SettingsDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowModality(Qt::WindowModal); + connect(dialog, SIGNAL(settingsChanged()), this, SLOT(onSettingsChanged())); + dialog->show(); } void MainWindow::openDefaultParamsDialog() { - DefaultParamsDialog* dialog = new DefaultParamsDialog(this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowModality(Qt::WindowModal); - dialog->show(); + DefaultParamsDialog* dialog = new DefaultParamsDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowModality(Qt::WindowModal); + dialog->show(); } void MainWindow::onSettingsChanged() { - QSettings settings; + QSettings settings; - m_autoSaveProject = settings.value("settings/auto_save_project").toBool(); + m_autoSaveProject = settings.value("settings/auto_save_project").toBool(); - if (auto* app = dynamic_cast(qApp)) { - app->installLanguage(settings.value("settings/language").toString()); - } + if (auto* app = dynamic_cast(qApp)) { + app->installLanguage(settings.value("settings/language").toString()); + } - m_ptrThumbSequence->invalidateAllThumbnails(); + const QSizeF max_logical_thumb_size = settings.value("settings/max_logical_thumb_size").toSizeF(); + if (m_maxLogicalThumbSize != max_logical_thumb_size) { + updateMaxLogicalThumbSize(max_logical_thumb_size); + } + + m_thumbSequence->invalidateAllThumbnails(); } void MainWindow::showAboutDialog() { - Ui::AboutDialog ui; - QDialog* dialog = new QDialog(this); - ui.setupUi(dialog); - ui.version->setText(QString(tr("version ")) + QString::fromUtf8(VERSION)); + Ui::AboutDialog ui; + QDialog* dialog = new QDialog(this); + ui.setupUi(dialog); + ui.version->setText(QString(tr("version ")) + QString::fromUtf8(VERSION)); - QResource license(":/GPLv3.html"); - ui.licenseViewer->setHtml(QString::fromUtf8((const char*) license.data(), static_cast(license.size()))); + QResource license(":/GPLv3.html"); + ui.licenseViewer->setHtml(QString::fromUtf8((const char*) license.data(), static_cast(license.size()))); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowModality(Qt::WindowModal); - dialog->show(); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowModality(Qt::WindowModal); + dialog->show(); } /** * This function is called asynchronously, always from the main thread. */ void MainWindow::handleOutOfMemorySituation() { - deleteLater(); + deleteLater(); - m_ptrOutOfMemoryDialog->setParams(m_projectFile, m_ptrStages, m_ptrPages, m_selectedPage, m_outFileNameGen); + m_outOfMemoryDialog->setParams(m_projectFile, m_stages, m_pages, m_selectedPage, m_outFileNameGen); - closeProjectWithoutSaving(); + closeProjectWithoutSaving(); - m_ptrOutOfMemoryDialog->setAttribute(Qt::WA_DeleteOnClose); - m_ptrOutOfMemoryDialog.release()->show(); + m_outOfMemoryDialog->setAttribute(Qt::WA_DeleteOnClose); + m_outOfMemoryDialog.release()->show(); } /** * Note: the removed widgets are not deleted. */ void MainWindow::removeWidgetsFromLayout(QLayout* layout) { - QLayoutItem* child; - while ((child = layout->takeAt(0))) { - delete child; - } + QLayoutItem* child; + while ((child = layout->takeAt(0))) { + delete child; + } } void MainWindow::removeFilterOptionsWidget() { - removeWidgetsFromLayout(m_pOptionsFrameLayout); - // Delete the old widget we were owning, if any. - m_optionsWidgetCleanup.clear(); + removeWidgetsFromLayout(m_optionsFrameLayout); + // Delete the old widget we were owning, if any. + m_optionsWidgetCleanup.clear(); - m_ptrOptionsWidget = 0; + m_optionsWidget = nullptr; } void MainWindow::updateProjectActions() { - const bool loaded = isProjectLoaded(); - actionSaveProject->setEnabled(loaded); - actionSaveProjectAs->setEnabled(loaded); - actionFixDpi->setEnabled(loaded); - actionRelinking->setEnabled(loaded); + const bool loaded = isProjectLoaded(); + actionSaveProject->setEnabled(loaded); + actionSaveProjectAs->setEnabled(loaded); + actionFixDpi->setEnabled(loaded); + actionRelinking->setEnabled(loaded); } bool MainWindow::isBatchProcessingInProgress() const { - return m_ptrBatchQueue.get() != 0; + return m_batchQueue.get() != 0; } bool MainWindow::isProjectLoaded() const { - return !m_outFileNameGen.outDir().isEmpty(); + return !m_outFileNameGen.outDir().isEmpty(); } bool MainWindow::isBelowSelectContent() const { - return isBelowSelectContent(m_curFilter); + return isBelowSelectContent(m_curFilter); } bool MainWindow::isBelowSelectContent(const int filter_idx) const { - return filter_idx > m_ptrStages->selectContentFilterIdx(); + return filter_idx > m_stages->selectContentFilterIdx(); } bool MainWindow::isBelowFixOrientation(int filter_idx) const { - return filter_idx > m_ptrStages->fixOrientationFilterIdx(); + return filter_idx > m_stages->fixOrientationFilterIdx(); } bool MainWindow::isOutputFilter() const { - return isOutputFilter(m_curFilter); + return isOutputFilter(m_curFilter); } bool MainWindow::isOutputFilter(const int filter_idx) const { - return filter_idx == m_ptrStages->outputFilterIdx(); + return filter_idx == m_stages->outputFilterIdx(); } PageView MainWindow::getCurrentView() const { - return m_ptrStages->filterAt(m_curFilter)->getView(); + return m_stages->filterAt(m_curFilter)->getView(); } void MainWindow::updateMainArea() { - if (m_ptrPages->numImages() == 0) { - filterList->setBatchProcessingPossible(false); - setDockWidgetsVisible(false); - showNewOpenProjectPanel(); - m_statusBarPanel->clear(); - } else if (isBatchProcessingInProgress()) { - filterList->setBatchProcessingPossible(false); - setImageWidget(m_ptrBatchProcessingWidget.get(), KEEP_OWNERSHIP); + if (m_pages->numImages() == 0) { + filterList->setBatchProcessingPossible(false); + setDockWidgetsVisible(false); + showNewOpenProjectPanel(); + m_statusBarPanel->clear(); + } else if (isBatchProcessingInProgress()) { + filterList->setBatchProcessingPossible(false); + setImageWidget(m_batchProcessingWidget.get(), KEEP_OWNERSHIP); + } else { + setDockWidgetsVisible(true); + const PageInfo page(m_thumbSequence->selectionLeader()); + if (page.isNull()) { + filterList->setBatchProcessingPossible(false); + removeImageWidget(); + removeFilterOptionsWidget(); } else { - setDockWidgetsVisible(true); - const PageInfo page(m_ptrThumbSequence->selectionLeader()); - if (page.isNull()) { - filterList->setBatchProcessingPossible(false); - removeImageWidget(); - removeFilterOptionsWidget(); - } else { - // Note that loadPageInteractive may reset it to false. - filterList->setBatchProcessingPossible(true); - PageSequence pageSequence = m_ptrThumbSequence->toPageSequence(); - if (pageSequence.numPages() > 0) { - m_statusBarPanel->updatePage(pageSequence.pageNo(page.id()) + 1, pageSequence.numPages(), page.id()); - } - loadPageInteractive(page); - } + // Note that loadPageInteractive may reset it to false. + filterList->setBatchProcessingPossible(true); + PageSequence pageSequence = m_thumbSequence->toPageSequence(); + if (pageSequence.numPages() > 0) { + m_statusBarPanel->updatePage(pageSequence.pageNo(page.id()) + 1, pageSequence.numPages(), page.id()); + } + loadPageInteractive(page); } + } } bool MainWindow::checkReadyForOutput(const PageId* ignore) const { - return m_ptrStages->pageLayoutFilter()->checkReadyForOutput(*m_ptrPages, ignore); + return m_stages->pageLayoutFilter()->checkReadyForOutput(*m_pages, ignore); } void MainWindow::loadPageInteractive(const PageInfo& page) { - assert(!isBatchProcessingInProgress()); + assert(!isBatchProcessingInProgress()); - m_ptrInteractiveQueue->cancelAndClear(); + m_interactiveQueue->cancelAndClear(); - if (isOutputFilter() && !checkReadyForOutput(&page.id())) { - filterList->setBatchProcessingPossible(false); + if (isOutputFilter() && !checkReadyForOutput(&page.id())) { + filterList->setBatchProcessingPossible(false); - const QString err_text(tr("Output is not yet possible, as the final size" - " of pages is not yet known.\nTo determine it," - " run batch processing at \"Select Content\" or" - " \"Margins\".")); + const QString err_text( + tr("Output is not yet possible, as the final size" + " of pages is not yet known.\nTo determine it," + " run batch processing at \"Select Content\" or" + " \"Margins\".")); - removeFilterOptionsWidget(); - setImageWidget(new ErrorWidget(err_text), TRANSFER_OWNERSHIP); + removeFilterOptionsWidget(); + setImageWidget(new ErrorWidget(err_text), TRANSFER_OWNERSHIP); - return; - } + return; + } - for (int i = 0; i < m_ptrStages->count(); i++) { - m_ptrStages->filterAt(i)->loadDefaultSettings(page); - } + for (int i = 0; i < m_stages->count(); i++) { + m_stages->filterAt(i)->loadDefaultSettings(page); + } - if (!isBatchProcessingInProgress()) { - if (m_pImageFrameLayout->indexOf(m_ptrProcessingIndicationWidget.get()) != -1) { - m_ptrProcessingIndicationWidget->processingRestartedEffect(); - } - setImageWidget(m_ptrProcessingIndicationWidget.get(), KEEP_OWNERSHIP, 0, false); - m_ptrStages->filterAt(m_curFilter)->preUpdateUI(this, page); + if (!isBatchProcessingInProgress()) { + if (m_imageFrameLayout->indexOf(m_processingIndicationWidget.get()) != -1) { + m_processingIndicationWidget->processingRestartedEffect(); } + bool current_widget_is_image = (Utils::castOrFindChild(m_imageFrameLayout->widget(0)) != nullptr); + setImageWidget(m_processingIndicationWidget.get(), KEEP_OWNERSHIP, nullptr, current_widget_is_image); + m_stages->filterAt(m_curFilter)->preUpdateUI(this, page); + } - assert(m_ptrThumbnailCache); + assert(m_thumbnailCache); - m_ptrInteractiveQueue->cancelAndClear(); - m_ptrInteractiveQueue->addProcessingTask(page, createCompositeTask(page, m_curFilter, /*batch=*/false, m_debug)); - m_ptrWorkerThreadPool->submitTask(m_ptrInteractiveQueue->takeForProcessing()); + m_interactiveQueue->cancelAndClear(); + m_interactiveQueue->addProcessingTask(page, createCompositeTask(page, m_curFilter, /*batch=*/false, m_debug)); + m_workerThreadPool->submitTask(m_interactiveQueue->takeForProcessing()); } // MainWindow::loadPageInteractive void MainWindow::updateWindowTitle() { - QString project_name; - CommandLine cli = CommandLine::get(); + QString project_name; + CommandLine cli = CommandLine::get(); - if (m_projectFile.isEmpty()) { - project_name = tr("Unnamed"); - } else if (cli.hasWindowTitle()) { - project_name = cli.getWindowTitle(); - } else { - project_name = QFileInfo(m_projectFile).baseName(); - } - const QString version(QString::fromUtf8(VERSION)); - setWindowTitle(tr("%2 - ScanTailor Advanced [%1bit]").arg(sizeof(void*) * 8).arg(project_name)); + if (m_projectFile.isEmpty()) { + project_name = tr("Unnamed"); + } else if (cli.hasWindowTitle()) { + project_name = cli.getWindowTitle(); + } else { + project_name = QFileInfo(m_projectFile).baseName(); + } + const QString version(QString::fromUtf8(VERSION)); + setWindowTitle(tr("%2 - ScanTailor Advanced [%1bit]").arg(sizeof(void*) * 8).arg(project_name)); } /** @@ -1560,432 +1579,452 @@ void MainWindow::updateWindowTitle() { * \return true if the project was closed, false if the user cancelled the process. */ bool MainWindow::closeProjectInteractive() { - if (!isProjectLoaded()) { - return true; - } - - if (m_projectFile.isEmpty()) { - switch (promptProjectSave()) { - case SAVE: - saveProjectTriggered(); - // fall through - case DONT_SAVE: - break; - case CANCEL: - return false; - } - closeProjectWithoutSaving(); + if (!isProjectLoaded()) { + return true; + } - return true; + if (m_projectFile.isEmpty()) { + switch (promptProjectSave()) { + case SAVE: + saveProjectTriggered(); + // fall through + case DONT_SAVE: + break; + case CANCEL: + return false; } + closeProjectWithoutSaving(); - const QFileInfo project_file(m_projectFile); - const QFileInfo backup_file(project_file.absoluteDir(), QString::fromLatin1("Backup.") + project_file.fileName()); - const QString backup_file_path(backup_file.absoluteFilePath()); - - ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen); - - if (!writer.write(backup_file_path, m_ptrStages->filters())) { - // Backup file could not be written??? - QFile::remove(backup_file_path); - switch (promptProjectSave()) { - case SAVE: - saveProjectTriggered(); - // fall through - case DONT_SAVE: - break; - case CANCEL: - return false; - } - closeProjectWithoutSaving(); - - return true; - } + return true; + } - if (compareFiles(m_projectFile, backup_file_path)) { - // The project hasn't really changed. - QFile::remove(backup_file_path); - closeProjectWithoutSaving(); + const QFileInfo project_file(m_projectFile); + const QFileInfo backup_file(project_file.absoluteDir(), QString::fromLatin1("Backup.") + project_file.fileName()); + const QString backup_file_path(backup_file.absoluteFilePath()); - return true; - } + ProjectWriter writer(m_pages, m_selectedPage, m_outFileNameGen); + if (!writer.write(backup_file_path, m_stages->filters())) { + // Backup file could not be written??? + QFile::remove(backup_file_path); switch (promptProjectSave()) { - case SAVE: - if (!Utils::overwritingRename(backup_file_path, m_projectFile)) { - QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); - - return false; - } - // fall through - case DONT_SAVE: - QFile::remove(backup_file_path); - break; - case CANCEL: - return false; + case SAVE: + saveProjectTriggered(); + // fall through + case DONT_SAVE: + break; + case CANCEL: + return false; } + closeProjectWithoutSaving(); + + return true; + } + if (compareFiles(m_projectFile, backup_file_path)) { + // The project hasn't really changed. + QFile::remove(backup_file_path); closeProjectWithoutSaving(); return true; + } + + switch (promptProjectSave()) { + case SAVE: + if (!Utils::overwritingRename(backup_file_path, m_projectFile)) { + QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); + + return false; + } + // fall through + case DONT_SAVE: + QFile::remove(backup_file_path); + break; + case CANCEL: + return false; + } + + closeProjectWithoutSaving(); + + return true; } // MainWindow::closeProjectInteractive void MainWindow::closeProjectWithoutSaving() { - auto pages = make_intrusive(); - switchToNewProject(pages, QString()); + auto pages = make_intrusive(); + switchToNewProject(pages, QString()); } bool MainWindow::saveProjectWithFeedback(const QString& project_file) { - ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen); + ProjectWriter writer(m_pages, m_selectedPage, m_outFileNameGen); - if (!writer.write(project_file, m_ptrStages->filters())) { - QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); + if (!writer.write(project_file, m_stages->filters())) { + QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); - return false; - } + return false; + } - return true; + return true; } /** * Note: showInsertFileDialog(BEFORE, ImageId()) is legal and means inserting at the end. */ void MainWindow::showInsertFileDialog(BeforeOrAfter before_or_after, const ImageId& existing) { - if (isBatchProcessingInProgress() || !isProjectLoaded()) { - return; - } - // We need to filter out files already in project. - class ProxyModel : public QSortFilterProxyModel { - public: - ProxyModel(const ProjectPages& pages) { - setDynamicSortFilter(true); - - const PageSequence sequence(pages.toPageSequence(IMAGE_VIEW)); - for (const PageInfo& page : sequence) { - m_inProjectFiles.push_back(QFileInfo(page.imageId().filePath())); - } - } - - protected: - virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { - const QModelIndex idx(source_parent.child(source_row, 0)); - const QVariant data(idx.data(QFileSystemModel::FilePathRole)); - if (data.isNull()) { - return true; - } + if (isBatchProcessingInProgress() || !isProjectLoaded()) { + return; + } + // We need to filter out files already in project. + class ProxyModel : public QSortFilterProxyModel { + public: + ProxyModel(const ProjectPages& pages) { + setDynamicSortFilter(true); + + const PageSequence sequence(pages.toPageSequence(IMAGE_VIEW)); + for (const PageInfo& page : sequence) { + m_inProjectFiles.push_back(QFileInfo(page.imageId().filePath())); + } + } + + protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { + const QModelIndex idx(source_parent.child(source_row, 0)); + const QVariant data(idx.data(QFileSystemModel::FilePathRole)); + if (data.isNull()) { + return true; + } - return !m_inProjectFiles.contains(QFileInfo(data.toString())); - } + return !m_inProjectFiles.contains(QFileInfo(data.toString())); + } - virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const { - return left.row() < right.row(); - } + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const { return left.row() < right.row(); } - private: - QFileInfoList m_inProjectFiles; - }; + private: + QFileInfoList m_inProjectFiles; + }; - std::unique_ptr dialog( - new QFileDialog(this, tr("Files to insert"), QFileInfo(existing.filePath()).absolutePath())); - dialog->setFileMode(QFileDialog::ExistingFiles); - dialog->setProxyModel(new ProxyModel(*m_ptrPages)); - dialog->setNameFilter(tr("Images not in project (%1)").arg("*.png *.tiff *.tif *.jpeg *.jpg")); - // XXX: Adding individual pages from a multi-page TIFF where - // some of the pages are already in project is not supported right now. - if (dialog->exec() != QDialog::Accepted) { - return; - } + std::unique_ptr dialog( + new QFileDialog(this, tr("Files to insert"), QFileInfo(existing.filePath()).absolutePath())); + dialog->setFileMode(QFileDialog::ExistingFiles); + dialog->setProxyModel(new ProxyModel(*m_pages)); + dialog->setNameFilter(tr("Images not in project (%1)").arg("*.png *.tiff *.tif *.jpeg *.jpg")); + // XXX: Adding individual pages from a multi-page TIFF where + // some of the pages are already in project is not supported right now. + if (dialog->exec() != QDialog::Accepted) { + return; + } - QStringList files(dialog->selectedFiles()); - if (files.empty()) { - return; - } + QStringList files(dialog->selectedFiles()); + if (files.empty()) { + return; + } - // The order of items returned by QFileDialog is platform-dependent, - // so we enforce our own ordering. - std::sort(files.begin(), files.end(), SmartFilenameOrdering()); + // The order of items returned by QFileDialog is platform-dependent, + // so we enforce our own ordering. + std::sort(files.begin(), files.end(), SmartFilenameOrdering()); - // I suspect on some platforms it may be possible to select the same file twice, - // so to be safe, remove duplicates. - files.erase(std::unique(files.begin(), files.end()), files.end()); + // I suspect on some platforms it may be possible to select the same file twice, + // so to be safe, remove duplicates. + files.erase(std::unique(files.begin(), files.end()), files.end()); - std::vector new_files; - std::vector loaded_files; - std::vector failed_files; // Those we failed to read metadata from. - // dialog->selectedFiles() returns file list in reverse order. - for (int i = files.size() - 1; i >= 0; --i) { - const QFileInfo file_info(files[i]); - ImageFileInfo image_file_info(file_info, std::vector()); + std::vector new_files; + std::vector loaded_files; + std::vector failed_files; // Those we failed to read metadata from. + // dialog->selectedFiles() returns file list in reverse order. + for (int i = files.size() - 1; i >= 0; --i) { + const QFileInfo file_info(files[i]); + ImageFileInfo image_file_info(file_info, std::vector()); - const ImageMetadataLoader::Status status = ImageMetadataLoader::load( - files.at(i), [&](const ImageMetadata& metadata) { image_file_info.imageInfo().push_back(metadata); }); + const ImageMetadataLoader::Status status = ImageMetadataLoader::load( + files.at(i), [&](const ImageMetadata& metadata) { image_file_info.imageInfo().push_back(metadata); }); - if (status == ImageMetadataLoader::LOADED) { - new_files.push_back(image_file_info); - loaded_files.push_back(file_info.absoluteFilePath()); - } else { - failed_files.push_back(file_info.absoluteFilePath()); - } + if (status == ImageMetadataLoader::LOADED) { + new_files.push_back(image_file_info); + loaded_files.push_back(file_info.absoluteFilePath()); + } else { + failed_files.push_back(file_info.absoluteFilePath()); } + } - if (!failed_files.empty()) { - std::unique_ptr err_dialog(new LoadFilesStatusDialog(this)); - err_dialog->setLoadedFiles(loaded_files); - err_dialog->setFailedFiles(failed_files); - err_dialog->setOkButtonName(QString(" %1 ").arg(tr("Skip failed files"))); - if ((err_dialog->exec() != QDialog::Accepted) || loaded_files.empty()) { - return; - } + if (!failed_files.empty()) { + std::unique_ptr err_dialog(new LoadFilesStatusDialog(this)); + err_dialog->setLoadedFiles(loaded_files); + err_dialog->setFailedFiles(failed_files); + err_dialog->setOkButtonName(QString(" %1 ").arg(tr("Skip failed files"))); + if ((err_dialog->exec() != QDialog::Accepted) || loaded_files.empty()) { + return; } + } - // Check if there is at least one DPI that's not OK. - if (std::find_if(new_files.begin(), new_files.end(), [&](ImageFileInfo p) -> bool { return !p.isDpiOK(); }) - != new_files.end()) { - std::unique_ptr dpi_dialog(new FixDpiDialog(new_files, this)); - dpi_dialog->setWindowModality(Qt::WindowModal); - if (dpi_dialog->exec() != QDialog::Accepted) { - return; - } - - new_files = dpi_dialog->files(); + // Check if there is at least one DPI that's not OK. + if (std::find_if(new_files.begin(), new_files.end(), [&](ImageFileInfo p) -> bool { return !p.isDpiOK(); }) + != new_files.end()) { + std::unique_ptr dpi_dialog(new FixDpiDialog(new_files, this)); + dpi_dialog->setWindowModality(Qt::WindowModal); + if (dpi_dialog->exec() != QDialog::Accepted) { + return; } - // Actually insert the new pages. - for (const ImageFileInfo& file : new_files) { - int image_num = -1; // Zero-based image number in a multi-page TIFF. - for (const ImageMetadata& metadata : file.imageInfo()) { - ++image_num; + new_files = dpi_dialog->files(); + } - const int num_sub_pages = ProjectPages::adviseNumberOfLogicalPages(metadata, OrthogonalRotation()); - const ImageInfo image_info(ImageId(file.fileInfo(), image_num), metadata, num_sub_pages, false, false); - insertImage(image_info, before_or_after, existing); - } + // Actually insert the new pages. + for (const ImageFileInfo& file : new_files) { + int image_num = -1; // Zero-based image number in a multi-page TIFF. + for (const ImageMetadata& metadata : file.imageInfo()) { + ++image_num; + + const int num_sub_pages = ProjectPages::adviseNumberOfLogicalPages(metadata, OrthogonalRotation()); + const ImageInfo image_info(ImageId(file.fileInfo(), image_num), metadata, num_sub_pages, false, false); + insertImage(image_info, before_or_after, existing); } + } } // MainWindow::showInsertFileDialog void MainWindow::showRemovePagesDialog(const std::set& pages) { - std::unique_ptr dialog(new QDialog(this)); - Ui::RemovePagesDialog ui; - ui.setupUi(dialog.get()); - ui.icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(48, 48)); + std::unique_ptr dialog(new QDialog(this)); + Ui::RemovePagesDialog ui; + ui.setupUi(dialog.get()); + ui.icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(48, 48)); - ui.text->setText(ui.text->text().arg(pages.size())); + ui.text->setText(ui.text->text().arg(pages.size())); - QPushButton* remove_btn = ui.buttonBox->button(QDialogButtonBox::Ok); - remove_btn->setText(tr("Remove")); + QPushButton* remove_btn = ui.buttonBox->button(QDialogButtonBox::Ok); + remove_btn->setText(tr("Remove")); - dialog->setWindowModality(Qt::WindowModal); - if (dialog->exec() == QDialog::Accepted) { - removeFromProject(pages); - eraseOutputFiles(pages); - } + dialog->setWindowModality(Qt::WindowModal); + if (dialog->exec() == QDialog::Accepted) { + removeFromProject(pages); + eraseOutputFiles(pages); + } } /** * Note: insertImage(..., BEFORE, ImageId()) is legal and means inserting at the end. */ void MainWindow::insertImage(const ImageInfo& new_image, BeforeOrAfter before_or_after, ImageId existing) { - std::vector pages(m_ptrPages->insertImage(new_image, before_or_after, existing, getCurrentView())); + std::vector pages(m_pages->insertImage(new_image, before_or_after, existing, getCurrentView())); - if (before_or_after == BEFORE) { - // The second one will be inserted first, then the first - // one will be inserted BEFORE the second one. - std::reverse(pages.begin(), pages.end()); - } + if (before_or_after == BEFORE) { + // The second one will be inserted first, then the first + // one will be inserted BEFORE the second one. + std::reverse(pages.begin(), pages.end()); + } - for (const PageInfo& page_info : pages) { - m_outFileNameGen.disambiguator()->registerFile(page_info.imageId().filePath()); - m_ptrThumbSequence->insert(page_info, before_or_after, existing); - existing = page_info.imageId(); - } + for (const PageInfo& page_info : pages) { + m_outFileNameGen.disambiguator()->registerFile(page_info.imageId().filePath()); + m_thumbSequence->insert(page_info, before_or_after, existing); + existing = page_info.imageId(); + } } void MainWindow::removeFromProject(const std::set& pages) { - m_ptrInteractiveQueue->cancelAndRemove(pages); - if (m_ptrBatchQueue) { - m_ptrBatchQueue->cancelAndRemove(pages); - } + m_interactiveQueue->cancelAndRemove(pages); + if (m_batchQueue) { + m_batchQueue->cancelAndRemove(pages); + } - m_ptrPages->removePages(pages); - - - const PageSequence itemsInOrder = m_ptrThumbSequence->toPageSequence(); - std::set new_selection; - - bool select_first_non_deleted = false; - if (itemsInOrder.numPages() > 0) { - // if first page was deleted select first not deleted page - // otherwise select last not deleted page from beginning - select_first_non_deleted = pages.find(itemsInOrder.pageAt(0).id()) != pages.end(); - - PageId last_non_deleted; - for (const PageInfo& page : itemsInOrder) { - const PageId& id = page.id(); - const bool was_deleted = (pages.find(id) != pages.end()); - - if (!was_deleted) { - if (select_first_non_deleted) { - m_ptrThumbSequence->setSelection(id); - new_selection.insert(id); - break; - } else { - last_non_deleted = id; - } - } else if (!select_first_non_deleted && !last_non_deleted.isNull()) { - m_ptrThumbSequence->setSelection(last_non_deleted); - new_selection.insert(last_non_deleted); - break; - } - } + m_pages->removePages(pages); + + + const PageSequence itemsInOrder = m_thumbSequence->toPageSequence(); + std::set new_selection; + + bool select_first_non_deleted = false; + if (itemsInOrder.numPages() > 0) { + // if first page was deleted select first not deleted page + // otherwise select last not deleted page from beginning + select_first_non_deleted = pages.find(itemsInOrder.pageAt(0).id()) != pages.end(); - m_ptrThumbSequence->removePages(pages); + PageId last_non_deleted; + for (const PageInfo& page : itemsInOrder) { + const PageId& id = page.id(); + const bool was_deleted = (pages.find(id) != pages.end()); - if (new_selection.empty()) { - // fallback to old behaviour - if (m_ptrThumbSequence->selectionLeader().isNull()) { - m_ptrThumbSequence->setSelection(m_ptrThumbSequence->firstPage().id()); - } + if (!was_deleted) { + if (select_first_non_deleted) { + m_thumbSequence->setSelection(id); + new_selection.insert(id); + break; + } else { + last_non_deleted = id; } + } else if (!select_first_non_deleted && !last_non_deleted.isNull()) { + m_thumbSequence->setSelection(last_non_deleted); + new_selection.insert(last_non_deleted); + break; + } + } + + m_thumbSequence->removePages(pages); + + if (new_selection.empty()) { + // fallback to old behaviour + if (m_thumbSequence->selectionLeader().isNull()) { + m_thumbSequence->setSelection(m_thumbSequence->firstPage().id()); + } } + } - updateMainArea(); + updateMainArea(); } // MainWindow::removeFromProject void MainWindow::eraseOutputFiles(const std::set& pages) { - std::vector erase_variations; - erase_variations.reserve(3); - - for (const PageId& page_id : pages) { - erase_variations.clear(); - switch (page_id.subPage()) { - case PageId::SINGLE_PAGE: - erase_variations.push_back(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::SINGLE_PAGE); - erase_variations.push_back(PageId::LEFT_PAGE); - break; - case PageId::RIGHT_PAGE: - erase_variations.push_back(PageId::SINGLE_PAGE); - erase_variations.push_back(PageId::RIGHT_PAGE); - break; - } + std::vector erase_variations; + erase_variations.reserve(3); - for (PageId::SubPage subpage : erase_variations) { - QFile::remove(m_outFileNameGen.filePathFor(PageId(page_id.imageId(), subpage))); - } + for (const PageId& page_id : pages) { + erase_variations.clear(); + switch (page_id.subPage()) { + case PageId::SINGLE_PAGE: + erase_variations.push_back(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::SINGLE_PAGE); + erase_variations.push_back(PageId::LEFT_PAGE); + break; + case PageId::RIGHT_PAGE: + erase_variations.push_back(PageId::SINGLE_PAGE); + erase_variations.push_back(PageId::RIGHT_PAGE); + break; + } + + for (PageId::SubPage subpage : erase_variations) { + QFile::remove(m_outFileNameGen.filePathFor(PageId(page_id.imageId(), subpage))); } + } } BackgroundTaskPtr MainWindow::createCompositeTask(const PageInfo& page, const int last_filter_idx, const bool batch, bool debug) { - intrusive_ptr fix_orientation_task; - intrusive_ptr page_split_task; - intrusive_ptr deskew_task; - intrusive_ptr select_content_task; - intrusive_ptr page_layout_task; - intrusive_ptr output_task; - - if (batch) { - debug = false; - } - - if (last_filter_idx >= m_ptrStages->outputFilterIdx()) { - output_task = m_ptrStages->outputFilter()->createTask(page.id(), m_ptrThumbnailCache, m_outFileNameGen, batch, - debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->pageLayoutFilterIdx()) { - page_layout_task = m_ptrStages->pageLayoutFilter()->createTask(page.id(), output_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->selectContentFilterIdx()) { - select_content_task = m_ptrStages->selectContentFilter()->createTask(page.id(), page_layout_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->deskewFilterIdx()) { - deskew_task = m_ptrStages->deskewFilter()->createTask(page.id(), select_content_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->pageSplitFilterIdx()) { - page_split_task = m_ptrStages->pageSplitFilter()->createTask(page, deskew_task, batch, debug); - debug = false; - } - if (last_filter_idx >= m_ptrStages->fixOrientationFilterIdx()) { - fix_orientation_task = m_ptrStages->fixOrientationFilter()->createTask(page.id(), page_split_task, batch); - debug = false; - } - assert(fix_orientation_task); - - return make_intrusive(batch ? BackgroundTask::BATCH : BackgroundTask::INTERACTIVE, page, - m_ptrThumbnailCache, m_ptrPages, fix_orientation_task); + intrusive_ptr fix_orientation_task; + intrusive_ptr page_split_task; + intrusive_ptr deskew_task; + intrusive_ptr select_content_task; + intrusive_ptr page_layout_task; + intrusive_ptr output_task; + + if (batch) { + debug = false; + } + + if (last_filter_idx >= m_stages->outputFilterIdx()) { + output_task = m_stages->outputFilter()->createTask(page.id(), m_thumbnailCache, m_outFileNameGen, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->pageLayoutFilterIdx()) { + page_layout_task = m_stages->pageLayoutFilter()->createTask(page.id(), output_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->selectContentFilterIdx()) { + select_content_task = m_stages->selectContentFilter()->createTask(page.id(), page_layout_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->deskewFilterIdx()) { + deskew_task = m_stages->deskewFilter()->createTask(page.id(), select_content_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->pageSplitFilterIdx()) { + page_split_task = m_stages->pageSplitFilter()->createTask(page, deskew_task, batch, debug); + debug = false; + } + if (last_filter_idx >= m_stages->fixOrientationFilterIdx()) { + fix_orientation_task = m_stages->fixOrientationFilter()->createTask(page.id(), page_split_task, batch); + debug = false; + } + assert(fix_orientation_task); + + return make_intrusive(batch ? BackgroundTask::BATCH : BackgroundTask::INTERACTIVE, page, + m_thumbnailCache, m_pages, fix_orientation_task); } // MainWindow::createCompositeTask intrusive_ptr MainWindow::createCompositeCacheDrivenTask(const int last_filter_idx) { - intrusive_ptr fix_orientation_task; - intrusive_ptr page_split_task; - intrusive_ptr deskew_task; - intrusive_ptr select_content_task; - intrusive_ptr page_layout_task; - intrusive_ptr output_task; - - if (last_filter_idx >= m_ptrStages->outputFilterIdx()) { - output_task = m_ptrStages->outputFilter()->createCacheDrivenTask(m_outFileNameGen); - } - if (last_filter_idx >= m_ptrStages->pageLayoutFilterIdx()) { - page_layout_task = m_ptrStages->pageLayoutFilter()->createCacheDrivenTask(output_task); - } - if (last_filter_idx >= m_ptrStages->selectContentFilterIdx()) { - select_content_task = m_ptrStages->selectContentFilter()->createCacheDrivenTask(page_layout_task); - } - if (last_filter_idx >= m_ptrStages->deskewFilterIdx()) { - deskew_task = m_ptrStages->deskewFilter()->createCacheDrivenTask(select_content_task); - } - if (last_filter_idx >= m_ptrStages->pageSplitFilterIdx()) { - page_split_task = m_ptrStages->pageSplitFilter()->createCacheDrivenTask(deskew_task); - } - if (last_filter_idx >= m_ptrStages->fixOrientationFilterIdx()) { - fix_orientation_task = m_ptrStages->fixOrientationFilter()->createCacheDrivenTask(page_split_task); - } - - assert(fix_orientation_task); - - return fix_orientation_task; + intrusive_ptr fix_orientation_task; + intrusive_ptr page_split_task; + intrusive_ptr deskew_task; + intrusive_ptr select_content_task; + intrusive_ptr page_layout_task; + intrusive_ptr output_task; + + if (last_filter_idx >= m_stages->outputFilterIdx()) { + output_task = m_stages->outputFilter()->createCacheDrivenTask(m_outFileNameGen); + } + if (last_filter_idx >= m_stages->pageLayoutFilterIdx()) { + page_layout_task = m_stages->pageLayoutFilter()->createCacheDrivenTask(output_task); + } + if (last_filter_idx >= m_stages->selectContentFilterIdx()) { + select_content_task = m_stages->selectContentFilter()->createCacheDrivenTask(page_layout_task); + } + if (last_filter_idx >= m_stages->deskewFilterIdx()) { + deskew_task = m_stages->deskewFilter()->createCacheDrivenTask(select_content_task); + } + if (last_filter_idx >= m_stages->pageSplitFilterIdx()) { + page_split_task = m_stages->pageSplitFilter()->createCacheDrivenTask(deskew_task); + } + if (last_filter_idx >= m_stages->fixOrientationFilterIdx()) { + fix_orientation_task = m_stages->fixOrientationFilter()->createCacheDrivenTask(page_split_task); + } + + assert(fix_orientation_task); + + return fix_orientation_task; } // MainWindow::createCompositeCacheDrivenTask void MainWindow::updateDisambiguationRecords(const PageSequence& pages) { - for (const PageInfo& page : pages) { - m_outFileNameGen.disambiguator()->registerFile(page.imageId().filePath()); - } + for (const PageInfo& page : pages) { + m_outFileNameGen.disambiguator()->registerFile(page.imageId().filePath()); + } } PageSelectionAccessor MainWindow::newPageSelectionAccessor() { - auto provider = make_intrusive(this); + auto provider = make_intrusive(this); - return PageSelectionAccessor(provider); + return PageSelectionAccessor(provider); } void MainWindow::changeEvent(QEvent* event) { - if (event != nullptr) { - switch (event->type()) { - case QEvent::LanguageChange: - retranslateUi(this); - updateWindowTitle(); - break; - default: - QWidget::changeEvent(event); - break; - } + if (event != nullptr) { + switch (event->type()) { + case QEvent::LanguageChange: + retranslateUi(this); + updateWindowTitle(); + break; + default: + QWidget::changeEvent(event); + break; } + } } void MainWindow::setDockWidgetsVisible(bool state) { - filterDockWidget->setVisible(state); - thumbnailsDockWidget->setVisible(state); + filterDockWidget->setVisible(state); + thumbnailsDockWidget->setVisible(state); +} + +void MainWindow::scaleThumbnails(const QWheelEvent* wheel_event) { + const QPoint& angle_delta = wheel_event->angleDelta(); + const int wheel_dist = angle_delta.x() + angle_delta.y(); + + if (std::abs(wheel_dist) >= 30) { + const double dx = std::copysign(25.0, wheel_dist); + const double dy = std::copysign(16.0, wheel_dist); + const double width = qBound(100.0, m_maxLogicalThumbSize.width() + dx, 1000.0); + const double height = qBound(64.0, m_maxLogicalThumbSize.height() + dy, 640.0); + updateMaxLogicalThumbSize(QSizeF(width, height)); + + QSettings().setValue("settings/max_logical_thumb_size", m_maxLogicalThumbSize); + } +} + +void MainWindow::updateMaxLogicalThumbSize(const QSizeF& size) { + m_maxLogicalThumbSize = size; + + m_thumbSequence->setMaxLogicalThumbSize(m_maxLogicalThumbSize); + setupThumbView(); + resetThumbSequence(currentPageOrderProvider(), ThumbnailSequence::KEEP_SELECTION); } diff --git a/MainWindow.h b/MainWindow.h index 39756e831..db1472b50 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -19,31 +19,31 @@ #ifndef MAINWINDOW_H_ #define MAINWINDOW_H_ -#include "ui_MainWindow.h" -#include "FilterUiInterface.h" -#include "NonCopyable.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "AbstractCommand.h" -#include "intrusive_ptr.h" #include "BackgroundTask.h" +#include "BeforeOrAfter.h" #include "FilterResult.h" -#include "ThumbnailSequence.h" +#include "FilterUiInterface.h" +#include "NonCopyable.h" #include "OutputFileNameGenerator.h" #include "PageId.h" -#include "PageView.h" #include "PageRange.h" +#include "PageView.h" #include "SelectedPage.h" -#include "BeforeOrAfter.h" #include "StatusBarPanel.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ThumbnailSequence.h" +#include "intrusive_ptr.h" +#include "ui_MainWindow.h" class AbstractFilter; class AbstractRelinker; @@ -76,258 +76,262 @@ class QRectF; class QLayout; class MainWindow : public QMainWindow, private FilterUiInterface, private Ui::MainWindow { - DECLARE_NON_COPYABLE(MainWindow) + DECLARE_NON_COPYABLE(MainWindow) + + Q_OBJECT + public: + MainWindow(); + + ~MainWindow() override; + + PageSequence allPages() const; - Q_OBJECT -public: - MainWindow(); + std::set selectedPages() const; - ~MainWindow() override; + std::vector selectedRanges() const; - PageSequence allPages() const; + protected: + bool eventFilter(QObject* obj, QEvent* ev) override; - std::set selectedPages() const; + void closeEvent(QCloseEvent* event) override; - std::vector selectedRanges() const; + void timerEvent(QTimerEvent* event) override; -protected: - bool eventFilter(QObject* obj, QEvent* ev) override; + void changeEvent(QEvent* event) override; - void closeEvent(QCloseEvent* event) override; + public slots: - void timerEvent(QTimerEvent* event) override; + void openProject(const QString& project_file); - void changeEvent(QEvent* event) override; + private: + enum MainAreaAction { UPDATE_MAIN_AREA, CLEAR_MAIN_AREA }; -public slots: + private slots: - void openProject(const QString& project_file); + void autoSaveProject(); -private: - enum MainAreaAction { UPDATE_MAIN_AREA, CLEAR_MAIN_AREA }; + void goFirstPage(); -private slots: + void goLastPage(); - void autoSaveProject(); + void goNextPage(); - void goFirstPage(); + void goPrevPage(); - void goLastPage(); + void goNextSelectedPage(); - void goNextPage(); + void goPrevSelectedPage(); - void goPrevPage(); + void goToPage(const PageId& page_id, + ThumbnailSequence::SelectionAction selection_action = ThumbnailSequence::RESET_SELECTION); - void goToPage(const PageId& page_id); + void currentPageChanged(const PageInfo& page_info, const QRectF& thumb_rect, ThumbnailSequence::SelectionFlags flags); - void currentPageChanged(const PageInfo& page_info, - const QRectF& thumb_rect, - ThumbnailSequence::SelectionFlags flags); + void pageContextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); - void pageContextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); + void pastLastPageContextMenuRequested(const QPoint& screen_pos); - void pastLastPageContextMenuRequested(const QPoint& screen_pos); + void thumbViewFocusToggled(bool checked); - void thumbViewFocusToggled(bool checked); + void thumbViewScrolled(); - void thumbViewScrolled(); + void filterSelectionChanged(const QItemSelection& selected); - void filterSelectionChanged(const QItemSelection& selected); + void switchFilter1(); - void switchFilter1(); + void switchFilter2(); - void switchFilter2(); + void switchFilter3(); - void switchFilter3(); + void switchFilter4(); - void switchFilter4(); + void switchFilter5(); - void switchFilter5(); + void switchFilter6(); - void switchFilter6(); + void pageOrderingChanged(int idx); - void pageOrderingChanged(int idx); + void reloadRequested(); - void reloadRequested(); + void startBatchProcessing(); - void startBatchProcessing(); + void stopBatchProcessing(MainAreaAction main_area = UPDATE_MAIN_AREA); - void stopBatchProcessing(MainAreaAction main_area = UPDATE_MAIN_AREA); + void invalidateThumbnail(const PageId& page_id) override; - void invalidateThumbnail(const PageId& page_id) override; + void invalidateThumbnail(const PageInfo& page_info); - void invalidateThumbnail(const PageInfo& page_info); + void invalidateAllThumbnails() override; - void invalidateAllThumbnails() override; + void showRelinkingDialog(); - void showRelinkingDialog(); + void filterResult(const BackgroundTaskPtr& task, const FilterResultPtr& result); - void filterResult(const BackgroundTaskPtr& task, const FilterResultPtr& result); + void debugToggled(bool enabled); - void debugToggled(bool enabled); + void fixDpiDialogRequested(); - void fixDpiDialogRequested(); + void fixedDpiSubmitted(); - void fixedDpiSubmitted(); + void saveProjectTriggered(); - void saveProjectTriggered(); + void saveProjectAsTriggered(); - void saveProjectAsTriggered(); + void newProject(); - void newProject(); + void newProjectCreated(ProjectCreationContext* context); - void newProjectCreated(ProjectCreationContext* context); + void openProject(); - void openProject(); + void projectOpened(ProjectOpeningContext* context); - void projectOpened(ProjectOpeningContext* context); + void closeProject(); - void closeProject(); + void openSettingsDialog(); - void openSettingsDialog(); + void openDefaultParamsDialog(); - void openDefaultParamsDialog(); + void onSettingsChanged(); - void onSettingsChanged(); + void showAboutDialog(); - void showAboutDialog(); + void handleOutOfMemorySituation(); - void handleOutOfMemorySituation(); + private: + class PageSelectionProviderImpl; -private: - class PageSelectionProviderImpl; + enum SavePromptResult { SAVE, DONT_SAVE, CANCEL }; - enum SavePromptResult { SAVE, DONT_SAVE, CANCEL }; + typedef intrusive_ptr FilterPtr; - typedef intrusive_ptr FilterPtr; + void setOptionsWidget(FilterOptionsWidget* widget, Ownership ownership) override; - void setOptionsWidget(FilterOptionsWidget* widget, Ownership ownership) override; + void setImageWidget(QWidget* widget, + Ownership ownership, + DebugImages* debug_images = nullptr, + bool overlay = false) override; - void setImageWidget(QWidget* widget, - Ownership ownership, - DebugImages* debug_images = nullptr, - bool clear_image_widget = true) override; + intrusive_ptr> relinkingDialogRequester() override; - intrusive_ptr> relinkingDialogRequester() override; + void switchToNewProject(const intrusive_ptr& pages, + const QString& out_dir, + const QString& project_file_path = QString(), + const ProjectReader* project_reader = nullptr); - void switchToNewProject(const intrusive_ptr& pages, - const QString& out_dir, - const QString& project_file_path = QString(), - const ProjectReader* project_reader = nullptr); + void setupThumbView(); - intrusive_ptr createThumbnailCache(); + void showNewOpenProjectPanel(); - void setupThumbView(); + SavePromptResult promptProjectSave(); - void showNewOpenProjectPanel(); + static bool compareFiles(const QString& fpath1, const QString& fpath2); - SavePromptResult promptProjectSave(); + intrusive_ptr currentPageOrderProvider() const; - static bool compareFiles(const QString& fpath1, const QString& fpath2); + void updateSortOptions(); - intrusive_ptr currentPageOrderProvider() const; + void resetThumbSequence(const intrusive_ptr& page_order_provider, + ThumbnailSequence::SelectionAction selection_action = ThumbnailSequence::RESET_SELECTION); - void updateSortOptions(); + void removeWidgetsFromLayout(QLayout* layout); - void resetThumbSequence(const intrusive_ptr& page_order_provider); + void removeFilterOptionsWidget(); - void removeWidgetsFromLayout(QLayout* layout); + void removeImageWidget(); - void removeFilterOptionsWidget(); + void updateProjectActions(); - void removeImageWidget(); + bool isBatchProcessingInProgress() const; - void updateProjectActions(); + bool isProjectLoaded() const; - bool isBatchProcessingInProgress() const; + bool isBelowSelectContent() const; - bool isProjectLoaded() const; + bool isBelowSelectContent(int filter_idx) const; - bool isBelowSelectContent() const; + bool isBelowFixOrientation(int filter_idx) const; - bool isBelowSelectContent(int filter_idx) const; + bool isOutputFilter() const; - bool isBelowFixOrientation(int filter_idx) const; + bool isOutputFilter(int filter_idx) const; - bool isOutputFilter() const; + PageView getCurrentView() const; - bool isOutputFilter(int filter_idx) const; + void updateMainArea(); - PageView getCurrentView() const; + bool checkReadyForOutput(const PageId* ignore = nullptr) const; - void updateMainArea(); + void loadPageInteractive(const PageInfo& page); - bool checkReadyForOutput(const PageId* ignore = nullptr) const; + void updateWindowTitle(); - void loadPageInteractive(const PageInfo& page); + bool closeProjectInteractive(); - void updateWindowTitle(); + void closeProjectWithoutSaving(); - bool closeProjectInteractive(); + bool saveProjectWithFeedback(const QString& project_file); - void closeProjectWithoutSaving(); + void showInsertFileDialog(BeforeOrAfter before_or_after, const ImageId& existig); - bool saveProjectWithFeedback(const QString& project_file); + void showRemovePagesDialog(const std::set& pages); - void showInsertFileDialog(BeforeOrAfter before_or_after, const ImageId& existig); + void insertImage(const ImageInfo& new_image, BeforeOrAfter before_or_after, ImageId existing); - void showRemovePagesDialog(const std::set& pages); + void removeFromProject(const std::set& pages); - void insertImage(const ImageInfo& new_image, BeforeOrAfter before_or_after, ImageId existing); + void eraseOutputFiles(const std::set& pages); - void removeFromProject(const std::set& pages); + BackgroundTaskPtr createCompositeTask(const PageInfo& page, int last_filter_idx, bool batch, bool debug); - void eraseOutputFiles(const std::set& pages); + intrusive_ptr createCompositeCacheDrivenTask(int last_filter_idx); - BackgroundTaskPtr createCompositeTask(const PageInfo& page, int last_filter_idx, bool batch, bool debug); + void createBatchProcessingWidget(); - intrusive_ptr createCompositeCacheDrivenTask(int last_filter_idx); + void updateDisambiguationRecords(const PageSequence& pages); - void createBatchProcessingWidget(); + void performRelinking(const intrusive_ptr& relinker); - void updateDisambiguationRecords(const PageSequence& pages); + PageSelectionAccessor newPageSelectionAccessor(); - void performRelinking(const intrusive_ptr& relinker); + void setDockWidgetsVisible(bool state); - PageSelectionAccessor newPageSelectionAccessor(); + void scaleThumbnails(const QWheelEvent* wheel_event); - void setDockWidgetsVisible(bool state); + void updateMaxLogicalThumbSize(const QSizeF& size); - QSizeF m_maxLogicalThumbSize; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrStages; - QString m_projectFile; - OutputFileNameGenerator m_outFileNameGen; - intrusive_ptr m_ptrThumbnailCache; - std::unique_ptr m_ptrThumbSequence; - std::unique_ptr m_ptrWorkerThreadPool; - std::unique_ptr m_ptrBatchQueue; - std::unique_ptr m_ptrInteractiveQueue; - QStackedLayout* m_pImageFrameLayout; - QStackedLayout* m_pOptionsFrameLayout; - QPointer m_ptrOptionsWidget; - QPointer m_ptrFixDpiDialog; - std::unique_ptr m_ptrTabbedDebugImages; - std::unique_ptr m_ptrContentBoxPropagator; - std::unique_ptr m_ptrPageOrientationPropagator; - std::unique_ptr m_ptrBatchProcessingWidget; - std::unique_ptr m_ptrProcessingIndicationWidget; - boost::function m_checkBeepWhenFinished; - SelectedPage m_selectedPage; - QObjectCleanupHandler m_optionsWidgetCleanup; - QObjectCleanupHandler m_imageWidgetCleanup; - std::unique_ptr m_ptrOutOfMemoryDialog; - int m_curFilter; - int m_ignoreSelectionChanges; - int m_ignorePageOrderingChanges; - bool m_debug; - bool m_closing; - bool m_beepOnBatchProcessingCompletion; - QTimer m_thumbResizeTimer; - QTimer m_autoSaveTimer; - bool m_autoSaveProject; - std::unique_ptr m_statusBarPanel; - std::unique_ptr m_unitsMenuActionGroup; + QSizeF m_maxLogicalThumbSize; + intrusive_ptr m_pages; + intrusive_ptr m_stages; + QString m_projectFile; + OutputFileNameGenerator m_outFileNameGen; + intrusive_ptr m_thumbnailCache; + std::unique_ptr m_thumbSequence; + std::unique_ptr m_workerThreadPool; + std::unique_ptr m_batchQueue; + std::unique_ptr m_interactiveQueue; + QStackedLayout* m_imageFrameLayout; + QStackedLayout* m_optionsFrameLayout; + QPointer m_optionsWidget; + QPointer m_fixDpiDialog; + std::unique_ptr m_tabbedDebugImages; + std::unique_ptr m_contentBoxPropagator; + std::unique_ptr m_pageOrientationPropagator; + std::unique_ptr m_batchProcessingWidget; + std::unique_ptr m_processingIndicationWidget; + boost::function m_checkBeepWhenFinished; + SelectedPage m_selectedPage; + QObjectCleanupHandler m_optionsWidgetCleanup; + QObjectCleanupHandler m_imageWidgetCleanup; + std::unique_ptr m_outOfMemoryDialog; + int m_curFilter; + int m_ignoreSelectionChanges; + int m_ignorePageOrderingChanges; + bool m_debug; + bool m_closing; + QTimer m_autoSaveTimer; + bool m_autoSaveProject; + std::unique_ptr m_statusBarPanel; + std::unique_ptr m_unitsMenuActionGroup; }; diff --git a/Margins.h b/Margins.h index 7e0126b60..273c01728 100644 --- a/Margins.h +++ b/Margins.h @@ -20,51 +20,33 @@ #define MARGINS_H_ class Margins { -public: - Margins() : m_top(), m_bottom(), m_left(), m_right() { - } - - Margins(double left, double top, double right, double bottom) - : m_top(top), m_bottom(bottom), m_left(left), m_right(right) { - } - - double top() const { - return m_top; - } - - void setTop(double val) { - m_top = val; - } - - double bottom() const { - return m_bottom; - } - - void setBottom(double val) { - m_bottom = val; - } - - double left() const { - return m_left; - } - - void setLeft(double val) { - m_left = val; - } - - double right() const { - return m_right; - } - - void setRight(double val) { - m_right = val; - } - -private: - double m_top; - double m_bottom; - double m_left; - double m_right; + public: + Margins() : m_top(), m_bottom(), m_left(), m_right() {} + + Margins(double left, double top, double right, double bottom) + : m_top(top), m_bottom(bottom), m_left(left), m_right(right) {} + + double top() const { return m_top; } + + void setTop(double val) { m_top = val; } + + double bottom() const { return m_bottom; } + + void setBottom(double val) { m_bottom = val; } + + double left() const { return m_left; } + + void setLeft(double val) { m_left = val; } + + double right() const { return m_right; } + + void setRight(double val) { m_right = val; } + + private: + double m_top; + double m_bottom; + double m_left; + double m_right; }; diff --git a/NativeScheme.cpp b/NativeScheme.cpp new file mode 100644 index 000000000..d2ac25c4c --- /dev/null +++ b/NativeScheme.cpp @@ -0,0 +1,42 @@ + +#include "NativeScheme.h" +#include + +QStyle* NativeScheme::getStyle() const { + return nullptr; +} + +QPalette NativeScheme::getPalette() const { + return QPalette(); +} + +std::unique_ptr NativeScheme::getStyleSheet() const { + auto styleSheet = std::make_unique(); + const QPalette palette = getPalette(); + + styleSheet->append(QString( + "QGraphicsView, ImageViewBase, QFrame#imageViewFrame {\n" + " background-color: %1;\n" + " background-attachment: scroll;\n" + "}").arg(palette.background().color().darker(115).name())); + + return styleSheet; +} + +ColorScheme::ColorParams NativeScheme::getColorParams() const { + ColorScheme::ColorParams customColors; + const QPalette palette = getPalette(); + + customColors[ThumbnailSequenceSelectedItemBackground] = palette.color(QPalette::Highlight).lighter(130); + customColors[ThumbnailSequenceSelectionLeaderBackground] = palette.color(QPalette::Highlight); + customColors[ProcessingIndicationFade] = palette.background().color().darker(115); + if (palette.window().color().lightnessF() < 0.5) { + // If system scheme is dark, adapt some colors. + customColors[ProcessingIndicationHeadColor] = palette.color(QPalette::Window).lighter(200); + customColors[ProcessingIndicationTail] = palette.color(QPalette::Window).lighter(130); + customColors[StageListHead] = customColors.at(ProcessingIndicationHeadColor); + customColors[StageListTail] = customColors.at(ProcessingIndicationTail); + } + + return customColors; +} diff --git a/NativeScheme.h b/NativeScheme.h new file mode 100644 index 000000000..592e6cc3c --- /dev/null +++ b/NativeScheme.h @@ -0,0 +1,20 @@ +#ifndef SCANTAILOR_ADVANCED_NATIVESCHEME_H +#define SCANTAILOR_ADVANCED_NATIVESCHEME_H + +#include +#include +#include +#include "ColorScheme.h" + +class NativeScheme : public ColorScheme { + public: + QStyle* getStyle() const override; + + QPalette getPalette() const override; + + std::unique_ptr getStyleSheet() const override; + + ColorParams getColorParams() const override; +}; + +#endif //SCANTAILOR_ADVANCED_NATIVESCHEME_H diff --git a/NewOpenProjectPanel.cpp b/NewOpenProjectPanel.cpp index 560de56b4..6667d1eb3 100644 --- a/NewOpenProjectPanel.cpp +++ b/NewOpenProjectPanel.cpp @@ -17,76 +17,76 @@ */ #include "NewOpenProjectPanel.h" +#include +#include +#include "ColorSchemeManager.h" #include "RecentProjects.h" #include "Utils.h" -#include "ColorSchemeManager.h" -#include -#include NewOpenProjectPanel::NewOpenProjectPanel(QWidget* parent) : QWidget(parent) { - setupUi(this); - - recentProjectsGroup->setLayout(new QVBoxLayout); - newProjectLabel->setText(Utils::richTextForLink(newProjectLabel->text())); - openProjectLabel->setText(Utils::richTextForLink(openProjectLabel->text())); - - RecentProjects rp; - rp.read(); - if (!rp.validate()) { - // Some project files weren't found. - // Write the list without them. - rp.write(); - } - if (rp.isEmpty()) { - recentProjectsGroup->setVisible(false); - } else { - rp.enumerate([this](const QString& file_path) { addRecentProject(file_path); }); - } - - connect(newProjectLabel, SIGNAL(linkActivated(const QString&)), this, SIGNAL(newProject())); - connect(openProjectLabel, SIGNAL(linkActivated(const QString&)), this, SIGNAL(openProject())); + setupUi(this); + + recentProjectsGroup->setLayout(new QVBoxLayout); + newProjectLabel->setText(Utils::richTextForLink(newProjectLabel->text())); + openProjectLabel->setText(Utils::richTextForLink(openProjectLabel->text())); + + RecentProjects rp; + rp.read(); + if (!rp.validate()) { + // Some project files weren't found. + // Write the list without them. + rp.write(); + } + if (rp.isEmpty()) { + recentProjectsGroup->setVisible(false); + } else { + rp.enumerate([this](const QString& file_path) { addRecentProject(file_path); }); + } + + connect(newProjectLabel, SIGNAL(linkActivated(const QString&)), this, SIGNAL(newProject())); + connect(openProjectLabel, SIGNAL(linkActivated(const QString&)), this, SIGNAL(openProject())); } void NewOpenProjectPanel::addRecentProject(const QString& file_path) { - const QFileInfo file_info(file_path); - QString base_name(file_info.baseName()); - if (base_name.isEmpty()) { - base_name = QChar('_'); - } - auto* label = new QLabel(recentProjectsGroup); - label->setWordWrap(true); - label->setTextFormat(Qt::RichText); - label->setText(Utils::richTextForLink(base_name, file_path)); - label->setToolTip(file_path); - - int fontSize = recentProjectsGroup->font().pointSize(); - QFont widgetFont = label->font(); - widgetFont.setPointSize(fontSize); - label->setFont(widgetFont); - - recentProjectsGroup->layout()->addWidget(label); - - connect(label, SIGNAL(linkActivated(const QString&)), this, SIGNAL(openRecentProject(const QString&))); + const QFileInfo file_info(file_path); + QString base_name(file_info.baseName()); + if (base_name.isEmpty()) { + base_name = QChar('_'); + } + auto* label = new QLabel(recentProjectsGroup); + label->setWordWrap(true); + label->setTextFormat(Qt::RichText); + label->setText(Utils::richTextForLink(base_name, file_path)); + label->setToolTip(file_path); + + int fontSize = recentProjectsGroup->font().pointSize(); + QFont widgetFont = label->font(); + widgetFont.setPointSize(fontSize); + label->setFont(widgetFont); + + recentProjectsGroup->layout()->addWidget(label); + + connect(label, SIGNAL(linkActivated(const QString&)), this, SIGNAL(openRecentProject(const QString&))); } void NewOpenProjectPanel::paintEvent(QPaintEvent*) { - // In fact Qt doesn't draw QWidget's background, unless - // autoFillBackground property is set, so we can safely - // draw our borders and shadows in the margins area. + // In fact Qt doesn't draw QWidget's background, unless + // autoFillBackground property is set, so we can safely + // draw our borders and shadows in the margins area. - int left = 0, top = 0, right = 0, bottom = 0; - layout()->getContentsMargins(&left, &top, &right, &bottom); + int left = 0, top = 0, right = 0, bottom = 0; + layout()->getContentsMargins(&left, &top, &right, &bottom); - const QRect widget_rect(rect()); - const QRect except_margins(widget_rect.adjusted(left, top, -right, -bottom)); + const QRect widget_rect(rect()); + const QRect except_margins(widget_rect.adjusted(left, top, -right, -bottom)); - const int border = 1; // Solid line border width. + const int border = 1; // Solid line border width. - QPainter painter(this); + QPainter painter(this); - painter.setPen( - QPen(ColorSchemeManager::instance()->getColorParam("open_new_project_border_color", palette().windowText()), - border)); + const QBrush border_brush + = ColorSchemeManager::instance()->getColorParam(ColorScheme::OpenNewProjectBorder, palette().windowText()); + painter.setPen(QPen(border_brush, border)); - painter.drawRect(except_margins); + painter.drawRect(except_margins); } // NewOpenProjectPanel::paintEvent diff --git a/NewOpenProjectPanel.h b/NewOpenProjectPanel.h index cba9fbc76..6f0c51eb1 100644 --- a/NewOpenProjectPanel.h +++ b/NewOpenProjectPanel.h @@ -19,30 +19,30 @@ #ifndef NEW_OPEN_PROJECT_PANEL_H_ #define NEW_OPEN_PROJECT_PANEL_H_ -#include "ui_NewOpenProjectPanel.h" #include #include +#include "ui_NewOpenProjectPanel.h" class QString; class NewOpenProjectPanel : public QWidget, private Ui::NewOpenProjectPanel { - Q_OBJECT -public: - explicit NewOpenProjectPanel(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit NewOpenProjectPanel(QWidget* parent = nullptr); -signals: + signals: - void newProject(); + void newProject(); - void openProject(); + void openProject(); - void openRecentProject(const QString& project_file); + void openRecentProject(const QString& project_file); -protected: - void paintEvent(QPaintEvent*) override; + protected: + void paintEvent(QPaintEvent*) override; -private: - void addRecentProject(const QString& file_path); + private: + void addRecentProject(const QString& file_path); }; diff --git a/NonOwningWidget.cpp b/NonOwningWidget.cpp index f35cc8216..0954ef557 100644 --- a/NonOwningWidget.cpp +++ b/NonOwningWidget.cpp @@ -18,13 +18,12 @@ #include "NonOwningWidget.h" -NonOwningWidget::NonOwningWidget(QWidget* parent) : QWidget(parent) { -} +NonOwningWidget::NonOwningWidget(QWidget* parent) : QWidget(parent) {} NonOwningWidget::~NonOwningWidget() { - for (QObject* child : children()) { - if (auto* widget = dynamic_cast(child)) { - widget->setParent(nullptr); - } + for (QObject* child : children()) { + if (auto* widget = dynamic_cast(child)) { + widget->setParent(nullptr); } + } } diff --git a/NonOwningWidget.h b/NonOwningWidget.h index 0f47b71ab..3ffed9027 100644 --- a/NonOwningWidget.h +++ b/NonOwningWidget.h @@ -26,10 +26,10 @@ * rather it calls setParent(0) on them. */ class NonOwningWidget : public QWidget { -public: - explicit NonOwningWidget(QWidget* parent = nullptr); + public: + explicit NonOwningWidget(QWidget* parent = nullptr); - ~NonOwningWidget() override; + ~NonOwningWidget() override; }; diff --git a/OpenGLSupport.cpp b/OpenGLSupport.cpp index 82af00bf1..2b5943226 100644 --- a/OpenGLSupport.cpp +++ b/OpenGLSupport.cpp @@ -17,42 +17,42 @@ */ #include "OpenGLSupport.h" -#include -#include +#include #include #include -#include +#include +#include bool OpenGLSupport::supported() { - QSurfaceFormat format; - format.setSamples(2); - format.setAlphaBufferSize(8); - - QOpenGLContext context; - context.setFormat(format); - if (!context.create()) { - return false; - } - format = context.format(); - - if (format.samples() < 2) { - return false; - } - if (!format.hasAlpha()) { - return false; - } - - return true; + QSurfaceFormat format; + format.setSamples(2); + format.setAlphaBufferSize(8); + + QOpenGLContext context; + context.setFormat(format); + if (!context.create()) { + return false; + } + format = context.format(); + + if (format.samples() < 2) { + return false; + } + if (!format.hasAlpha()) { + return false; + } + + return true; } QString OpenGLSupport::deviceName() { - QString name; - QOpenGLContext context; - QOffscreenSurface surface; - if (context.create() && (surface.create(), true) && context.makeCurrent(&surface)) { - name = QString::fromUtf8((const char*) context.functions()->glGetString(GL_RENDERER)); - context.doneCurrent(); - } - - return name; + QString name; + QOpenGLContext context; + QOffscreenSurface surface; + if (context.create() && (surface.create(), true) && context.makeCurrent(&surface)) { + name = QString::fromUtf8((const char*) context.functions()->glGetString(GL_RENDERER)); + context.doneCurrent(); + } + + return name; } diff --git a/OpenGLSupport.h b/OpenGLSupport.h index f15fab55e..cbf55693f 100644 --- a/OpenGLSupport.h +++ b/OpenGLSupport.h @@ -22,18 +22,18 @@ #include class OpenGLSupport { -public: - /** - * \brief Returns true if OpenGL support is present and provides the necessary features. - */ - static bool supported(); - - /** - * \brief Returns the device name (GL_RENDERER) associated with the default OpenGL context. - * - * \note This function has to be called from the GUI thread only. - */ - static QString deviceName(); + public: + /** + * \brief Returns true if OpenGL support is present and provides the necessary features. + */ + static bool supported(); + + /** + * \brief Returns the device name (GL_RENDERER) associated with the default OpenGL context. + * + * \note This function has to be called from the GUI thread only. + */ + static QString deviceName(); }; diff --git a/OrderByCompleteness.cpp b/OrderByCompleteness.cpp new file mode 100644 index 000000000..f3682a220 --- /dev/null +++ b/OrderByCompleteness.cpp @@ -0,0 +1,9 @@ +#include "OrderByCompleteness.h" + +bool OrderByCompleteness::precedes(const PageId&, bool lhs_incomplete, const PageId&, bool rhs_incomplete) const { + if (lhs_incomplete != rhs_incomplete) { + // Incomplete pages go to the back. + return rhs_incomplete; + } + return true; +} diff --git a/OrderByCompleteness.h b/OrderByCompleteness.h new file mode 100644 index 000000000..4068432d4 --- /dev/null +++ b/OrderByCompleteness.h @@ -0,0 +1,15 @@ +#ifndef SCANTAILOR_ADVANCED_ORDERBYCOMPLETENESS_H +#define SCANTAILOR_ADVANCED_ORDERBYCOMPLETENESS_H + + +#include "PageOrderProvider.h" + +class OrderByCompleteness : public PageOrderProvider { + public: + OrderByCompleteness() = default; + + bool precedes(const PageId&, bool lhs_incomplete, const PageId&, bool rhs_incomplete) const override; +}; + + +#endif // SCANTAILOR_ADVANCED_ORDERBYCOMPLETENESS_H diff --git a/OrderByDeviationProvider.cpp b/OrderByDeviationProvider.cpp index 307312aad..05ed68faf 100644 --- a/OrderByDeviationProvider.cpp +++ b/OrderByDeviationProvider.cpp @@ -2,17 +2,16 @@ #include "OrderByDeviationProvider.h" OrderByDeviationProvider::OrderByDeviationProvider(const DeviationProvider& deviationProvider) - : m_deviationProvider(&deviationProvider) { -} + : m_deviationProvider(&deviationProvider) {} bool OrderByDeviationProvider::precedes(const PageId& lhs_page, bool lhs_incomplete, const PageId& rhs_page, bool rhs_incomplete) const { - if (lhs_incomplete != rhs_incomplete) { - // Invalid (unknown) sizes go to the back. - return lhs_incomplete; - } + if (lhs_incomplete != rhs_incomplete) { + // Invalid (unknown) sizes go to the back. + return lhs_incomplete; + } - return (m_deviationProvider->getDeviationValue(lhs_page) > m_deviationProvider->getDeviationValue(rhs_page)); + return (m_deviationProvider->getDeviationValue(lhs_page) > m_deviationProvider->getDeviationValue(rhs_page)); } diff --git a/OrderByDeviationProvider.h b/OrderByDeviationProvider.h index 38fb27314..ad3bbe602 100644 --- a/OrderByDeviationProvider.h +++ b/OrderByDeviationProvider.h @@ -8,16 +8,16 @@ #include "PageOrderProvider.h" class OrderByDeviationProvider : public PageOrderProvider { -public: - explicit OrderByDeviationProvider(const DeviationProvider& deviationProvider); + public: + explicit OrderByDeviationProvider(const DeviationProvider& deviationProvider); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - const DeviationProvider* m_deviationProvider; + private: + const DeviationProvider* m_deviationProvider; }; diff --git a/OrthogonalRotation.cpp b/OrthogonalRotation.cpp index 109a942c1..09a63caed 100644 --- a/OrthogonalRotation.cpp +++ b/OrthogonalRotation.cpp @@ -17,121 +17,121 @@ */ #include "OrthogonalRotation.h" -#include #include +#include #include #include void OrthogonalRotation::nextClockwiseDirection() { - m_degrees += 90; - if (m_degrees == 360) { - m_degrees = 0; - } + m_degrees += 90; + if (m_degrees == 360) { + m_degrees = 0; + } } void OrthogonalRotation::prevClockwiseDirection() { - m_degrees -= 90; - if (m_degrees == -90) { - m_degrees = 270; - } + m_degrees -= 90; + if (m_degrees == -90) { + m_degrees = 270; + } } QSize OrthogonalRotation::rotate(const QSize& dimensions) const { - if ((m_degrees == 90) || (m_degrees == 270)) { - return QSize(dimensions.height(), dimensions.width()); - } else { - return dimensions; - } + if ((m_degrees == 90) || (m_degrees == 270)) { + return QSize(dimensions.height(), dimensions.width()); + } else { + return dimensions; + } } QSize OrthogonalRotation::unrotate(const QSize& dimensions) const { - return rotate(dimensions); + return rotate(dimensions); } QSizeF OrthogonalRotation::rotate(const QSizeF& dimensions) const { - if ((m_degrees == 90) || (m_degrees == 270)) { - return QSizeF(dimensions.height(), dimensions.width()); - } else { - return dimensions; - } + if ((m_degrees == 90) || (m_degrees == 270)) { + return QSizeF(dimensions.height(), dimensions.width()); + } else { + return dimensions; + } } QSizeF OrthogonalRotation::unrotate(const QSizeF& dimensions) const { - return rotate(dimensions); + return rotate(dimensions); } QPointF OrthogonalRotation::rotate(const QPointF& point, const double xmax, const double ymax) const { - QPointF rotated; - - switch (m_degrees) { - case 0: - rotated = point; - break; - case 90: - rotated.setX(ymax - point.y()); - rotated.setY(point.x()); - break; - case 180: - rotated.setX(xmax - point.x()); - rotated.setY(ymax - point.y()); - break; - case 270: - rotated.setX(point.y()); - rotated.setY(xmax - point.x()); - break; - default: - assert(!"Unreachable"); - } - - return rotated; + QPointF rotated; + + switch (m_degrees) { + case 0: + rotated = point; + break; + case 90: + rotated.setX(ymax - point.y()); + rotated.setY(point.x()); + break; + case 180: + rotated.setX(xmax - point.x()); + rotated.setY(ymax - point.y()); + break; + case 270: + rotated.setX(point.y()); + rotated.setY(xmax - point.x()); + break; + default: + assert(!"Unreachable"); + } + + return rotated; } QPointF OrthogonalRotation::unrotate(const QPointF& point, const double xmax, const double ymax) const { - QPointF unrotated; - - switch (m_degrees) { - case 0: - unrotated = point; - break; - case 90: - unrotated.setX(point.y()); - unrotated.setY(xmax - point.x()); - break; - case 180: - unrotated.setX(xmax - point.x()); - unrotated.setY(ymax - point.y()); - break; - case 270: - unrotated.setX(ymax - point.y()); - unrotated.setY(point.x()); - break; - default: - assert(!"Unreachable"); - } - - return unrotated; + QPointF unrotated; + + switch (m_degrees) { + case 0: + unrotated = point; + break; + case 90: + unrotated.setX(point.y()); + unrotated.setY(xmax - point.x()); + break; + case 180: + unrotated.setX(xmax - point.x()); + unrotated.setY(ymax - point.y()); + break; + case 270: + unrotated.setX(ymax - point.y()); + unrotated.setY(point.x()); + break; + default: + assert(!"Unreachable"); + } + + return unrotated; } QTransform OrthogonalRotation::transform(const QSizeF& dimensions) const { - QTransform t; - - switch (m_degrees) { - case 0: - return t; - case 90: - t.translate(dimensions.height(), 0); - break; - case 180: - t.translate(dimensions.width(), dimensions.height()); - break; - case 270: - t.translate(0, dimensions.width()); - break; - default: - assert(!"Unreachable"); - } - - t.rotate(m_degrees); - - return t; + QTransform t; + + switch (m_degrees) { + case 0: + return t; + case 90: + t.translate(dimensions.height(), 0); + break; + case 180: + t.translate(dimensions.width(), dimensions.height()); + break; + case 270: + t.translate(0, dimensions.width()); + break; + default: + assert(!"Unreachable"); + } + + t.rotate(m_degrees); + + return t; } diff --git a/OrthogonalRotation.h b/OrthogonalRotation.h index c045561ef..89a4d109f 100644 --- a/OrthogonalRotation.h +++ b/OrthogonalRotation.h @@ -25,43 +25,40 @@ class QPointF; class QTransform; class OrthogonalRotation { -public: - OrthogonalRotation() : m_degrees(0) { - } + public: + OrthogonalRotation() : m_degrees(0) {} - int toDegrees() const { - return m_degrees; - } + int toDegrees() const { return m_degrees; } - void nextClockwiseDirection(); + void nextClockwiseDirection(); - void prevClockwiseDirection(); + void prevClockwiseDirection(); - QSize rotate(const QSize& dimensions) const; + QSize rotate(const QSize& dimensions) const; - QSize unrotate(const QSize& dimensions) const; + QSize unrotate(const QSize& dimensions) const; - QSizeF rotate(const QSizeF& dimensions) const; + QSizeF rotate(const QSizeF& dimensions) const; - QSizeF unrotate(const QSizeF& dimensions) const; + QSizeF unrotate(const QSizeF& dimensions) const; - QPointF rotate(const QPointF& point, double xmax, double ymax) const; + QPointF rotate(const QPointF& point, double xmax, double ymax) const; - QPointF unrotate(const QPointF& point, double xmax, double ymax) const; + QPointF unrotate(const QPointF& point, double xmax, double ymax) const; - QTransform transform(const QSizeF& dimensions) const; + QTransform transform(const QSizeF& dimensions) const; -private: - int m_degrees; + private: + int m_degrees; }; inline bool operator==(const OrthogonalRotation& lhs, const OrthogonalRotation& rhs) { - return lhs.toDegrees() == rhs.toDegrees(); + return lhs.toDegrees() == rhs.toDegrees(); } inline bool operator!=(const OrthogonalRotation& lhs, const OrthogonalRotation& rhs) { - return lhs.toDegrees() != rhs.toDegrees(); + return lhs.toDegrees() != rhs.toDegrees(); } #endif // ifndef ORTHOGONALROTATION_H_ diff --git a/OutOfMemoryDialog.cpp b/OutOfMemoryDialog.cpp index 4b6bcb96a..24ffd193e 100644 --- a/OutOfMemoryDialog.cpp +++ b/OutOfMemoryDialog.cpp @@ -17,25 +17,25 @@ */ #include "OutOfMemoryDialog.h" -#include "ProjectWriter.h" -#include "RecentProjects.h" #include #include #include #include +#include "ProjectWriter.h" +#include "RecentProjects.h" OutOfMemoryDialog::OutOfMemoryDialog(QWidget* parent) : QDialog(parent) { - ui.setupUi(this); - if (sizeof(void*) > 4) { - ui.only_32bit_1->hide(); - ui.only_32bit_2->hide(); - } + ui.setupUi(this); + if (sizeof(void*) > 4) { + ui.only_32bit_1->hide(); + ui.only_32bit_2->hide(); + } - ui.topLevelStack->setCurrentWidget(ui.mainPage); + ui.topLevelStack->setCurrentWidget(ui.mainPage); - connect(ui.saveProjectBtn, SIGNAL(clicked()), SLOT(saveProject())); - connect(ui.saveProjectAsBtn, SIGNAL(clicked()), SLOT(saveProjectAs())); - connect(ui.dontSaveBtn, SIGNAL(clicked()), SLOT(reject())); + connect(ui.saveProjectBtn, SIGNAL(clicked()), SLOT(saveProject())); + connect(ui.saveProjectAsBtn, SIGNAL(clicked()), SLOT(saveProjectAs())); + connect(ui.dontSaveBtn, SIGNAL(clicked()), SLOT(reject())); } void OutOfMemoryDialog::setParams(const QString& project_file, @@ -43,70 +43,70 @@ void OutOfMemoryDialog::setParams(const QString& project_file, intrusive_ptr pages, const SelectedPage& selected_page, const OutputFileNameGenerator& out_file_name_gen) { - m_projectFile = project_file; - m_ptrStages = std::move(stages); - m_ptrPages = std::move(pages); - m_selectedPage = selected_page; - m_outFileNameGen = out_file_name_gen; + m_projectFile = project_file; + m_stages = std::move(stages); + m_pages = std::move(pages); + m_selectedPage = selected_page; + m_outFileNameGen = out_file_name_gen; - ui.saveProjectBtn->setVisible(!project_file.isEmpty()); + ui.saveProjectBtn->setVisible(!project_file.isEmpty()); } void OutOfMemoryDialog::saveProject() { - if (m_projectFile.isEmpty()) { - saveProjectAs(); - } else if (saveProjectWithFeedback(m_projectFile)) { - showSaveSuccessScreen(); - } + if (m_projectFile.isEmpty()) { + saveProjectAs(); + } else if (saveProjectWithFeedback(m_projectFile)) { + showSaveSuccessScreen(); + } } void OutOfMemoryDialog::saveProjectAs() { - // XXX: this function is duplicated MainWindow - - QString project_dir; - if (!m_projectFile.isEmpty()) { - project_dir = QFileInfo(m_projectFile).absolutePath(); - } else { - QSettings settings; - project_dir = settings.value("project/lastDir").toString(); - } - - QString project_file( - QFileDialog::getSaveFileName(this, QString(), project_dir, tr("Scan Tailor Projects") + " (*.ScanTailor)")); - if (project_file.isEmpty()) { - return; - } - - if (!project_file.endsWith(".ScanTailor", Qt::CaseInsensitive)) { - project_file += ".ScanTailor"; - } - - if (saveProjectWithFeedback(project_file)) { - m_projectFile = project_file; - showSaveSuccessScreen(); - - QSettings settings; - settings.setValue("project/lastDir", QFileInfo(m_projectFile).absolutePath()); - - RecentProjects rp; - rp.read(); - rp.setMostRecent(m_projectFile); - rp.write(); - } + // XXX: this function is duplicated MainWindow + + QString project_dir; + if (!m_projectFile.isEmpty()) { + project_dir = QFileInfo(m_projectFile).absolutePath(); + } else { + QSettings settings; + project_dir = settings.value("project/lastDir").toString(); + } + + QString project_file( + QFileDialog::getSaveFileName(this, QString(), project_dir, tr("Scan Tailor Projects") + " (*.ScanTailor)")); + if (project_file.isEmpty()) { + return; + } + + if (!project_file.endsWith(".ScanTailor", Qt::CaseInsensitive)) { + project_file += ".ScanTailor"; + } + + if (saveProjectWithFeedback(project_file)) { + m_projectFile = project_file; + showSaveSuccessScreen(); + + QSettings settings; + settings.setValue("project/lastDir", QFileInfo(m_projectFile).absolutePath()); + + RecentProjects rp; + rp.read(); + rp.setMostRecent(m_projectFile); + rp.write(); + } } // OutOfMemoryDialog::saveProjectAs bool OutOfMemoryDialog::saveProjectWithFeedback(const QString& project_file) { - ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen); + ProjectWriter writer(m_pages, m_selectedPage, m_outFileNameGen); - if (!writer.write(project_file, m_ptrStages->filters())) { - QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); + if (!writer.write(project_file, m_stages->filters())) { + QMessageBox::warning(this, tr("Error"), tr("Error saving the project file!")); - return false; - } + return false; + } - return true; + return true; } void OutOfMemoryDialog::showSaveSuccessScreen() { - ui.topLevelStack->setCurrentWidget(ui.saveSuccessPage); + ui.topLevelStack->setCurrentWidget(ui.saveSuccessPage); } diff --git a/OutOfMemoryDialog.h b/OutOfMemoryDialog.h index 640b28cdf..dc7026438 100644 --- a/OutOfMemoryDialog.h +++ b/OutOfMemoryDialog.h @@ -19,44 +19,44 @@ #ifndef OUT_OF_MEMORY_DIALOG_H_ #define OUT_OF_MEMORY_DIALOG_H_ -#include "ui_OutOfMemoryDialog.h" +#include +#include #include "OutputFileNameGenerator.h" -#include "intrusive_ptr.h" -#include "StageSequence.h" #include "ProjectPages.h" #include "SelectedPage.h" -#include -#include +#include "StageSequence.h" +#include "intrusive_ptr.h" +#include "ui_OutOfMemoryDialog.h" class OutOfMemoryDialog : public QDialog { - Q_OBJECT -public: - explicit OutOfMemoryDialog(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit OutOfMemoryDialog(QWidget* parent = nullptr); - void setParams(const QString& project_file, - // may be empty - intrusive_ptr stages, - intrusive_ptr pages, - const SelectedPage& selected_page, - const OutputFileNameGenerator& out_file_name_gen); + void setParams(const QString& project_file, + // may be empty + intrusive_ptr stages, + intrusive_ptr pages, + const SelectedPage& selected_page, + const OutputFileNameGenerator& out_file_name_gen); -private slots: + private slots: - void saveProject(); + void saveProject(); - void saveProjectAs(); + void saveProjectAs(); -private: - bool saveProjectWithFeedback(const QString& project_file); + private: + bool saveProjectWithFeedback(const QString& project_file); - void showSaveSuccessScreen(); + void showSaveSuccessScreen(); - Ui::OutOfMemoryDialog ui; - QString m_projectFile; - intrusive_ptr m_ptrStages; - intrusive_ptr m_ptrPages; - SelectedPage m_selectedPage; - OutputFileNameGenerator m_outFileNameGen; + Ui::OutOfMemoryDialog ui; + QString m_projectFile; + intrusive_ptr m_stages; + intrusive_ptr m_pages; + SelectedPage m_selectedPage; + OutputFileNameGenerator m_outFileNameGen; }; diff --git a/OutOfMemoryHandler.cpp b/OutOfMemoryHandler.cpp index 5211a4be3..a90cca379 100644 --- a/OutOfMemoryHandler.cpp +++ b/OutOfMemoryHandler.cpp @@ -18,41 +18,40 @@ #include "OutOfMemoryHandler.h" -OutOfMemoryHandler::OutOfMemoryHandler() : m_hadOOM(false) { -} +OutOfMemoryHandler::OutOfMemoryHandler() : m_hadOOM(false) {} OutOfMemoryHandler& OutOfMemoryHandler::instance() { - // Depending on the compiler, this may not be thread-safe. - // However, because we insist an instance of this object to be created early on, - // the only case that might get us into trouble is an out-of-memory situation - // after main() has returned and this instance got destroyed. This scenario - // sounds rather fantastic, and is not a big deal, as the project would have - // already been saved. - static OutOfMemoryHandler object; - - return object; + // Depending on the compiler, this may not be thread-safe. + // However, because we insist an instance of this object to be created early on, + // the only case that might get us into trouble is an out-of-memory situation + // after main() has returned and this instance got destroyed. This scenario + // sounds rather fantastic, and is not a big deal, as the project would have + // already been saved. + static OutOfMemoryHandler object; + + return object; } void OutOfMemoryHandler::allocateEmergencyMemory(size_t bytes) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - boost::scoped_array(new char[bytes]).swap(m_emergencyBuffer); + boost::scoped_array(new char[bytes]).swap(m_emergencyBuffer); } void OutOfMemoryHandler::handleOutOfMemorySituation() { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - if (m_hadOOM) { - return; - } + if (m_hadOOM) { + return; + } - m_hadOOM = true; - boost::scoped_array().swap(m_emergencyBuffer); - QMetaObject::invokeMethod(this, "outOfMemory", Qt::QueuedConnection); + m_hadOOM = true; + boost::scoped_array().swap(m_emergencyBuffer); + QMetaObject::invokeMethod(this, "outOfMemory", Qt::QueuedConnection); } bool OutOfMemoryHandler::hadOutOfMemorySituation() const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - return m_hadOOM; + return m_hadOOM; } diff --git a/OutOfMemoryHandler.h b/OutOfMemoryHandler.h index 9409c590f..0d5398273 100644 --- a/OutOfMemoryHandler.h +++ b/OutOfMemoryHandler.h @@ -19,40 +19,40 @@ #ifndef OUT_OF_MEMORY_HANDLER_H_ #define OUT_OF_MEMORY_HANDLER_H_ -#include "NonCopyable.h" -#include #include +#include #include #include +#include "NonCopyable.h" class OutOfMemoryHandler : public QObject { - Q_OBJECT - DECLARE_NON_COPYABLE(OutOfMemoryHandler) + Q_OBJECT + DECLARE_NON_COPYABLE(OutOfMemoryHandler) -public: - static OutOfMemoryHandler& instance(); + public: + static OutOfMemoryHandler& instance(); - /** - * To be called once, before any OOM situations can occur. - */ - void allocateEmergencyMemory(size_t bytes); + /** + * To be called once, before any OOM situations can occur. + */ + void allocateEmergencyMemory(size_t bytes); - /** May be called from any thread. */ - void handleOutOfMemorySituation(); + /** May be called from any thread. */ + void handleOutOfMemorySituation(); - bool hadOutOfMemorySituation() const; + bool hadOutOfMemorySituation() const; -signals: + signals: - /** Will be dispatched from the main thread. */ - void outOfMemory(); + /** Will be dispatched from the main thread. */ + void outOfMemory(); -private: - OutOfMemoryHandler(); + private: + OutOfMemoryHandler(); - mutable QMutex m_mutex; - boost::scoped_array m_emergencyBuffer; - bool m_hadOOM; + mutable QMutex m_mutex; + boost::scoped_array m_emergencyBuffer; + bool m_hadOOM; }; diff --git a/OutputFileNameGenerator.cpp b/OutputFileNameGenerator.cpp index 44a38c053..ab323e727 100644 --- a/OutputFileNameGenerator.cpp +++ b/OutputFileNameGenerator.cpp @@ -17,53 +17,52 @@ */ #include "OutputFileNameGenerator.h" -#include "PageId.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" -#include #include +#include #include +#include "AbstractRelinker.h" +#include "PageId.h" +#include "RelinkablePath.h" OutputFileNameGenerator::OutputFileNameGenerator() - : m_ptrDisambiguator(new FileNameDisambiguator), m_outDir(), m_layoutDirection(Qt::LeftToRight) { -} + : m_disambiguator(new FileNameDisambiguator), m_outDir(), m_layoutDirection(Qt::LeftToRight) {} OutputFileNameGenerator::OutputFileNameGenerator(intrusive_ptr disambiguator, const QString& out_dir, Qt::LayoutDirection layout_direction) - : m_ptrDisambiguator(std::move(disambiguator)), m_outDir(out_dir), m_layoutDirection(layout_direction) { - assert(m_ptrDisambiguator); + : m_disambiguator(std::move(disambiguator)), m_outDir(out_dir), m_layoutDirection(layout_direction) { + assert(m_disambiguator); } void OutputFileNameGenerator::performRelinking(const AbstractRelinker& relinker) { - m_ptrDisambiguator->performRelinking(relinker); - m_outDir = relinker.substitutionPathFor(RelinkablePath(m_outDir, RelinkablePath::Dir)); + m_disambiguator->performRelinking(relinker); + m_outDir = relinker.substitutionPathFor(RelinkablePath(m_outDir, RelinkablePath::Dir)); } QString OutputFileNameGenerator::fileNameFor(const PageId& page) const { - const bool ltr = (m_layoutDirection == Qt::LeftToRight); - const PageId::SubPage sub_page = page.subPage(); - const int label = m_ptrDisambiguator->getLabel(page.imageId().filePath()); + const bool ltr = (m_layoutDirection == Qt::LeftToRight); + const PageId::SubPage sub_page = page.subPage(); + const int label = m_disambiguator->getLabel(page.imageId().filePath()); - QString name(QFileInfo(page.imageId().filePath()).completeBaseName()); - if (label != 0) { - name += QString::fromLatin1("(%1)").arg(label); - } - if (page.imageId().isMultiPageFile()) { - name += QString::fromLatin1("_page%1").arg(page.imageId().page(), 4, 10, QLatin1Char('0')); - } - if (sub_page != PageId::SINGLE_PAGE) { - name += QLatin1Char('_'); - name += QLatin1Char(ltr == (sub_page == PageId::LEFT_PAGE) ? '1' : '2'); - name += QLatin1Char(sub_page == PageId::LEFT_PAGE ? 'L' : 'R'); - } - name += QString::fromLatin1(".tif"); + QString name(QFileInfo(page.imageId().filePath()).completeBaseName()); + if (label != 0) { + name += QString::fromLatin1("(%1)").arg(label); + } + if (page.imageId().isMultiPageFile()) { + name += QString::fromLatin1("_page%1").arg(page.imageId().page(), 4, 10, QLatin1Char('0')); + } + if (sub_page != PageId::SINGLE_PAGE) { + name += QLatin1Char('_'); + name += QLatin1Char(ltr == (sub_page == PageId::LEFT_PAGE) ? '1' : '2'); + name += QLatin1Char(sub_page == PageId::LEFT_PAGE ? 'L' : 'R'); + } + name += QString::fromLatin1(".tif"); - return name; + return name; } QString OutputFileNameGenerator::filePathFor(const PageId& page) const { - const QString file_name(fileNameFor(page)); + const QString file_name(fileNameFor(page)); - return QDir(m_outDir).absoluteFilePath(file_name); + return QDir(m_outDir).absoluteFilePath(file_name); } diff --git a/OutputFileNameGenerator.h b/OutputFileNameGenerator.h index 0e605f49b..c26cbeeb7 100644 --- a/OutputFileNameGenerator.h +++ b/OutputFileNameGenerator.h @@ -19,49 +19,41 @@ #ifndef OUTPUT_FILE_NAME_GENERATOR_H_ #define OUTPUT_FILE_NAME_GENERATOR_H_ -#include "FileNameDisambiguator.h" -#include "intrusive_ptr.h" #include #include +#include "FileNameDisambiguator.h" +#include "intrusive_ptr.h" class PageId; class AbstractRelinker; class OutputFileNameGenerator { - // Member-wise copying is OK. -public: - OutputFileNameGenerator(); + // Member-wise copying is OK. + public: + OutputFileNameGenerator(); - OutputFileNameGenerator(intrusive_ptr disambiguator, - const QString& out_dir, - Qt::LayoutDirection layout_direction); + OutputFileNameGenerator(intrusive_ptr disambiguator, + const QString& out_dir, + Qt::LayoutDirection layout_direction); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - Qt::LayoutDirection layoutDirection() const { - return m_layoutDirection; - } + Qt::LayoutDirection layoutDirection() const { return m_layoutDirection; } - const QString& outDir() const { - return m_outDir; - } + const QString& outDir() const { return m_outDir; } - FileNameDisambiguator* disambiguator() { - return m_ptrDisambiguator.get(); - } + FileNameDisambiguator* disambiguator() { return m_disambiguator.get(); } - const FileNameDisambiguator* disambiguator() const { - return m_ptrDisambiguator.get(); - } + const FileNameDisambiguator* disambiguator() const { return m_disambiguator.get(); } - QString fileNameFor(const PageId& page) const; + QString fileNameFor(const PageId& page) const; - QString filePathFor(const PageId& page) const; + QString filePathFor(const PageId& page) const; -private: - intrusive_ptr m_ptrDisambiguator; - QString m_outDir; - Qt::LayoutDirection m_layoutDirection; + private: + intrusive_ptr m_disambiguator; + QString m_outDir; + Qt::LayoutDirection m_layoutDirection; }; diff --git a/PageId.cpp b/PageId.cpp index af80bc5f5..35c30c3f8 100644 --- a/PageId.cpp +++ b/PageId.cpp @@ -19,67 +19,65 @@ #include "PageId.h" #include -PageId::PageId() : m_subPage(SINGLE_PAGE) { -} +PageId::PageId() : m_subPage(SINGLE_PAGE) {} -PageId::PageId(const ImageId& image_id, SubPage subpage) : m_imageId(image_id), m_subPage(subpage) { -} +PageId::PageId(const ImageId& image_id, SubPage subpage) : m_imageId(image_id), m_subPage(subpage) {} QString PageId::subPageToString(const SubPage sub_page) { - const char* str = nullptr; - - switch (sub_page) { - case SINGLE_PAGE: - str = "single"; - break; - case LEFT_PAGE: - str = "left"; - break; - case RIGHT_PAGE: - str = "right"; - break; - } - - assert(str); - - return QString::fromLatin1(str); + const char* str = nullptr; + + switch (sub_page) { + case SINGLE_PAGE: + str = "single"; + break; + case LEFT_PAGE: + str = "left"; + break; + case RIGHT_PAGE: + str = "right"; + break; + } + + assert(str); + + return QString::fromLatin1(str); } PageId::SubPage PageId::subPageFromString(const QString& string, bool* ok) { - bool recognized = true; - SubPage sub_page = SINGLE_PAGE; - - if (string == "single") { - sub_page = SINGLE_PAGE; - } else if (string == "left") { - sub_page = LEFT_PAGE; - } else if (string == "right") { - sub_page = RIGHT_PAGE; - } else { - recognized = false; - } - - if (ok) { - *ok = recognized; - } - - return sub_page; + bool recognized = true; + SubPage sub_page = SINGLE_PAGE; + + if (string == "single") { + sub_page = SINGLE_PAGE; + } else if (string == "left") { + sub_page = LEFT_PAGE; + } else if (string == "right") { + sub_page = RIGHT_PAGE; + } else { + recognized = false; + } + + if (ok) { + *ok = recognized; + } + + return sub_page; } bool operator==(const PageId& lhs, const PageId& rhs) { - return ((lhs.subPage() == rhs.subPage()) && (lhs.imageId() == rhs.imageId())); + return ((lhs.subPage() == rhs.subPage()) && (lhs.imageId() == rhs.imageId())); } bool operator!=(const PageId& lhs, const PageId& rhs) { - return !(lhs == rhs); + return !(lhs == rhs); } bool operator<(const PageId& lhs, const PageId& rhs) { - if (lhs.imageId() < rhs.imageId()) { - return true; - } else if (rhs.imageId() < lhs.imageId()) { - return false; - } else { - return lhs.subPage() < rhs.subPage(); - } + if (lhs.imageId() < rhs.imageId()) { + return true; + } else if (rhs.imageId() < lhs.imageId()) { + return false; + } else { + return lhs.subPage() < rhs.subPage(); + } } diff --git a/PageId.h b/PageId.h index 4cd18de76..afb45e01a 100644 --- a/PageId.h +++ b/PageId.h @@ -29,47 +29,37 @@ class QString; * An image can contain one or two logical pages. */ class PageId { - // Member-wise copying is OK. -public: - enum SubPage { SINGLE_PAGE, LEFT_PAGE, RIGHT_PAGE }; + // Member-wise copying is OK. + public: + enum SubPage { SINGLE_PAGE, LEFT_PAGE, RIGHT_PAGE }; - PageId(); + PageId(); - /** - * \note The default parameter for subpage is not arbitrary. It has to - * preceed other values in terms of operator<(). That's necessary - * to be able to use lower_bound() to find the first page with - * a matching image id. - */ - explicit PageId(const ImageId& image_id, SubPage subpage = SINGLE_PAGE); + /** + * \note The default parameter for subpage is not arbitrary. It has to + * preceed other values in terms of operator<(). That's necessary + * to be able to use lower_bound() to find the first page with + * a matching image id. + */ + explicit PageId(const ImageId& image_id, SubPage subpage = SINGLE_PAGE); - bool isNull() const { - return m_imageId.isNull(); - } + bool isNull() const { return m_imageId.isNull(); } - ImageId& imageId() { - return m_imageId; - } + ImageId& imageId() { return m_imageId; } - const ImageId& imageId() const { - return m_imageId; - } + const ImageId& imageId() const { return m_imageId; } - SubPage subPage() const { - return m_subPage; - } + SubPage subPage() const { return m_subPage; } - QString subPageAsString() const { - return subPageToString(m_subPage); - } + QString subPageAsString() const { return subPageToString(m_subPage); } - static QString subPageToString(SubPage sub_page); + static QString subPageToString(SubPage sub_page); - static SubPage subPageFromString(const QString& string, bool* ok = nullptr); + static SubPage subPageFromString(const QString& string, bool* ok = nullptr); -private: - ImageId m_imageId; - SubPage m_subPage; + private: + ImageId m_imageId; + SubPage m_subPage; }; @@ -80,11 +70,11 @@ bool operator!=(const PageId& lhs, const PageId& rhs); bool operator<(const PageId& lhs, const PageId& rhs); namespace std { -template<> +template <> struct hash { - size_t operator()(const PageId& pageId) const noexcept { - return (hash()(pageId.imageId()) ^ hash()(pageId.subPage()) << 1); - } + size_t operator()(const PageId& pageId) const noexcept { + return (hash()(pageId.imageId()) ^ hash()(pageId.subPage()) << 1); + } }; } // namespace std diff --git a/PageInfo.cpp b/PageInfo.cpp index e4ab7466b..e10ad1314 100644 --- a/PageInfo.cpp +++ b/PageInfo.cpp @@ -18,17 +18,15 @@ #include "PageInfo.h" -PageInfo::PageInfo() : m_imageSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) { -} +PageInfo::PageInfo() : m_imageSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) {} PageInfo::PageInfo(const PageId& page_id, const ImageMetadata& metadata, int image_sub_pages, bool left_half_removed, bool right_half_removed) - : m_pageId(page_id), - m_metadata(metadata), - m_imageSubPages(image_sub_pages), - m_leftHalfRemoved(left_half_removed), - m_rightHalfRemoved(right_half_removed) { -} + : m_pageId(page_id), + m_metadata(metadata), + m_imageSubPages(image_sub_pages), + m_leftHalfRemoved(left_half_removed), + m_rightHalfRemoved(right_half_removed) {} diff --git a/PageInfo.h b/PageInfo.h index 82c6f1516..9df52262d 100644 --- a/PageInfo.h +++ b/PageInfo.h @@ -19,58 +19,42 @@ #ifndef PAGEINFO_H_ #define PAGEINFO_H_ -#include "PageId.h" #include "ImageMetadata.h" +#include "PageId.h" class PageInfo { - // Member-wise copying is OK. -public: - PageInfo(); - - PageInfo(const PageId& page_id, - const ImageMetadata& metadata, - int image_sub_pages, - bool left_half_removed, - bool right_half_removed); - - bool isNull() const { - return m_pageId.isNull(); - } - - const PageId& id() const { - return m_pageId; - } - - void setId(const PageId& id) { - m_pageId = id; - } - - const ImageId& imageId() const { - return m_pageId.imageId(); - } - - const ImageMetadata& metadata() const { - return m_metadata; - } - - int imageSubPages() const { - return m_imageSubPages; - } - - bool leftHalfRemoved() const { - return m_leftHalfRemoved; - } - - bool rightHalfRemoved() const { - return m_rightHalfRemoved; - } - -private: - PageId m_pageId; - ImageMetadata m_metadata; - int m_imageSubPages; - bool m_leftHalfRemoved; - bool m_rightHalfRemoved; + // Member-wise copying is OK. + public: + PageInfo(); + + PageInfo(const PageId& page_id, + const ImageMetadata& metadata, + int image_sub_pages, + bool left_half_removed, + bool right_half_removed); + + bool isNull() const { return m_pageId.isNull(); } + + const PageId& id() const { return m_pageId; } + + void setId(const PageId& id) { m_pageId = id; } + + const ImageId& imageId() const { return m_pageId.imageId(); } + + const ImageMetadata& metadata() const { return m_metadata; } + + int imageSubPages() const { return m_imageSubPages; } + + bool leftHalfRemoved() const { return m_leftHalfRemoved; } + + bool rightHalfRemoved() const { return m_rightHalfRemoved; } + + private: + PageId m_pageId; + ImageMetadata m_metadata; + int m_imageSubPages; + bool m_leftHalfRemoved; + bool m_rightHalfRemoved; }; diff --git a/PageOrderOption.h b/PageOrderOption.h index 3f9c567b1..005dc276c 100644 --- a/PageOrderOption.h +++ b/PageOrderOption.h @@ -19,33 +19,28 @@ #ifndef PAGE_ORDER_OPTION_H_ #define PAGE_ORDER_OPTION_H_ -#include "intrusive_ptr.h" -#include "PageOrderProvider.h" #include +#include "PageOrderProvider.h" +#include "intrusive_ptr.h" class PageOrderOption { - // Member-wise copying is OK. -public: - typedef intrusive_ptr ProviderPtr; - - PageOrderOption(const QString& name, ProviderPtr provider) : m_name(name), m_ptrProvider(std::move(provider)) { - } - - const QString& name() const { - return m_name; - } - - /** - * Returns the ordering information provider. - * A null provider is OK and is to be interpreted as default order. - */ - const ProviderPtr& provider() const { - return m_ptrProvider; - } - -private: - QString m_name; - ProviderPtr m_ptrProvider; + // Member-wise copying is OK. + public: + typedef intrusive_ptr ProviderPtr; + + PageOrderOption(const QString& name, ProviderPtr provider) : m_name(name), m_provider(std::move(provider)) {} + + const QString& name() const { return m_name; } + + /** + * Returns the ordering information provider. + * A null provider is OK and is to be interpreted as default order. + */ + const ProviderPtr& provider() const { return m_provider; } + + private: + QString m_name; + ProviderPtr m_provider; }; diff --git a/PageOrderProvider.h b/PageOrderProvider.h index 9ce98dbf8..d3bcbf179 100644 --- a/PageOrderProvider.h +++ b/PageOrderProvider.h @@ -27,16 +27,16 @@ class PageId; * A base class for different page ordering strategies. */ class PageOrderProvider : public ref_countable { -public: - /** - * Returns true if \p lhs_page precedes \p rhs_page. - * \p lhs_incomplete and \p rhs_incomplete indicate whether - * a page is represented by IncompleteThumbnail. - */ - virtual bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const = 0; + public: + /** + * Returns true if \p lhs_page precedes \p rhs_page. + * \p lhs_incomplete and \p rhs_incomplete indicate whether + * a page is represented by IncompleteThumbnail. + */ + virtual bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const = 0; }; diff --git a/PageOrientationPropagator.cpp b/PageOrientationPropagator.cpp index b67e8e377..8e02096f3 100644 --- a/PageOrientationPropagator.cpp +++ b/PageOrientationPropagator.cpp @@ -21,39 +21,34 @@ #include #include "CompositeCacheDrivenTask.h" #include "OrthogonalRotation.h" -#include "ProjectPages.h" #include "PageSequence.h" -#include "filters/page_split/Filter.h" +#include "ProjectPages.h" #include "filter_dc/PageOrientationCollector.h" +#include "filters/page_split/Filter.h" class PageOrientationPropagator::Collector : public PageOrientationCollector { -public: - void process(const OrthogonalRotation& orientation) override { - m_orientation = orientation; - } + public: + void process(const OrthogonalRotation& orientation) override { m_orientation = orientation; } - const OrthogonalRotation& orientation() const { - return m_orientation; - } + const OrthogonalRotation& orientation() const { return m_orientation; } -private: - OrthogonalRotation m_orientation; + private: + OrthogonalRotation m_orientation; }; PageOrientationPropagator::PageOrientationPropagator(intrusive_ptr page_split_filter, intrusive_ptr task) - : m_ptrPageSplitFilter(std::move(page_split_filter)), m_ptrTask(std::move(task)) { -} + : m_pageSplitFilter(std::move(page_split_filter)), m_task(std::move(task)) {} PageOrientationPropagator::~PageOrientationPropagator() = default; void PageOrientationPropagator::propagate(const ProjectPages& pages) { - const PageSequence sequence(pages.toPageSequence(PAGE_VIEW)); + const PageSequence sequence(pages.toPageSequence(PAGE_VIEW)); - for (const PageInfo& page_info : sequence) { - Collector collector; - m_ptrTask->process(page_info, &collector); - m_ptrPageSplitFilter->pageOrientationUpdate(page_info.imageId(), collector.orientation()); - } + for (const PageInfo& page_info : sequence) { + Collector collector; + m_task->process(page_info, &collector); + m_pageSplitFilter->pageOrientationUpdate(page_info.imageId(), collector.orientation()); + } } diff --git a/PageOrientationPropagator.h b/PageOrientationPropagator.h index 46ac7fdbc..f29820e0f 100644 --- a/PageOrientationPropagator.h +++ b/PageOrientationPropagator.h @@ -19,8 +19,8 @@ #ifndef PAGE_ORIENTATION_PROPAGATOR_H_ #define PAGE_ORIENTATION_PROPAGATOR_H_ -#include "intrusive_ptr.h" #include +#include "intrusive_ptr.h" class CompositeCacheDrivenTask; class ProjectPages; @@ -39,19 +39,19 @@ class Filter; * answer, while "Fix Orientation" provides a hint. */ class PageOrientationPropagator { -public: - PageOrientationPropagator(intrusive_ptr page_split_filter, - intrusive_ptr task); + public: + PageOrientationPropagator(intrusive_ptr page_split_filter, + intrusive_ptr task); - ~PageOrientationPropagator(); + ~PageOrientationPropagator(); - void propagate(const ProjectPages& pages); + void propagate(const ProjectPages& pages); -private: - class Collector; + private: + class Collector; - intrusive_ptr m_ptrPageSplitFilter; - intrusive_ptr m_ptrTask; + intrusive_ptr m_pageSplitFilter; + intrusive_ptr m_task; }; diff --git a/PageRange.cpp b/PageRange.cpp index 043860c15..d289793ec 100644 --- a/PageRange.cpp +++ b/PageRange.cpp @@ -19,25 +19,25 @@ #include "PageRange.h" std::set PageRange::selectEveryOther(const PageId& base) const { - std::set selection; - - auto it(pages.begin()); - const auto end(pages.end()); - for (; it != end && *it != base; ++it) { - // Continue until we have a match. - } - if (it == end) { - return selection; - } + std::set selection; + + auto it(pages.begin()); + const auto end(pages.end()); + for (; it != end && *it != base; ++it) { + // Continue until we have a match. + } + if (it == end) { + return selection; + } - const int base_idx = static_cast(it - pages.begin()); - int idx = 0; - for (const PageId& page_id : pages) { - if (((idx - base_idx) & 1) == 0) { - selection.insert(page_id); - } - ++idx; + const int base_idx = static_cast(it - pages.begin()); + int idx = 0; + for (const PageId& page_id : pages) { + if (((idx - base_idx) & 1) == 0) { + selection.insert(page_id); } + ++idx; + } - return selection; + return selection; } diff --git a/PageRange.h b/PageRange.h index 86fbc9c66..057bf727c 100644 --- a/PageRange.h +++ b/PageRange.h @@ -19,18 +19,18 @@ #ifndef PAGE_RANGE_H_ #define PAGE_RANGE_H_ -#include "PageId.h" -#include #include +#include +#include "PageId.h" class PageRange { -public: - /** - * \brief Ordered list of consecutive pages. - */ - std::vector pages; + public: + /** + * \brief Ordered list of consecutive pages. + */ + std::vector pages; - std::set selectEveryOther(const PageId& base) const; + std::set selectEveryOther(const PageId& base) const; }; diff --git a/PageSelectionAccessor.cpp b/PageSelectionAccessor.cpp index bd8037532..520c56b74 100644 --- a/PageSelectionAccessor.cpp +++ b/PageSelectionAccessor.cpp @@ -22,17 +22,16 @@ #include "PageSequence.h" PageSelectionAccessor::PageSelectionAccessor(intrusive_ptr provider) - : m_ptrProvider(std::move(provider)) { -} + : m_provider(std::move(provider)) {} PageSequence PageSelectionAccessor::allPages() const { - return m_ptrProvider->allPages(); + return m_provider->allPages(); } std::set PageSelectionAccessor::selectedPages() const { - return m_ptrProvider->selectedPages(); + return m_provider->selectedPages(); } std::vector PageSelectionAccessor::selectedRanges() const { - return m_ptrProvider->selectedRanges(); + return m_provider->selectedRanges(); } diff --git a/PageSelectionAccessor.h b/PageSelectionAccessor.h index 8c0163ca0..d4d03e4d0 100644 --- a/PageSelectionAccessor.h +++ b/PageSelectionAccessor.h @@ -19,28 +19,28 @@ #ifndef PAGE_SELECTION_ACCESSOR_H_ #define PAGE_SELECTION_ACCESSOR_H_ -#include "PageSelectionProvider.h" +#include +#include #include "PageId.h" #include "PageRange.h" +#include "PageSelectionProvider.h" #include "intrusive_ptr.h" -#include -#include class PageSequence; class PageSelectionAccessor { - // Member-wise copying is OK. -public: - explicit PageSelectionAccessor(intrusive_ptr provider); + // Member-wise copying is OK. + public: + explicit PageSelectionAccessor(intrusive_ptr provider); - PageSequence allPages() const; + PageSequence allPages() const; - std::set selectedPages() const; + std::set selectedPages() const; - std::vector selectedRanges() const; + std::vector selectedRanges() const; -private: - intrusive_ptr m_ptrProvider; + private: + intrusive_ptr m_provider; }; diff --git a/PageSelectionProvider.h b/PageSelectionProvider.h index 56653aa64..5395e2c67 100644 --- a/PageSelectionProvider.h +++ b/PageSelectionProvider.h @@ -19,21 +19,21 @@ #ifndef PAGE_SELECTION_PROVIDER_H_ #define PAGE_SELECTION_PROVIDER_H_ -#include "ref_countable.h" #include #include +#include "ref_countable.h" class PageSequence; class PageId; class PageRange; class PageSelectionProvider : public ref_countable { -public: - virtual PageSequence allPages() const = 0; + public: + virtual PageSequence allPages() const = 0; - virtual std::set selectedPages() const = 0; + virtual std::set selectedPages() const = 0; - virtual std::vector selectedRanges() const = 0; + virtual std::vector selectedRanges() const = 0; }; diff --git a/PageSequence.cpp b/PageSequence.cpp index f136a1c7b..d23dd586f 100644 --- a/PageSequence.cpp +++ b/PageSequence.cpp @@ -19,75 +19,75 @@ #include "PageSequence.h" void PageSequence::append(const PageInfo& page_info) { - m_pages.push_back(page_info); + m_pages.push_back(page_info); } const PageInfo& PageSequence::pageAt(const size_t idx) const { - return m_pages.at(idx); // may throw + return m_pages.at(idx); // may throw } const PageInfo& PageSequence::pageAt(const PageId page) const { - auto it(m_pages.begin()); - const auto end(m_pages.end()); - for (; it != end && it->id() != page; ++it) { - } - return *it; + auto it(m_pages.begin()); + const auto end(m_pages.end()); + for (; it != end && it->id() != page; ++it) { + } + return *it; } int PageSequence::pageNo(const PageId& page) const { - auto it(m_pages.begin()); - const auto end(m_pages.end()); - int res = 0; - for (; it != end && it->id() != page; ++it, ++res) { - } - return (it == end) ? -1 : res; + auto it(m_pages.begin()); + const auto end(m_pages.end()); + int res = 0; + for (; it != end && it->id() != page; ++it, ++res) { + } + return (it == end) ? -1 : res; } std::set PageSequence::selectAll() const { - std::set selection; + std::set selection; - for (const PageInfo& page_info : m_pages) { - selection.insert(page_info.id()); - } + for (const PageInfo& page_info : m_pages) { + selection.insert(page_info.id()); + } - return selection; + return selection; } std::set PageSequence::selectPagePlusFollowers(const PageId& page) const { - std::set selection; - - auto it(m_pages.begin()); - const auto end(m_pages.end()); - for (; it != end && it->id() != page; ++it) { - // Continue until we have a match. - } - for (; it != end; ++it) { - selection.insert(it->id()); - } - - return selection; + std::set selection; + + auto it(m_pages.begin()); + const auto end(m_pages.end()); + for (; it != end && it->id() != page; ++it) { + // Continue until we have a match. + } + for (; it != end; ++it) { + selection.insert(it->id()); + } + + return selection; } std::set PageSequence::selectEveryOther(const PageId& base) const { - std::set selection; - - auto it(m_pages.begin()); - const auto end(m_pages.end()); - for (; it != end && it->id() != base; ++it) { - // Continue until we have a match. - } - if (it == end) { - return selection; - } + std::set selection; + + auto it(m_pages.begin()); + const auto end(m_pages.end()); + for (; it != end && it->id() != base; ++it) { + // Continue until we have a match. + } + if (it == end) { + return selection; + } - const int base_idx = static_cast(it - m_pages.begin()); - int idx = 0; - for (const PageInfo& page_info : m_pages) { - if (((idx - base_idx) & 1) == 0) { - selection.insert(page_info.id()); - } - ++idx; + const int base_idx = static_cast(it - m_pages.begin()); + int idx = 0; + for (const PageInfo& page_info : m_pages) { + if (((idx - base_idx) & 1) == 0) { + selection.insert(page_info.id()); } + ++idx; + } - return selection; + return selection; } diff --git a/PageSequence.h b/PageSequence.h index 5991f681b..cc0168354 100644 --- a/PageSequence.h +++ b/PageSequence.h @@ -19,50 +19,40 @@ #ifndef PAGE_SEQUENCE_H_ #define PAGE_SEQUENCE_H_ -#include "PageInfo.h" -#include -#include #include +#include +#include +#include "PageInfo.h" class PageSequence { - // Member-wise copying is OK. -public: - void append(const PageInfo& page_info); + // Member-wise copying is OK. + public: + void append(const PageInfo& page_info); - size_t numPages() const { - return m_pages.size(); - } + size_t numPages() const { return m_pages.size(); } - const PageInfo& pageAt(PageId page) const; + const PageInfo& pageAt(PageId page) const; - const PageInfo& pageAt(size_t idx) const; + const PageInfo& pageAt(size_t idx) const; - int pageNo(const PageId& page) const; + int pageNo(const PageId& page) const; - std::set selectAll() const; + std::set selectAll() const; - std::set selectPagePlusFollowers(const PageId& page) const; + std::set selectPagePlusFollowers(const PageId& page) const; - std::set selectEveryOther(const PageId& base) const; + std::set selectEveryOther(const PageId& base) const; - std::vector::iterator begin() { - return m_pages.begin(); - } + std::vector::iterator begin() { return m_pages.begin(); } - std::vector::iterator end() { - return m_pages.end(); - } + std::vector::iterator end() { return m_pages.end(); } - std::vector::const_iterator begin() const { - return m_pages.cbegin(); - } + std::vector::const_iterator begin() const { return m_pages.cbegin(); } - std::vector::const_iterator end() const { - return m_pages.cend(); - } + std::vector::const_iterator end() const { return m_pages.cend(); } -private: - std::vector m_pages; + private: + std::vector m_pages; }; diff --git a/PayloadEvent.h b/PayloadEvent.h index 874fd33b8..a41e98ca8 100644 --- a/PayloadEvent.h +++ b/PayloadEvent.h @@ -21,22 +21,17 @@ #include -template +template class PayloadEvent : public QEvent { -public: - explicit PayloadEvent(const T& payload) : QEvent(User), m_payload(payload) { - } + public: + explicit PayloadEvent(const T& payload) : QEvent(User), m_payload(payload) {} - const T& payload() const { - return m_payload; - } + const T& payload() const { return m_payload; } - T& payload() { - return m_payload; - } + T& payload() { return m_payload; } -private: - T m_payload; + private: + T m_payload; }; diff --git a/PixmapRenderer.cpp b/PixmapRenderer.cpp index f1a4c2890..605f7e743 100644 --- a/PixmapRenderer.cpp +++ b/PixmapRenderer.cpp @@ -19,13 +19,13 @@ #include "PixmapRenderer.h" #include #include -#include #include #include +#include void PixmapRenderer::drawPixmap(QPainter& painter, const QPixmap& pixmap) { - const QTransform inv_transform(painter.worldTransform().inverted()); - const QRectF src_rect(inv_transform.map(QRectF(painter.viewport())).boundingRect()); - const QRectF bounded_src_rect(src_rect.intersected(pixmap.rect())); - painter.drawPixmap(bounded_src_rect, pixmap, bounded_src_rect); + const QTransform inv_transform(painter.worldTransform().inverted()); + const QRectF src_rect(inv_transform.map(QRectF(painter.viewport())).boundingRect()); + const QRectF bounded_src_rect(src_rect.intersected(pixmap.rect())); + painter.drawPixmap(bounded_src_rect, pixmap, bounded_src_rect); } diff --git a/PixmapRenderer.h b/PixmapRenderer.h index 31aa74321..6e6730610 100644 --- a/PixmapRenderer.h +++ b/PixmapRenderer.h @@ -23,20 +23,20 @@ class QPainter; class QPixmap; class PixmapRenderer { -public: - /** - * \brief Workarounds an issue with QPainter::drawPixmap(). - * - * This method is more or less equivalent to: - * \code - * QPainter::drawPixmap(0, 0, pixmap); - * \endcode - * However, Qt's raster paint engine for some reason refuses to draw - * the pixmap at all if a very strong zoom is applied (as of Qt 5.4.0). - * This method solves the problem above by calculating the visible area - * of the pixmap and communicating that information to QPainter. - */ - static void drawPixmap(QPainter& painter, const QPixmap& pixmap); + public: + /** + * \brief Workarounds an issue with QPainter::drawPixmap(). + * + * This method is more or less equivalent to: + * \code + * QPainter::drawPixmap(0, 0, pixmap); + * \endcode + * However, Qt's raster paint engine for some reason refuses to draw + * the pixmap at all if a very strong zoom is applied (as of Qt 5.4.0). + * This method solves the problem above by calculating the visible area + * of the pixmap and communicating that information to QPainter. + */ + static void drawPixmap(QPainter& painter, const QPixmap& pixmap); }; diff --git a/PngMetadataLoader.cpp b/PngMetadataLoader.cpp index dc8bf61d9..046f3f2de 100644 --- a/PngMetadataLoader.cpp +++ b/PngMetadataLoader.cpp @@ -17,109 +17,105 @@ */ #include "PngMetadataLoader.h" +#include +#include +#include "Dpm.h" #include "ImageMetadata.h" #include "NonCopyable.h" -#include "Dpm.h" -#include -#include namespace { class PngHandle { - DECLARE_NON_COPYABLE(PngHandle) + DECLARE_NON_COPYABLE(PngHandle) -public: - PngHandle(); + public: + PngHandle(); - ~PngHandle(); + ~PngHandle(); - png_structp handle() const { - return m_pPng; - } + png_structp handle() const { return m_png; } - png_infop info() const { - return m_pInfo; - } + png_infop info() const { return m_info; } -private: - png_structp m_pPng; - png_infop m_pInfo; + private: + png_structp m_png; + png_infop m_info; }; PngHandle::PngHandle() { - m_pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!m_pPng) { - throw std::bad_alloc(); - } - m_pInfo = png_create_info_struct(m_pPng); - if (!m_pInfo) { - throw std::bad_alloc(); - } + m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!m_png) { + throw std::bad_alloc(); + } + m_info = png_create_info_struct(m_png); + if (!m_info) { + throw std::bad_alloc(); + } } PngHandle::~PngHandle() { - png_destroy_read_struct(&m_pPng, &m_pInfo, nullptr); + png_destroy_read_struct(&m_png, &m_info, nullptr); } } // namespace static void readFn(png_structp png_ptr, png_bytep data, png_size_t length) { - auto* io_device = (QIODevice*) png_get_io_ptr(png_ptr); - while (length > 0) { - const qint64 read = io_device->read((char*) data, length); - if (read <= 0) { - png_error(png_ptr, "Read Error"); - - return; - } - length -= read; + auto* io_device = (QIODevice*) png_get_io_ptr(png_ptr); + while (length > 0) { + const qint64 read = io_device->read((char*) data, length); + if (read <= 0) { + png_error(png_ptr, "Read Error"); + + return; } + length -= read; + } } void PngMetadataLoader::registerMyself() { - static bool registered = false; - if (!registered) { - ImageMetadataLoader::registerLoader(intrusive_ptr(new PngMetadataLoader)); - registered = true; - } + static bool registered = false; + if (!registered) { + ImageMetadataLoader::registerLoader(make_intrusive()); + registered = true; + } } ImageMetadataLoader::Status PngMetadataLoader::loadMetadata(QIODevice& io_device, const VirtualFunction& out) { - if (!io_device.isReadable()) { - return GENERIC_ERROR; - } - - png_byte signature[8]; - if (io_device.peek((char*) signature, 8) != 8) { - return FORMAT_NOT_RECOGNIZED; - } - - if (png_sig_cmp(signature, 0, sizeof(signature)) != 0) { - return FORMAT_NOT_RECOGNIZED; - } - - PngHandle png; - - if (setjmp(png_jmpbuf(png.handle()))) { - return GENERIC_ERROR; - } - - png_set_read_fn(png.handle(), &io_device, &readFn); - png_read_info(png.handle(), png.info()); - - QSize size; - Dpi dpi; - size.setWidth(png_get_image_width(png.handle(), png.info())); - size.setHeight(png_get_image_height(png.handle(), png.info())); - png_uint_32 res_x, res_y; - int unit_type; - if (png_get_pHYs(png.handle(), png.info(), &res_x, &res_y, &unit_type)) { - if (unit_type == PNG_RESOLUTION_METER) { - dpi = Dpm(res_x, res_y); - } + if (!io_device.isReadable()) { + return GENERIC_ERROR; + } + + png_byte signature[8]; + if (io_device.peek((char*) signature, 8) != 8) { + return FORMAT_NOT_RECOGNIZED; + } + + if (png_sig_cmp(signature, 0, sizeof(signature)) != 0) { + return FORMAT_NOT_RECOGNIZED; + } + + PngHandle png; + + if (setjmp(png_jmpbuf(png.handle()))) { + return GENERIC_ERROR; + } + + png_set_read_fn(png.handle(), &io_device, &readFn); + png_read_info(png.handle(), png.info()); + + QSize size; + Dpi dpi; + size.setWidth(png_get_image_width(png.handle(), png.info())); + size.setHeight(png_get_image_height(png.handle(), png.info())); + png_uint_32 res_x, res_y; + int unit_type; + if (png_get_pHYs(png.handle(), png.info(), &res_x, &res_y, &unit_type)) { + if (unit_type == PNG_RESOLUTION_METER) { + dpi = Dpm(res_x, res_y); } + } - out(ImageMetadata(size, dpi)); + out(ImageMetadata(size, dpi)); - return LOADED; + return LOADED; } // PngMetadataLoader::loadMetadata diff --git a/PngMetadataLoader.h b/PngMetadataLoader.h index 1413c92f2..938ca1f82 100644 --- a/PngMetadataLoader.h +++ b/PngMetadataLoader.h @@ -19,25 +19,25 @@ #ifndef PNGMETADATALOADER_H_ #define PNGMETADATALOADER_H_ +#include #include "ImageMetadataLoader.h" #include "VirtualFunction.h" -#include class QIODevice; class ImageMetadata; class PngMetadataLoader : public ImageMetadataLoader { -public: - /** - * \brief Register this loader in the global registry. - * - * The same restrictions apply here as for - * ImageMetadataLoader::registerLoader() - */ - static void registerMyself(); - -protected: - Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; + public: + /** + * \brief Register this loader in the global registry. + * + * The same restrictions apply here as for + * ImageMetadataLoader::registerLoader() + */ + static void registerMyself(); + + protected: + Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; }; diff --git a/ProcessingIndicationWidget.cpp b/ProcessingIndicationWidget.cpp index f91c79655..ee7b758d0 100644 --- a/ProcessingIndicationWidget.cpp +++ b/ProcessingIndicationWidget.cpp @@ -17,11 +17,11 @@ */ #include "ProcessingIndicationWidget.h" -#include "imageproc/ColorInterpolation.h" -#include "ColorSchemeManager.h" -#include #include #include +#include +#include "ColorSchemeManager.h" +#include "imageproc/ColorInterpolation.h" using namespace imageproc; @@ -29,70 +29,65 @@ static const double distinction_increase = 1.0 / 5.0; static const double distinction_decrease = -1.0 / 3.0; ProcessingIndicationWidget::ProcessingIndicationWidget(QWidget* parent) - : QWidget(parent), m_animation(10), m_distinction(1.0), m_distinctionDelta(distinction_increase), m_timerId(0) { - m_headColor = ColorSchemeManager::instance() - ->getColorParam("processing_indication_head_color", - palette().color(QPalette::Window).lighter(200)) - .color(); - m_tailColor = ColorSchemeManager::instance() - ->getColorParam("processing_indication_tail_color", - palette().color(QPalette::Window).lighter(130)) - .color(); + : QWidget(parent), m_animation(10), m_distinction(1.0), m_distinctionDelta(distinction_increase), m_timerId(0) { + m_headColor = ColorSchemeManager::instance()->getColorParam(ColorScheme::ProcessingIndicationHeadColor, + palette().color(QPalette::Window).darker(200)); + m_tailColor = ColorSchemeManager::instance()->getColorParam(ColorScheme::ProcessingIndicationTail, + palette().color(QPalette::Window).darker(130)); } void ProcessingIndicationWidget::resetAnimation() { - m_distinction = 1.0; - m_distinctionDelta = distinction_increase; + m_distinction = 1.0; + m_distinctionDelta = distinction_increase; } void ProcessingIndicationWidget::processingRestartedEffect() { - m_distinction = 1.0; - m_distinctionDelta = distinction_decrease; + m_distinction = 1.0; + m_distinctionDelta = distinction_decrease; } void ProcessingIndicationWidget::paintEvent(QPaintEvent* event) { - QRect animation_rect(animationRect()); - if (!event->rect().contains(animation_rect)) { - update(animation_rect); + QRect animation_rect(animationRect()); + if (!event->rect().contains(animation_rect)) { + update(animation_rect); - return; - } + return; + } - QColor head_color(colorInterpolation(m_tailColor, m_headColor, m_distinction)); + QColor head_color(colorInterpolation(m_tailColor, m_headColor, m_distinction)); - m_distinction += m_distinctionDelta; - if (m_distinction > 1.0) { - m_distinction = 1.0; - } else if (m_distinction <= 0.0) { - m_distinction = 0.0; - m_distinctionDelta = distinction_increase; - } + m_distinction += m_distinctionDelta; + if (m_distinction > 1.0) { + m_distinction = 1.0; + } else if (m_distinction <= 0.0) { + m_distinction = 0.0; + m_distinctionDelta = distinction_increase; + } - QPainter painter(this); + QPainter painter(this); - QColor fadeColor = ColorSchemeManager::instance() - ->getColorParam("processing_indication_fade_color", palette().background().color()) - .color(); - fadeColor.setAlpha(127); - painter.fillRect(rect(), fadeColor); + QColor fade_color = ColorSchemeManager::instance()->getColorParam(ColorScheme::ProcessingIndicationFade, + palette().background().color()); + fade_color.setAlpha(127); + painter.fillRect(rect(), fade_color); - m_animation.nextFrame(head_color, m_tailColor, &painter, animation_rect); + m_animation.nextFrame(head_color, m_tailColor, &painter, animation_rect); - if (m_timerId == 0) { - m_timerId = startTimer(180); - } + if (m_timerId == 0) { + m_timerId = startTimer(180); + } } void ProcessingIndicationWidget::timerEvent(QTimerEvent* event) { - killTimer(event->timerId()); - m_timerId = 0; - update(animationRect()); + killTimer(event->timerId()); + m_timerId = 0; + update(animationRect()); } QRect ProcessingIndicationWidget::animationRect() const { - QRect r(0, 0, 80, 80); - r.moveCenter(rect().center()); - r &= rect(); + QRect r(0, 0, 80, 80); + r.moveCenter(rect().center()); + r &= rect(); - return r; + return r; } diff --git a/ProcessingIndicationWidget.h b/ProcessingIndicationWidget.h index 983362a38..f72d60e63 100644 --- a/ProcessingIndicationWidget.h +++ b/ProcessingIndicationWidget.h @@ -19,9 +19,9 @@ #ifndef PROCESSING_INDICATION_WIDGET_H_ #define PROCESSING_INDICATION_WIDGET_H_ -#include "BubbleAnimation.h" -#include #include +#include +#include "BubbleAnimation.h" class QRect; @@ -30,34 +30,34 @@ class QRect; * when an image is being processed. */ class ProcessingIndicationWidget : public QWidget { -public: - explicit ProcessingIndicationWidget(QWidget* parent = nullptr); + public: + explicit ProcessingIndicationWidget(QWidget* parent = nullptr); - /** - * \brief Resets animation to the state it had just after - * constructing this object. - */ - void resetAnimation(); + /** + * \brief Resets animation to the state it had just after + * constructing this object. + */ + void resetAnimation(); - /** - * \brief Launch the "processing restarted" effect. - */ - void processingRestartedEffect(); + /** + * \brief Launch the "processing restarted" effect. + */ + void processingRestartedEffect(); -protected: - void paintEvent(QPaintEvent* event) override; + protected: + void paintEvent(QPaintEvent* event) override; - void timerEvent(QTimerEvent* event) override; + void timerEvent(QTimerEvent* event) override; -private: - QRect animationRect() const; + private: + QRect animationRect() const; - BubbleAnimation m_animation; - QColor m_headColor; - QColor m_tailColor; - double m_distinction; - double m_distinctionDelta; - int m_timerId; + BubbleAnimation m_animation; + QColor m_headColor; + QColor m_tailColor; + double m_distinction; + double m_distinctionDelta; + int m_timerId; }; diff --git a/ProcessingTaskQueue.cpp b/ProcessingTaskQueue.cpp index eb81078bd..434493be0 100644 --- a/ProcessingTaskQueue.cpp +++ b/ProcessingTaskQueue.cpp @@ -19,112 +19,111 @@ #include "ProcessingTaskQueue.h" ProcessingTaskQueue::Entry::Entry(const PageInfo& page_info, const BackgroundTaskPtr& tsk) - : pageInfo(page_info), task(tsk), takenForProcessing(false) { -} + : pageInfo(page_info), task(tsk), takenForProcessing(false) {} ProcessingTaskQueue::ProcessingTaskQueue() = default; void ProcessingTaskQueue::addProcessingTask(const PageInfo& page_info, const BackgroundTaskPtr& task) { - m_queue.emplace_back(page_info, task); - m_pageToSelectWhenDone = PageInfo(); + m_queue.emplace_back(page_info, task); + m_pageToSelectWhenDone = PageInfo(); } BackgroundTaskPtr ProcessingTaskQueue::takeForProcessing() { - for (Entry& ent : m_queue) { - if (!ent.takenForProcessing) { - ent.takenForProcessing = true; - - if (m_selectedPage.isNull()) { - // In this mode we select the most recently submitted for processing page. - // This means question marks on selected pages, but at least this avoids - // jumps caused by dynamic ordering. - m_selectedPage = ent.pageInfo; - } - - return ent.task; - } + for (Entry& ent : m_queue) { + if (!ent.takenForProcessing) { + ent.takenForProcessing = true; + + if (m_selectedPage.isNull()) { + // In this mode we select the most recently submitted for processing page. + // This means question marks on selected pages, but at least this avoids + // jumps caused by dynamic ordering. + m_selectedPage = ent.pageInfo; + } + + return ent.task; } + } - return nullptr; + return nullptr; } void ProcessingTaskQueue::processingFinished(const BackgroundTaskPtr& task) { - auto it(m_queue.begin()); - const auto end(m_queue.end()); - - for (;; ++it) { - if (it == end) { - // Task not found. - return; - } - - if (!it->takenForProcessing) { - // There is no point in looking further. - return; - } - - if (it->task == task) { - break; - } + auto it(m_queue.begin()); + const auto end(m_queue.end()); + + for (;; ++it) { + if (it == end) { + // Task not found. + return; } + if (!it->takenForProcessing) { + // There is no point in looking further. + return; + } - const bool removing_selected_page = (m_selectedPage.id() == it->pageInfo.id()); + if (it->task == task) { + break; + } + } - auto next_it(it); - ++next_it; - if ((next_it == end) && m_pageToSelectWhenDone.isNull()) { - m_pageToSelectWhenDone = it->pageInfo; - } + const bool removing_selected_page = (m_selectedPage.id() == it->pageInfo.id()); + + auto next_it(it); + ++next_it; + + if ((next_it == end) && m_pageToSelectWhenDone.isNull()) { + m_pageToSelectWhenDone = it->pageInfo; + } - m_queue.erase(it); + m_queue.erase(it); - if (removing_selected_page) { - if (!m_queue.empty()) { - m_selectedPage = m_queue.front().pageInfo; - } else if (!m_pageToSelectWhenDone.isNull()) { - m_selectedPage = m_pageToSelectWhenDone; - } + if (removing_selected_page) { + if (!m_queue.empty()) { + m_selectedPage = m_queue.front().pageInfo; + } else if (!m_pageToSelectWhenDone.isNull()) { + m_selectedPage = m_pageToSelectWhenDone; } + } } // ProcessingTaskQueue::processingFinished PageInfo ProcessingTaskQueue::selectedPage() const { - return m_selectedPage; + return m_selectedPage; } bool ProcessingTaskQueue::allProcessed() const { - return m_queue.empty(); + return m_queue.empty(); } void ProcessingTaskQueue::cancelAndRemove(const std::set& pages) { - auto it(m_queue.begin()); - const auto end(m_queue.end()); - while (it != end) { - if (pages.find(it->pageInfo.id()) == pages.end()) { - ++it; - } else { - if (it->takenForProcessing) { - it->task->cancel(); - } - - if (m_selectedPage.id() == it->pageInfo.id()) { - m_selectedPage = PageInfo(); - } - - - m_queue.erase(it++); - } + auto it(m_queue.begin()); + const auto end(m_queue.end()); + while (it != end) { + if (pages.find(it->pageInfo.id()) == pages.end()) { + ++it; + } else { + if (it->takenForProcessing) { + it->task->cancel(); + } + + if (m_selectedPage.id() == it->pageInfo.id()) { + m_selectedPage = PageInfo(); + } + + + m_queue.erase(it++); } + } } void ProcessingTaskQueue::cancelAndClear() { - while (!m_queue.empty()) { - Entry& ent = m_queue.front(); - if (ent.takenForProcessing) { - ent.task->cancel(); - } - m_queue.pop_front(); + while (!m_queue.empty()) { + Entry& ent = m_queue.front(); + if (ent.takenForProcessing) { + ent.task->cancel(); } - m_selectedPage = m_pageToSelectWhenDone; + m_queue.pop_front(); + } + m_selectedPage = m_pageToSelectWhenDone; } diff --git a/ProcessingTaskQueue.h b/ProcessingTaskQueue.h index 0c5848c8b..fae7af043 100644 --- a/ProcessingTaskQueue.h +++ b/ProcessingTaskQueue.h @@ -19,57 +19,57 @@ #ifndef PROCESSING_TASK_QUEUE_H_ #define PROCESSING_TASK_QUEUE_H_ -#include "NonCopyable.h" -#include "BackgroundTask.h" -#include "PageInfo.h" -#include "PageId.h" #include #include +#include "BackgroundTask.h" +#include "NonCopyable.h" +#include "PageId.h" +#include "PageInfo.h" class ProcessingTaskQueue { - DECLARE_NON_COPYABLE(ProcessingTaskQueue) + DECLARE_NON_COPYABLE(ProcessingTaskQueue) -public: - ProcessingTaskQueue(); + public: + ProcessingTaskQueue(); - void addProcessingTask(const PageInfo& page_info, const BackgroundTaskPtr& task); + void addProcessingTask(const PageInfo& page_info, const BackgroundTaskPtr& task); - /** - * The first task among those that haven't been already taken for processing - * is marked as taken and returned. A null task will be returned if there - * are no such tasks. - */ - BackgroundTaskPtr takeForProcessing(); + /** + * The first task among those that haven't been already taken for processing + * is marked as taken and returned. A null task will be returned if there + * are no such tasks. + */ + BackgroundTaskPtr takeForProcessing(); - void processingFinished(const BackgroundTaskPtr& task); + void processingFinished(const BackgroundTaskPtr& task); - /** - * \brief Returns the page to be visually selected. - * - * To be called after takeForProcessing() / processingFinished(). - * It may return a null PageInfo, meaning not to change whatever - * selection we currently have. - */ - PageInfo selectedPage() const; + /** + * \brief Returns the page to be visually selected. + * + * To be called after takeForProcessing() / processingFinished(). + * It may return a null PageInfo, meaning not to change whatever + * selection we currently have. + */ + PageInfo selectedPage() const; - bool allProcessed() const; + bool allProcessed() const; - void cancelAndRemove(const std::set& pages); + void cancelAndRemove(const std::set& pages); - void cancelAndClear(); + void cancelAndClear(); -private: - struct Entry { - PageInfo pageInfo; - BackgroundTaskPtr task; - bool takenForProcessing; + private: + struct Entry { + PageInfo pageInfo; + BackgroundTaskPtr task; + bool takenForProcessing; - Entry(const PageInfo& page_info, const BackgroundTaskPtr& task); - }; + Entry(const PageInfo& page_info, const BackgroundTaskPtr& task); + }; - std::list m_queue; - PageInfo m_selectedPage; - PageInfo m_pageToSelectWhenDone; + std::list m_queue; + PageInfo m_selectedPage; + PageInfo m_pageToSelectWhenDone; }; diff --git a/ProjectCreationContext.cpp b/ProjectCreationContext.cpp index a00dbc2db..316882242 100644 --- a/ProjectCreationContext.cpp +++ b/ProjectCreationContext.cpp @@ -16,85 +16,84 @@ along with this program. If not, see . */ -#include "ProjectFilesDialog.h" -#include "FixDpiDialog.h" #include "ProjectCreationContext.h" -#include #include +#include #include +#include "FixDpiDialog.h" +#include "ProjectFilesDialog.h" -ProjectCreationContext::ProjectCreationContext(QWidget* parent) - : m_layoutDirection(Qt::LeftToRight), m_pParent(parent) { - showProjectFilesDialog(); +ProjectCreationContext::ProjectCreationContext(QWidget* parent) : m_layoutDirection(Qt::LeftToRight), m_parent(parent) { + showProjectFilesDialog(); } ProjectCreationContext::~ProjectCreationContext() { - // Deleting a null pointer is OK. - delete m_ptrProjectFilesDialog; - delete m_ptrFixDpiDialog; + // Deleting a null pointer is OK. + delete m_projectFilesDialog; + delete m_fixDpiDialog; } namespace { -template +template bool allDpisOK(const T& container) { - using namespace boost::lambda; + using namespace boost::lambda; - return std::find_if(container.begin(), container.end(), !bind(&ImageFileInfo::isDpiOK, _1)) == container.end(); + return std::find_if(container.begin(), container.end(), !bind(&ImageFileInfo::isDpiOK, _1)) == container.end(); } } // anonymous namespace void ProjectCreationContext::projectFilesSubmitted() { - m_files = m_ptrProjectFilesDialog->inProjectFiles(); - m_outDir = m_ptrProjectFilesDialog->outputDirectory(); - m_layoutDirection = Qt::LeftToRight; - if (m_ptrProjectFilesDialog->isRtlLayout()) { - m_layoutDirection = Qt::RightToLeft; - } + m_files = m_projectFilesDialog->inProjectFiles(); + m_outDir = m_projectFilesDialog->outputDirectory(); + m_layoutDirection = Qt::LeftToRight; + if (m_projectFilesDialog->isRtlLayout()) { + m_layoutDirection = Qt::RightToLeft; + } - if (!m_ptrProjectFilesDialog->isDpiFixingForced() && allDpisOK(m_files)) { - emit done(this); - } else { - showFixDpiDialog(); - } + if (!m_projectFilesDialog->isDpiFixingForced() && allDpisOK(m_files)) { + emit done(this); + } else { + showFixDpiDialog(); + } } void ProjectCreationContext::projectFilesDialogDestroyed() { - if (!m_ptrFixDpiDialog) { - deleteLater(); - } + if (!m_fixDpiDialog) { + deleteLater(); + } } void ProjectCreationContext::fixedDpiSubmitted() { - m_files = m_ptrFixDpiDialog->files(); - emit done(this); + m_files = m_fixDpiDialog->files(); + emit done(this); } void ProjectCreationContext::fixDpiDialogDestroyed() { - deleteLater(); + deleteLater(); } void ProjectCreationContext::showProjectFilesDialog() { - assert(!m_ptrProjectFilesDialog); - m_ptrProjectFilesDialog = new ProjectFilesDialog(m_pParent); - m_ptrProjectFilesDialog->setAttribute(Qt::WA_DeleteOnClose); - m_ptrProjectFilesDialog->setAttribute(Qt::WA_QuitOnClose, false); - if (m_pParent) { - m_ptrProjectFilesDialog->setWindowModality(Qt::WindowModal); - } - connect(m_ptrProjectFilesDialog, SIGNAL(accepted()), this, SLOT(projectFilesSubmitted())); - connect(m_ptrProjectFilesDialog, SIGNAL(destroyed(QObject*)), this, SLOT(projectFilesDialogDestroyed())); - m_ptrProjectFilesDialog->show(); + assert(!m_projectFilesDialog); + m_projectFilesDialog = new ProjectFilesDialog(m_parent); + m_projectFilesDialog->setAttribute(Qt::WA_DeleteOnClose); + m_projectFilesDialog->setAttribute(Qt::WA_QuitOnClose, false); + if (m_parent) { + m_projectFilesDialog->setWindowModality(Qt::WindowModal); + } + connect(m_projectFilesDialog, SIGNAL(accepted()), this, SLOT(projectFilesSubmitted())); + connect(m_projectFilesDialog, SIGNAL(destroyed(QObject*)), this, SLOT(projectFilesDialogDestroyed())); + m_projectFilesDialog->show(); } void ProjectCreationContext::showFixDpiDialog() { - assert(!m_ptrFixDpiDialog); - m_ptrFixDpiDialog = new FixDpiDialog(m_files, m_pParent); - m_ptrFixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); - m_ptrFixDpiDialog->setAttribute(Qt::WA_QuitOnClose, false); - if (m_pParent) { - m_ptrFixDpiDialog->setWindowModality(Qt::WindowModal); - } - connect(m_ptrFixDpiDialog, SIGNAL(accepted()), this, SLOT(fixedDpiSubmitted())); - connect(m_ptrFixDpiDialog, SIGNAL(destroyed(QObject*)), this, SLOT(fixDpiDialogDestroyed())); - m_ptrFixDpiDialog->show(); + assert(!m_fixDpiDialog); + m_fixDpiDialog = new FixDpiDialog(m_files, m_parent); + m_fixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); + m_fixDpiDialog->setAttribute(Qt::WA_QuitOnClose, false); + if (m_parent) { + m_fixDpiDialog->setWindowModality(Qt::WindowModal); + } + connect(m_fixDpiDialog, SIGNAL(accepted()), this, SLOT(fixedDpiSubmitted())); + connect(m_fixDpiDialog, SIGNAL(destroyed(QObject*)), this, SLOT(fixDpiDialogDestroyed())); + m_fixDpiDialog->show(); } diff --git a/ProjectCreationContext.h b/ProjectCreationContext.h index b550e610b..6e1cc18fd 100644 --- a/ProjectCreationContext.h +++ b/ProjectCreationContext.h @@ -19,64 +19,58 @@ #ifndef PROJECTCREATIONCONTEXT_H_ #define PROJECTCREATIONCONTEXT_H_ -#include "NonCopyable.h" -#include "ImageFileInfo.h" #include #include #include #include #include +#include "ImageFileInfo.h" +#include "NonCopyable.h" class ProjectFilesDialog; class FixDpiDialog; class QWidget; class ProjectCreationContext : public QObject { - Q_OBJECT - DECLARE_NON_COPYABLE(ProjectCreationContext) + Q_OBJECT + DECLARE_NON_COPYABLE(ProjectCreationContext) -public: - explicit ProjectCreationContext(QWidget* parent); + public: + explicit ProjectCreationContext(QWidget* parent); - ~ProjectCreationContext() override; + ~ProjectCreationContext() override; - const std::vector& files() const { - return m_files; - } + const std::vector& files() const { return m_files; } - const QString& outDir() const { - return m_outDir; - } + const QString& outDir() const { return m_outDir; } - Qt::LayoutDirection layoutDirection() const { - return m_layoutDirection; - } + Qt::LayoutDirection layoutDirection() const { return m_layoutDirection; } -signals: + signals: - void done(ProjectCreationContext* context); + void done(ProjectCreationContext* context); -private slots: + private slots: - void projectFilesSubmitted(); + void projectFilesSubmitted(); - void projectFilesDialogDestroyed(); + void projectFilesDialogDestroyed(); - void fixedDpiSubmitted(); + void fixedDpiSubmitted(); - void fixDpiDialogDestroyed(); + void fixDpiDialogDestroyed(); -private: - void showProjectFilesDialog(); + private: + void showProjectFilesDialog(); - void showFixDpiDialog(); + void showFixDpiDialog(); - QPointer m_ptrProjectFilesDialog; - QPointer m_ptrFixDpiDialog; - QString m_outDir; - std::vector m_files; - Qt::LayoutDirection m_layoutDirection; - QWidget* m_pParent; + QPointer m_projectFilesDialog; + QPointer m_fixDpiDialog; + QString m_outDir; + std::vector m_files; + Qt::LayoutDirection m_layoutDirection; + QWidget* m_parent; }; diff --git a/ProjectFilesDialog.cpp b/ProjectFilesDialog.cpp index 51247cc45..aac53de5d 100644 --- a/ProjectFilesDialog.cpp +++ b/ProjectFilesDialog.cpp @@ -17,501 +17,479 @@ */ #include "ProjectFilesDialog.h" -#include "NonCopyable.h" -#include "ImageMetadataLoader.h" -#include "SmartFilenameOrdering.h" -#include #include #include #include +#include #include +#include "ImageMetadataLoader.h" +#include "NonCopyable.h" +#include "SmartFilenameOrdering.h" class ProjectFilesDialog::Item { -public: - enum Status { STATUS_DEFAULT, STATUS_LOAD_OK, STATUS_LOAD_FAILED }; + public: + enum Status { STATUS_DEFAULT, STATUS_LOAD_OK, STATUS_LOAD_FAILED }; - Item(const QFileInfo& file_info, Qt::ItemFlags flags) - : m_fileInfo(file_info), m_flags(flags), m_status(STATUS_DEFAULT) { - } + Item(const QFileInfo& file_info, Qt::ItemFlags flags) + : m_fileInfo(file_info), m_flags(flags), m_status(STATUS_DEFAULT) {} - const QFileInfo& fileInfo() const { - return m_fileInfo; - } + const QFileInfo& fileInfo() const { return m_fileInfo; } - Qt::ItemFlags flags() const { - return m_flags; - } + Qt::ItemFlags flags() const { return m_flags; } - Status status() const { - return m_status; - } + Status status() const { return m_status; } - void setStatus(Status status) { - m_status = status; - } + void setStatus(Status status) { m_status = status; } - const std::vector& perPageMetadata() const { - return m_perPageMetadata; - } + const std::vector& perPageMetadata() const { return m_perPageMetadata; } - std::vector& perPageMetadata() { - return m_perPageMetadata; - } + std::vector& perPageMetadata() { return m_perPageMetadata; } -private: - QFileInfo m_fileInfo; - Qt::ItemFlags m_flags; - std::vector m_perPageMetadata; - Status m_status; + private: + QFileInfo m_fileInfo; + Qt::ItemFlags m_flags; + std::vector m_perPageMetadata; + Status m_status; }; class ProjectFilesDialog::FileList : private QAbstractListModel { - DECLARE_NON_COPYABLE(FileList) + DECLARE_NON_COPYABLE(FileList) -public: - enum LoadStatus { LOAD_OK, LOAD_FAILED, NO_MORE_FILES }; + public: + enum LoadStatus { LOAD_OK, LOAD_FAILED, NO_MORE_FILES }; - FileList(); + FileList(); - ~FileList() override; + ~FileList() override; - QAbstractItemModel* model() { - return this; - } + QAbstractItemModel* model() { return this; } - template - void files(OutFunc out) const; + template + void files(OutFunc out) const; - const Item& item(const QModelIndex& index) { - return m_items[index.row()]; - } + const Item& item(const QModelIndex& index) { return m_items[index.row()]; } - template - void items(OutFunc out) const; + template + void items(OutFunc out) const; - template - void items(const QItemSelection& selection, OutFunc out) const; + template + void items(const QItemSelection& selection, OutFunc out) const; - size_t count() const { - return m_items.size(); - } + size_t count() const { return m_items.size(); } - void clear(); + void clear(); - template - void append(It begin, It end); + template + void append(It begin, It end); - template - void assign(It begin, It end); + template + void assign(It begin, It end); - void remove(const QItemSelection& selection); + void remove(const QItemSelection& selection); - void prepareForLoadingFiles(); + void prepareForLoadingFiles(); - LoadStatus loadNextFile(); + LoadStatus loadNextFile(); -private: - int rowCount(const QModelIndex& parent) const override; + private: + int rowCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; - std::vector m_items; - std::deque m_itemsToLoad; + std::vector m_items; + std::deque m_itemsToLoad; }; class ProjectFilesDialog::SortedFileList : private QSortFilterProxyModel { - DECLARE_NON_COPYABLE(SortedFileList) + DECLARE_NON_COPYABLE(SortedFileList) -public: - explicit SortedFileList(FileList& delegate); + public: + explicit SortedFileList(FileList& delegate); - QAbstractProxyModel* model() { - return this; - } + QAbstractProxyModel* model() { return this; } -private: - bool lessThan(const QModelIndex& lhs, const QModelIndex& rhs) const override; + private: + bool lessThan(const QModelIndex& lhs, const QModelIndex& rhs) const override; - FileList& m_rDelegate; + FileList& m_delegate; }; class ProjectFilesDialog::ItemVisualOrdering { -public: - bool operator()(const Item& lhs, const Item& rhs) const; + public: + bool operator()(const Item& lhs, const Item& rhs) const; }; -template +template void ProjectFilesDialog::FileList::files(OutFunc out) const { - auto it(m_items.begin()); - const auto end(m_items.end()); - for (; it != end; ++it) { - out(it->fileInfo()); - } + auto it(m_items.begin()); + const auto end(m_items.end()); + for (; it != end; ++it) { + out(it->fileInfo()); + } } -template +template void ProjectFilesDialog::FileList::items(OutFunc out) const { - std::for_each(m_items.begin(), m_items.end(), out); + std::for_each(m_items.begin(), m_items.end(), out); } -template +template void ProjectFilesDialog::FileList::items(const QItemSelection& selection, OutFunc out) const { - QListIterator it(selection); - while (it.hasNext()) { - const QItemSelectionRange& range = it.next(); - for (int row = range.top(); row <= range.bottom(); ++row) { - out(m_items[row]); - } + QListIterator it(selection); + while (it.hasNext()) { + const QItemSelectionRange& range = it.next(); + for (int row = range.top(); row <= range.bottom(); ++row) { + out(m_items[row]); } + } } -template +template void ProjectFilesDialog::FileList::append(It begin, It end) { - if (begin == end) { - return; - } - const size_t count = std::distance(begin, end); - beginInsertRows(QModelIndex(), static_cast(m_items.size()), static_cast(m_items.size() + count - 1)); - m_items.insert(m_items.end(), begin, end); - endInsertRows(); + if (begin == end) { + return; + } + const size_t count = std::distance(begin, end); + beginInsertRows(QModelIndex(), static_cast(m_items.size()), static_cast(m_items.size() + count - 1)); + m_items.insert(m_items.end(), begin, end); + endInsertRows(); } -template +template void ProjectFilesDialog::FileList::assign(It begin, It end) { - clear(); - append(begin, end); + clear(); + append(begin, end); } ProjectFilesDialog::ProjectFilesDialog(QWidget* parent) - : QDialog(parent), - m_ptrOffProjectFiles(new FileList), - m_ptrOffProjectFilesSorted(new SortedFileList(*m_ptrOffProjectFiles)), - m_ptrInProjectFiles(new FileList), - m_ptrInProjectFilesSorted(new SortedFileList(*m_ptrInProjectFiles)), - m_loadTimerId(0), - m_metadataLoadFailed(false), - m_autoOutDir(true) { - m_supportedExtensions.insert("png"); - m_supportedExtensions.insert("jpg"); - m_supportedExtensions.insert("jpeg"); - m_supportedExtensions.insert("tif"); - m_supportedExtensions.insert("tiff"); - - setupUi(this); - offProjectList->setModel(m_ptrOffProjectFilesSorted->model()); - inProjectList->setModel(m_ptrInProjectFilesSorted->model()); - - connect(inpDirBrowseBtn, SIGNAL(clicked()), this, SLOT(inpDirBrowse())); - connect(outDirBrowseBtn, SIGNAL(clicked()), this, SLOT(outDirBrowse())); - connect(inpDirLine, SIGNAL(textEdited(const QString&)), this, SLOT(inpDirEdited(const QString&))); - connect(outDirLine, SIGNAL(textEdited(const QString&)), this, SLOT(outDirEdited(const QString&))); - connect(addToProjectBtn, SIGNAL(clicked()), this, SLOT(addToProject())); - connect(removeFromProjectBtn, SIGNAL(clicked()), this, SLOT(removeFromProject())); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onOK())); + : QDialog(parent), + m_offProjectFiles(new FileList), + m_offProjectFilesSorted(new SortedFileList(*m_offProjectFiles)), + m_inProjectFiles(new FileList), + m_inProjectFilesSorted(new SortedFileList(*m_inProjectFiles)), + m_loadTimerId(0), + m_metadataLoadFailed(false), + m_autoOutDir(true) { + m_supportedExtensions.insert("png"); + m_supportedExtensions.insert("jpg"); + m_supportedExtensions.insert("jpeg"); + m_supportedExtensions.insert("tif"); + m_supportedExtensions.insert("tiff"); + + setupUi(this); + offProjectList->setModel(m_offProjectFilesSorted->model()); + inProjectList->setModel(m_inProjectFilesSorted->model()); + + connect(inpDirBrowseBtn, SIGNAL(clicked()), this, SLOT(inpDirBrowse())); + connect(outDirBrowseBtn, SIGNAL(clicked()), this, SLOT(outDirBrowse())); + connect(inpDirLine, SIGNAL(textEdited(const QString&)), this, SLOT(inpDirEdited(const QString&))); + connect(outDirLine, SIGNAL(textEdited(const QString&)), this, SLOT(outDirEdited(const QString&))); + connect(addToProjectBtn, SIGNAL(clicked()), this, SLOT(addToProject())); + connect(removeFromProjectBtn, SIGNAL(clicked()), this, SLOT(removeFromProject())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onOK())); } ProjectFilesDialog::~ProjectFilesDialog() = default; QString ProjectFilesDialog::inputDirectory() const { - return inpDirLine->text(); + return inpDirLine->text(); } QString ProjectFilesDialog::outputDirectory() const { - return outDirLine->text(); + return outDirLine->text(); } std::vector ProjectFilesDialog::inProjectFiles() const { - std::vector files; - m_ptrInProjectFiles->items([&](const Item& item) { files.emplace_back(item.fileInfo(), item.perPageMetadata()); }); + std::vector files; + m_inProjectFiles->items([&](const Item& item) { files.emplace_back(item.fileInfo(), item.perPageMetadata()); }); - std::sort(files.begin(), files.end(), [](const ImageFileInfo& lhs, const ImageFileInfo& rhs) { - return SmartFilenameOrdering()(lhs.fileInfo(), rhs.fileInfo()); - }); + std::sort(files.begin(), files.end(), [](const ImageFileInfo& lhs, const ImageFileInfo& rhs) { + return SmartFilenameOrdering()(lhs.fileInfo(), rhs.fileInfo()); + }); - return files; + return files; } bool ProjectFilesDialog::isRtlLayout() const { - return rtlLayoutCB->isChecked(); + return rtlLayoutCB->isChecked(); } bool ProjectFilesDialog::isDpiFixingForced() const { - return forceFixDpi->isChecked(); + return forceFixDpi->isChecked(); } QString ProjectFilesDialog::sanitizePath(const QString& path) { - QString trimmed(path.trimmed()); - if (trimmed.startsWith(QChar('"')) && trimmed.endsWith(QChar('"'))) { - trimmed.chop(1); - if (!trimmed.isEmpty()) { - trimmed.remove(0, 1); - } + QString trimmed(path.trimmed()); + if (trimmed.startsWith(QChar('"')) && trimmed.endsWith(QChar('"'))) { + trimmed.chop(1); + if (!trimmed.isEmpty()) { + trimmed.remove(0, 1); } + } - return trimmed; + return trimmed; } void ProjectFilesDialog::inpDirBrowse() { - QSettings settings; - - QString initial_dir(inpDirLine->text()); - if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { - initial_dir = settings.value("lastInputDir").toString(); - } - if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { - initial_dir = QDir::home().absolutePath(); - } else { - QDir dir(initial_dir); - if (dir.cdUp()) { - initial_dir = dir.absolutePath(); - } - } - - const QString dir(QFileDialog::getExistingDirectory(this, tr("Input Directory"), initial_dir)); - - if (!dir.isEmpty()) { - setInputDir(dir); - settings.setValue("lastInputDir", dir); - } + QSettings settings; + + QString initial_dir(inpDirLine->text()); + if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { + initial_dir = settings.value("lastInputDir").toString(); + } + if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { + initial_dir = QDir::home().absolutePath(); + } else { + QDir dir(initial_dir); + if (dir.cdUp()) { + initial_dir = dir.absolutePath(); + } + } + + const QString dir(QFileDialog::getExistingDirectory(this, tr("Input Directory"), initial_dir)); + + if (!dir.isEmpty()) { + setInputDir(dir); + settings.setValue("lastInputDir", dir); + } } void ProjectFilesDialog::outDirBrowse() { - QString initial_dir(outDirLine->text()); - if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { - initial_dir = QDir::home().absolutePath(); - } + QString initial_dir(outDirLine->text()); + if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) { + initial_dir = QDir::home().absolutePath(); + } - const QString dir(QFileDialog::getExistingDirectory(this, tr("Output Directory"), initial_dir)); + const QString dir(QFileDialog::getExistingDirectory(this, tr("Output Directory"), initial_dir)); - if (!dir.isEmpty()) { - setOutputDir(dir); - } + if (!dir.isEmpty()) { + setOutputDir(dir); + } } void ProjectFilesDialog::inpDirEdited(const QString& text) { - setInputDir(sanitizePath(text), /* auto_add_files= */ false); + setInputDir(sanitizePath(text), /* auto_add_files= */ false); } void ProjectFilesDialog::outDirEdited(const QString& text) { - m_autoOutDir = false; + m_autoOutDir = false; } namespace { struct FileInfoLess { - bool operator()(const QFileInfo& lhs, const QFileInfo& rhs) const { - if (lhs == rhs) { - // This takes into account filesystem's case sensitivity. - return false; - } - - return lhs.absoluteFilePath() < rhs.absoluteFilePath(); + bool operator()(const QFileInfo& lhs, const QFileInfo& rhs) const { + if (lhs == rhs) { + // This takes into account filesystem's case sensitivity. + return false; } + + return lhs.absoluteFilePath() < rhs.absoluteFilePath(); + } }; } // namespace void ProjectFilesDialog::setInputDir(const QString& dir, const bool auto_add_files) { - inpDirLine->setText(QDir::toNativeSeparators(dir)); - if (m_autoOutDir) { - setOutputDir(QDir::cleanPath(QDir(dir).filePath("out"))); - } - - QFileInfoList files(QDir(dir).entryInfoList(QDir::Files)); - - { - // Filter out files already in project. - // Here we use simple ordering, which is OK. - - std::vector new_files(files.begin(), files.end()); - std::vector existing_files; - m_ptrInProjectFiles->files([&](const QFileInfo& file_info) { existing_files.push_back(file_info); }); - std::sort(new_files.begin(), new_files.end(), FileInfoLess()); - std::sort(existing_files.begin(), existing_files.end(), FileInfoLess()); - - files.clear(); - std::set_difference(new_files.begin(), new_files.end(), existing_files.begin(), existing_files.end(), - std::back_inserter(files), FileInfoLess()); - } - - typedef std::vector ItemList; - ItemList items; - for (const QFileInfo& file : files) { - Qt::ItemFlags flags; - if (m_supportedExtensions.contains(file.suffix().toLower())) { - flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - } - items.emplace_back(file, flags); - } - - m_ptrOffProjectFiles->assign(items.begin(), items.end()); - - if (auto_add_files && (m_ptrInProjectFiles->count() == 0)) { - offProjectList->selectAll(); - addToProject(); - } + inpDirLine->setText(QDir::toNativeSeparators(dir)); + if (m_autoOutDir) { + setOutputDir(QDir::cleanPath(QDir(dir).filePath("out"))); + } + + QFileInfoList files(QDir(dir).entryInfoList(QDir::Files)); + + { + // Filter out files already in project. + // Here we use simple ordering, which is OK. + + std::vector new_files(files.begin(), files.end()); + std::vector existing_files; + m_inProjectFiles->files([&](const QFileInfo& file_info) { existing_files.push_back(file_info); }); + std::sort(new_files.begin(), new_files.end(), FileInfoLess()); + std::sort(existing_files.begin(), existing_files.end(), FileInfoLess()); + + files.clear(); + std::set_difference(new_files.begin(), new_files.end(), existing_files.begin(), existing_files.end(), + std::back_inserter(files), FileInfoLess()); + } + + typedef std::vector ItemList; + ItemList items; + for (const QFileInfo& file : files) { + Qt::ItemFlags flags; + if (m_supportedExtensions.contains(file.suffix().toLower())) { + flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + items.emplace_back(file, flags); + } + + m_offProjectFiles->assign(items.begin(), items.end()); + + if (auto_add_files && (m_inProjectFiles->count() == 0)) { + offProjectList->selectAll(); + addToProject(); + } } // ProjectFilesDialog::setInputDir void ProjectFilesDialog::setOutputDir(const QString& dir) { - outDirLine->setText(QDir::toNativeSeparators(dir)); + outDirLine->setText(QDir::toNativeSeparators(dir)); } void ProjectFilesDialog::addToProject() { - const QItemSelection selection( - m_ptrOffProjectFilesSorted->model()->mapSelectionToSource(offProjectList->selectionModel()->selection())); + const QItemSelection selection( + m_offProjectFilesSorted->model()->mapSelectionToSource(offProjectList->selectionModel()->selection())); - typedef std::vector ItemList; - ItemList items; + typedef std::vector ItemList; + ItemList items; - m_ptrOffProjectFiles->items(selection, [&](const Item& item) { items.push_back(item); }); + m_offProjectFiles->items(selection, [&](const Item& item) { items.push_back(item); }); - m_ptrInProjectFiles->append(items.begin(), items.end()); - m_ptrOffProjectFiles->remove(selection); + m_inProjectFiles->append(items.begin(), items.end()); + m_offProjectFiles->remove(selection); } void ProjectFilesDialog::removeFromProject() { - const QDir input_dir(inpDirLine->text()); + const QDir input_dir(inpDirLine->text()); - const QItemSelection selection( - m_ptrInProjectFilesSorted->model()->mapSelectionToSource(inProjectList->selectionModel()->selection())); + const QItemSelection selection( + m_inProjectFilesSorted->model()->mapSelectionToSource(inProjectList->selectionModel()->selection())); - typedef std::vector ItemList; - ItemList items; + typedef std::vector ItemList; + ItemList items; - m_ptrInProjectFiles->items(selection, [&](const Item& item) { - if (item.fileInfo().dir() == input_dir) { - items.push_back(item); - } - }); + m_inProjectFiles->items(selection, [&](const Item& item) { + if (item.fileInfo().dir() == input_dir) { + items.push_back(item); + } + }); - m_ptrOffProjectFiles->append(items.begin(), items.end()); - m_ptrInProjectFiles->remove(selection); + m_offProjectFiles->append(items.begin(), items.end()); + m_inProjectFiles->remove(selection); } void ProjectFilesDialog::onOK() { - if (m_ptrInProjectFiles->count() == 0) { - QMessageBox::warning(this, tr("Error"), tr("No files in project!")); - + if (m_inProjectFiles->count() == 0) { + QMessageBox::warning(this, tr("Error"), tr("No files in project!")); + + return; + } + + const QDir inp_dir(inpDirLine->text()); + if (!inp_dir.isAbsolute() || !inp_dir.exists()) { + QMessageBox::warning(this, tr("Error"), tr("Input directory is not set or doesn't exist.")); + + return; + } + + const QDir out_dir(outDirLine->text()); + if (inp_dir == out_dir) { + QMessageBox::warning(this, tr("Error"), tr("Input and output directories can't be the same.")); + + return; + } + + 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("Output directory doesn't exist. Create it?"), + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::Yes; + if (!create) { return; + } } - - const QDir inp_dir(inpDirLine->text()); - if (!inp_dir.isAbsolute() || !inp_dir.exists()) { - QMessageBox::warning(this, tr("Error"), tr("Input directory is not set or doesn't exist.")); + if (create) { + if (!out_dir.mkpath(out_dir.path())) { + QMessageBox::warning(this, tr("Error"), tr("Unable to create output directory.")); return; + } } + } + if (!out_dir.isAbsolute() || !out_dir.exists()) { + QMessageBox::warning(this, tr("Error"), tr("Output directory is not set or doesn't exist.")); - const QDir out_dir(outDirLine->text()); - if (inp_dir == out_dir) { - QMessageBox::warning(this, tr("Error"), tr("Input and output directories can't be the same.")); + return; + } - return; - } - - 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("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 output directory.")); - - return; - } - } - } - if (!out_dir.isAbsolute() || !out_dir.exists()) { - QMessageBox::warning(this, tr("Error"), tr("Output directory is not set or doesn't exist.")); - - return; - } - - startLoadingMetadata(); + startLoadingMetadata(); } // ProjectFilesDialog::onOK void ProjectFilesDialog::startLoadingMetadata() { - m_ptrInProjectFiles->prepareForLoadingFiles(); - - progressBar->setMaximum(static_cast(m_ptrInProjectFiles->count())); - inpDirLine->setEnabled(false); - inpDirBrowseBtn->setEnabled(false); - outDirLine->setEnabled(false); - outDirBrowseBtn->setEnabled(false); - addToProjectBtn->setEnabled(false); - removeFromProjectBtn->setEnabled(false); - offProjectSelectAllBtn->setEnabled(false); - inProjectSelectAllBtn->setEnabled(false); - rtlLayoutCB->setEnabled(false); - forceFixDpi->setEnabled(false); - buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - offProjectList->clearSelection(); - inProjectList->clearSelection(); - m_loadTimerId = startTimer(0); - m_metadataLoadFailed = false; + m_inProjectFiles->prepareForLoadingFiles(); + + progressBar->setMaximum(static_cast(m_inProjectFiles->count())); + inpDirLine->setEnabled(false); + inpDirBrowseBtn->setEnabled(false); + outDirLine->setEnabled(false); + outDirBrowseBtn->setEnabled(false); + addToProjectBtn->setEnabled(false); + removeFromProjectBtn->setEnabled(false); + offProjectSelectAllBtn->setEnabled(false); + inProjectSelectAllBtn->setEnabled(false); + rtlLayoutCB->setEnabled(false); + forceFixDpi->setEnabled(false); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + offProjectList->clearSelection(); + inProjectList->clearSelection(); + m_loadTimerId = startTimer(0); + m_metadataLoadFailed = false; } void ProjectFilesDialog::timerEvent(QTimerEvent* event) { - if (event->timerId() != m_loadTimerId) { - QWidget::timerEvent(event); - - return; - } - - switch (m_ptrInProjectFiles->loadNextFile()) { - case FileList::NO_MORE_FILES: - finishLoadingMetadata(); - break; - case FileList::LOAD_FAILED: - m_metadataLoadFailed = true; - // Fall through. - case FileList::LOAD_OK: - progressBar->setValue(progressBar->value() + 1); - break; - } + if (event->timerId() != m_loadTimerId) { + QWidget::timerEvent(event); + + return; + } + + switch (m_inProjectFiles->loadNextFile()) { + case FileList::NO_MORE_FILES: + finishLoadingMetadata(); + break; + case FileList::LOAD_FAILED: + m_metadataLoadFailed = true; + // Fall through. + case FileList::LOAD_OK: + progressBar->setValue(progressBar->value() + 1); + break; + } } void ProjectFilesDialog::finishLoadingMetadata() { - killTimer(m_loadTimerId); - - inpDirLine->setEnabled(true); - inpDirBrowseBtn->setEnabled(true); - outDirLine->setEnabled(true); - outDirBrowseBtn->setEnabled(true); - addToProjectBtn->setEnabled(true); - removeFromProjectBtn->setEnabled(true); - offProjectSelectAllBtn->setEnabled(true); - inProjectSelectAllBtn->setEnabled(true); - rtlLayoutCB->setEnabled(true); - forceFixDpi->setEnabled(true); - buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - - if (m_metadataLoadFailed) { - progressBar->setValue(0); - QMessageBox::warning(this, tr("Error"), - tr("Some of the files failed to load.\n" - "Either we don't support their format, or they are broken.\n" - "You should remove them from the project.")); - - return; - } - - accept(); + killTimer(m_loadTimerId); + + inpDirLine->setEnabled(true); + inpDirBrowseBtn->setEnabled(true); + outDirLine->setEnabled(true); + outDirBrowseBtn->setEnabled(true); + addToProjectBtn->setEnabled(true); + removeFromProjectBtn->setEnabled(true); + offProjectSelectAllBtn->setEnabled(true); + inProjectSelectAllBtn->setEnabled(true); + rtlLayoutCB->setEnabled(true); + forceFixDpi->setEnabled(true); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + + if (m_metadataLoadFailed) { + progressBar->setValue(0); + QMessageBox::warning(this, tr("Error"), + tr("Some of the files failed to load.\n" + "Either we don't support their format, or they are broken.\n" + "You should remove them from the project.")); + + return; + } + + accept(); } /*====================== ProjectFilesDialog::FileList ====================*/ @@ -521,138 +499,138 @@ ProjectFilesDialog::FileList::FileList() = default; ProjectFilesDialog::FileList::~FileList() = default; void ProjectFilesDialog::FileList::clear() { - if (m_items.empty()) { - return; - } - beginRemoveRows(QModelIndex(), 0, static_cast(m_items.size() - 1)); - m_items.clear(); - endRemoveRows(); + if (m_items.empty()) { + return; + } + beginRemoveRows(QModelIndex(), 0, static_cast(m_items.size() - 1)); + m_items.clear(); + endRemoveRows(); } void ProjectFilesDialog::FileList::remove(const QItemSelection& selection) { - if (selection.isEmpty()) { - return; - } - - typedef std::pair Range; - QVector sorted_ranges; - for (const auto& range : selection) { - sorted_ranges.push_back(Range(range.top(), range.bottom())); - } - - std::sort(sorted_ranges.begin(), sorted_ranges.end(), - [](const Range& lhs, const Range& rhs) { return lhs.first < rhs.first; }); - - QVectorIterator it(sorted_ranges); - int rows_removed = 0; - while (it.hasNext()) { - const Range& range = it.next(); - const int first = range.first - rows_removed; - const int last = range.second - rows_removed; - beginRemoveRows(QModelIndex(), first, last); - m_items.erase(m_items.begin() + first, m_items.begin() + (last + 1)); - endRemoveRows(); - rows_removed += last - first + 1; - } + if (selection.isEmpty()) { + return; + } + + typedef std::pair Range; + QVector sorted_ranges; + for (const auto& range : selection) { + sorted_ranges.push_back(Range(range.top(), range.bottom())); + } + + std::sort(sorted_ranges.begin(), sorted_ranges.end(), + [](const Range& lhs, const Range& rhs) { return lhs.first < rhs.first; }); + + QVectorIterator it(sorted_ranges); + int rows_removed = 0; + while (it.hasNext()) { + const Range& range = it.next(); + const int first = range.first - rows_removed; + const int last = range.second - rows_removed; + beginRemoveRows(QModelIndex(), first, last); + m_items.erase(m_items.begin() + first, m_items.begin() + (last + 1)); + endRemoveRows(); + rows_removed += last - first + 1; + } } // ProjectFilesDialog::FileList::remove int ProjectFilesDialog::FileList::rowCount(const QModelIndex&) const { - return static_cast(m_items.size()); + return static_cast(m_items.size()); } QVariant ProjectFilesDialog::FileList::data(const QModelIndex& index, const int role) const { - const Item& item = m_items[index.row()]; - switch (role) { - case Qt::DisplayRole: - return item.fileInfo().fileName(); - case Qt::ForegroundRole: - switch (item.status()) { - case Item::STATUS_DEFAULT: - return QVariant(); - case Item::STATUS_LOAD_OK: - return QBrush(QColor(0x00, 0xff, 0x00)); - case Item::STATUS_LOAD_FAILED: - return QBrush(QColor(0xff, 0x00, 0x00)); - } - break; - default: - break; - } - - return QVariant(); + const Item& item = m_items[index.row()]; + switch (role) { + case Qt::DisplayRole: + return item.fileInfo().fileName(); + case Qt::ForegroundRole: + switch (item.status()) { + case Item::STATUS_DEFAULT: + return QVariant(); + case Item::STATUS_LOAD_OK: + return QBrush(QColor(0x00, 0xff, 0x00)); + case Item::STATUS_LOAD_FAILED: + return QBrush(QColor(0xff, 0x00, 0x00)); + } + break; + default: + break; + } + + return QVariant(); } Qt::ItemFlags ProjectFilesDialog::FileList::flags(const QModelIndex& index) const { - return m_items[index.row()].flags(); + return m_items[index.row()].flags(); } void ProjectFilesDialog::FileList::prepareForLoadingFiles() { - std::deque item_indexes; - const auto num_items = static_cast(m_items.size()); - for (int i = 0; i < num_items; ++i) { - item_indexes.push_back(i); - } + std::deque item_indexes; + const auto num_items = static_cast(m_items.size()); + for (int i = 0; i < num_items; ++i) { + item_indexes.push_back(i); + } - std::sort(item_indexes.begin(), item_indexes.end(), - [&](int lhs, int rhs) { return ItemVisualOrdering()(m_items[lhs], m_items[rhs]); }); + std::sort(item_indexes.begin(), item_indexes.end(), + [&](int lhs, int rhs) { return ItemVisualOrdering()(m_items[lhs], m_items[rhs]); }); - m_itemsToLoad.swap(item_indexes); + m_itemsToLoad.swap(item_indexes); } ProjectFilesDialog::FileList::LoadStatus ProjectFilesDialog::FileList::loadNextFile() { - if (m_itemsToLoad.empty()) { - return NO_MORE_FILES; - } - - const int item_idx = m_itemsToLoad.front(); - Item& item = m_items[item_idx]; - std::vector per_page_metadata; - const QString file_path(item.fileInfo().absoluteFilePath()); - const ImageMetadataLoader::Status st = ImageMetadataLoader::load( - file_path, [&](const ImageMetadata& metadata) { per_page_metadata.push_back(metadata); }); - - LoadStatus status; - - if (st == ImageMetadataLoader::LOADED) { - status = LOAD_OK; - item.perPageMetadata().swap(per_page_metadata); - item.setStatus(Item::STATUS_LOAD_OK); - } else { - status = LOAD_FAILED; - item.setStatus(Item::STATUS_LOAD_FAILED); - } - const QModelIndex idx(index(item_idx, 0)); - emit dataChanged(idx, idx); - - m_itemsToLoad.pop_front(); - - return status; + if (m_itemsToLoad.empty()) { + return NO_MORE_FILES; + } + + const int item_idx = m_itemsToLoad.front(); + Item& item = m_items[item_idx]; + std::vector per_page_metadata; + const QString file_path(item.fileInfo().absoluteFilePath()); + const ImageMetadataLoader::Status st = ImageMetadataLoader::load( + file_path, [&](const ImageMetadata& metadata) { per_page_metadata.push_back(metadata); }); + + LoadStatus status; + + if (st == ImageMetadataLoader::LOADED) { + status = LOAD_OK; + item.perPageMetadata().swap(per_page_metadata); + item.setStatus(Item::STATUS_LOAD_OK); + } else { + status = LOAD_FAILED; + item.setStatus(Item::STATUS_LOAD_FAILED); + } + const QModelIndex idx(index(item_idx, 0)); + emit dataChanged(idx, idx); + + m_itemsToLoad.pop_front(); + + return status; } // ProjectFilesDialog::FileList::loadNextFile /*================= ProjectFilesDialog::SortedFileList ===================*/ -ProjectFilesDialog::SortedFileList::SortedFileList(FileList& delegate) : m_rDelegate(delegate) { - setSourceModel(delegate.model()); - setDynamicSortFilter(true); - sort(0); +ProjectFilesDialog::SortedFileList::SortedFileList(FileList& delegate) : m_delegate(delegate) { + setSourceModel(delegate.model()); + setDynamicSortFilter(true); + sort(0); } bool ProjectFilesDialog::SortedFileList::lessThan(const QModelIndex& lhs, const QModelIndex& rhs) const { - const Item& lhs_item = m_rDelegate.item(lhs); - const Item& rhs_item = m_rDelegate.item(rhs); + const Item& lhs_item = m_delegate.item(lhs); + const Item& rhs_item = m_delegate.item(rhs); - return ItemVisualOrdering()(lhs_item, rhs_item); + return ItemVisualOrdering()(lhs_item, rhs_item); } /*=============== ProjectFilesDialog::ItemVisualOrdering =================*/ bool ProjectFilesDialog::ItemVisualOrdering::operator()(const Item& lhs, const Item& rhs) const { - const bool lhs_failed = (lhs.status() == Item::STATUS_LOAD_FAILED); - const bool rhs_failed = (rhs.status() == Item::STATUS_LOAD_FAILED); - if (lhs_failed != rhs_failed) { - // Failed ones go to the top. - return lhs_failed; - } - - return SmartFilenameOrdering()(lhs.fileInfo(), rhs.fileInfo()); + const bool lhs_failed = (lhs.status() == Item::STATUS_LOAD_FAILED); + const bool rhs_failed = (rhs.status() == Item::STATUS_LOAD_FAILED); + if (lhs_failed != rhs_failed) { + // Failed ones go to the top. + return lhs_failed; + } + + return SmartFilenameOrdering()(lhs.fileInfo(), rhs.fileInfo()); } diff --git a/ProjectFilesDialog.h b/ProjectFilesDialog.h index 192407c23..eaf591e0f 100644 --- a/ProjectFilesDialog.h +++ b/ProjectFilesDialog.h @@ -19,73 +19,73 @@ #ifndef PROJECTFILESDIALOG_H_ #define PROJECTFILESDIALOG_H_ -#include "ui_ProjectFilesDialog.h" -#include "ImageFileInfo.h" #include -#include #include -#include +#include #include +#include +#include "ImageFileInfo.h" +#include "ui_ProjectFilesDialog.h" class ProjectFilesDialog : public QDialog, private Ui::ProjectFilesDialog { - Q_OBJECT -public: - explicit ProjectFilesDialog(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit ProjectFilesDialog(QWidget* parent = nullptr); - ~ProjectFilesDialog() override; + ~ProjectFilesDialog() override; - QString inputDirectory() const; + QString inputDirectory() const; - QString outputDirectory() const; + QString outputDirectory() const; - std::vector inProjectFiles() const; + std::vector inProjectFiles() const; - bool isRtlLayout() const; + bool isRtlLayout() const; - bool isDpiFixingForced() const; + bool isDpiFixingForced() const; -private slots: + private slots: - static QString sanitizePath(const QString& path); + static QString sanitizePath(const QString& path); - void inpDirBrowse(); + void inpDirBrowse(); - void outDirBrowse(); + void outDirBrowse(); - void inpDirEdited(const QString& text); + void inpDirEdited(const QString& text); - void outDirEdited(const QString& text); + void outDirEdited(const QString& text); - void addToProject(); + void addToProject(); - void removeFromProject(); + void removeFromProject(); - void onOK(); + void onOK(); -private: - class Item; - class FileList; - class SortedFileList; - class ItemVisualOrdering; + private: + class Item; + class FileList; + class SortedFileList; + class ItemVisualOrdering; - void setInputDir(const QString& dir, bool auto_add_files = true); + void setInputDir(const QString& dir, bool auto_add_files = true); - void setOutputDir(const QString& dir); + void setOutputDir(const QString& dir); - void startLoadingMetadata(); + void startLoadingMetadata(); - void timerEvent(QTimerEvent* event) override; + void timerEvent(QTimerEvent* event) override; - void finishLoadingMetadata(); + void finishLoadingMetadata(); - QSet m_supportedExtensions; - std::unique_ptr m_ptrOffProjectFiles; - std::unique_ptr m_ptrOffProjectFilesSorted; - std::unique_ptr m_ptrInProjectFiles; - std::unique_ptr m_ptrInProjectFilesSorted; - int m_loadTimerId; - bool m_metadataLoadFailed; - bool m_autoOutDir; + QSet m_supportedExtensions; + std::unique_ptr m_offProjectFiles; + std::unique_ptr m_offProjectFilesSorted; + std::unique_ptr m_inProjectFiles; + std::unique_ptr m_inProjectFilesSorted; + int m_loadTimerId; + bool m_metadataLoadFailed; + bool m_autoOutDir; }; diff --git a/ProjectOpeningContext.cpp b/ProjectOpeningContext.cpp index c7fae3619..1a7d31cc1 100644 --- a/ProjectOpeningContext.cpp +++ b/ProjectOpeningContext.cpp @@ -17,64 +17,63 @@ */ #include "ProjectOpeningContext.h" +#include +#include #include "FixDpiDialog.h" #include "ProjectPages.h" #include "version.h" -#include -#include ProjectOpeningContext::ProjectOpeningContext(QWidget* parent, const QString& project_file, const QDomDocument& doc) - : m_projectFile(project_file), m_reader(doc), m_pParent(parent) { -} + : m_projectFile(project_file), m_reader(doc), m_parent(parent) {} ProjectOpeningContext::~ProjectOpeningContext() { - // Deleting a null pointer is OK. - delete m_ptrFixDpiDialog; + // Deleting a null pointer is OK. + delete m_fixDpiDialog; } void ProjectOpeningContext::proceed() { - if (!m_reader.success()) { - deleteLater(); - if (!m_reader.getVersion().isNull() && (m_reader.getVersion().toInt() != PROJECT_VERSION)) { - QMessageBox::warning(m_pParent, tr("Error"), - tr("The project file is not compatible with the current application version.")); + if (!m_reader.success()) { + deleteLater(); + if (!m_reader.getVersion().isNull() && (m_reader.getVersion().toInt() != PROJECT_VERSION)) { + QMessageBox::warning(m_parent, tr("Error"), + tr("The project file is not compatible with the current application version.")); - return; - } + return; + } - QMessageBox::critical(m_pParent, tr("Error"), tr("Unable to interpret the project file.")); + QMessageBox::critical(m_parent, tr("Error"), tr("Unable to interpret the project file.")); - return; - } + return; + } - if (m_reader.pages()->validateDpis()) { - deleteLater(); - emit done(this); + if (m_reader.pages()->validateDpis()) { + deleteLater(); + emit done(this); - return; - } + return; + } - showFixDpiDialog(); + showFixDpiDialog(); } void ProjectOpeningContext::fixedDpiSubmitted() { - m_reader.pages()->updateMetadataFrom(m_ptrFixDpiDialog->files()); - emit done(this); + m_reader.pages()->updateMetadataFrom(m_fixDpiDialog->files()); + emit done(this); } void ProjectOpeningContext::fixDpiDialogDestroyed() { - deleteLater(); + deleteLater(); } void ProjectOpeningContext::showFixDpiDialog() { - assert(!m_ptrFixDpiDialog); - m_ptrFixDpiDialog = new FixDpiDialog(m_reader.pages()->toImageFileInfo(), m_pParent); - m_ptrFixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); - m_ptrFixDpiDialog->setAttribute(Qt::WA_QuitOnClose, false); - if (m_pParent) { - m_ptrFixDpiDialog->setWindowModality(Qt::WindowModal); - } - connect(m_ptrFixDpiDialog, SIGNAL(accepted()), this, SLOT(fixedDpiSubmitted())); - connect(m_ptrFixDpiDialog, SIGNAL(destroyed(QObject*)), this, SLOT(fixDpiDialogDestroyed())); - m_ptrFixDpiDialog->show(); + assert(!m_fixDpiDialog); + m_fixDpiDialog = new FixDpiDialog(m_reader.pages()->toImageFileInfo(), m_parent); + m_fixDpiDialog->setAttribute(Qt::WA_DeleteOnClose); + m_fixDpiDialog->setAttribute(Qt::WA_QuitOnClose, false); + if (m_parent) { + m_fixDpiDialog->setWindowModality(Qt::WindowModal); + } + connect(m_fixDpiDialog, SIGNAL(accepted()), this, SLOT(fixedDpiSubmitted())); + connect(m_fixDpiDialog, SIGNAL(destroyed(QObject*)), this, SLOT(fixDpiDialogDestroyed())); + m_fixDpiDialog->show(); } diff --git a/ProjectOpeningContext.h b/ProjectOpeningContext.h index 5b391b358..63574cca8 100644 --- a/ProjectOpeningContext.h +++ b/ProjectOpeningContext.h @@ -19,55 +19,51 @@ #ifndef PROJECTOPENINGCONTEXT_H_ #define PROJECTOPENINGCONTEXT_H_ -#include "NonCopyable.h" -#include "ProjectReader.h" -#include "ImageFileInfo.h" #include #include #include #include #include +#include "ImageFileInfo.h" +#include "NonCopyable.h" +#include "ProjectReader.h" class FixDpiDialog; class QWidget; class QDomDocument; class ProjectOpeningContext : public QObject { - Q_OBJECT - DECLARE_NON_COPYABLE(ProjectOpeningContext) + Q_OBJECT + DECLARE_NON_COPYABLE(ProjectOpeningContext) -public: - ProjectOpeningContext(QWidget* parent, const QString& project_file, const QDomDocument& doc); + public: + ProjectOpeningContext(QWidget* parent, const QString& project_file, const QDomDocument& doc); - ~ProjectOpeningContext() override; + ~ProjectOpeningContext() override; - void proceed(); + void proceed(); - const QString& projectFile() const { - return m_projectFile; - } + const QString& projectFile() const { return m_projectFile; } - ProjectReader* projectReader() { - return &m_reader; - } + ProjectReader* projectReader() { return &m_reader; } -signals: + signals: - void done(ProjectOpeningContext* context); + void done(ProjectOpeningContext* context); -private slots: + private slots: - void fixedDpiSubmitted(); + void fixedDpiSubmitted(); - void fixDpiDialogDestroyed(); + void fixDpiDialogDestroyed(); -private: - void showFixDpiDialog(); + private: + void showFixDpiDialog(); - QString m_projectFile; - ProjectReader m_reader; - QPointer m_ptrFixDpiDialog; - QWidget* m_pParent; + QString m_projectFile; + ProjectReader m_reader; + QPointer m_fixDpiDialog; + QWidget* m_parent; }; diff --git a/ProjectPages.cpp b/ProjectPages.cpp index fca0182f6..45ac37826 100644 --- a/ProjectPages.cpp +++ b/ProjectPages.cpp @@ -17,416 +17,412 @@ */ #include "ProjectPages.h" +#include +#include +#include +#include +#include +#include +#include +#include "AbstractRelinker.h" #include "ImageFileInfo.h" #include "ImageInfo.h" #include "OrthogonalRotation.h" #include "PageSequence.h" #include "RelinkablePath.h" -#include "AbstractRelinker.h" -#include -#include -#include -#include -#include -#include -#include ProjectPages::ProjectPages(const Qt::LayoutDirection layout_direction) { - initSubPagesInOrder(layout_direction); + initSubPagesInOrder(layout_direction); } ProjectPages::ProjectPages(const std::vector& info, const Qt::LayoutDirection layout_direction) { - initSubPagesInOrder(layout_direction); - - for (const ImageInfo& image : info) { - ImageDesc image_desc(image); - // Enforce some rules. - if (image_desc.numLogicalPages == 2) { - image_desc.leftHalfRemoved = false; - image_desc.rightHalfRemoved = false; - } else if (image_desc.numLogicalPages != 1) { - continue; - } else if (image_desc.leftHalfRemoved && image_desc.rightHalfRemoved) { - image_desc.leftHalfRemoved = false; - image_desc.rightHalfRemoved = false; - } - - m_images.push_back(image_desc); + initSubPagesInOrder(layout_direction); + + for (const ImageInfo& image : info) { + ImageDesc image_desc(image); + // Enforce some rules. + if (image_desc.numLogicalPages == 2) { + image_desc.leftHalfRemoved = false; + image_desc.rightHalfRemoved = false; + } else if (image_desc.numLogicalPages != 1) { + continue; + } else if (image_desc.leftHalfRemoved && image_desc.rightHalfRemoved) { + image_desc.leftHalfRemoved = false; + image_desc.rightHalfRemoved = false; } + + m_images.push_back(image_desc); + } } ProjectPages::ProjectPages(const std::vector& files, const Pages pages, const Qt::LayoutDirection layout_direction) { - initSubPagesInOrder(layout_direction); - - for (const ImageFileInfo& file : files) { - const QString& file_path = file.fileInfo().absoluteFilePath(); - const std::vector& images = file.imageInfo(); - const auto num_images = static_cast(images.size()); - const int multi_page_base = num_images > 1 ? 1 : 0; - for (int i = 0; i < num_images; ++i) { - const ImageMetadata& metadata = images[i]; - const ImageId id(file_path, multi_page_base + i); - m_images.emplace_back(id, metadata, pages); - } + initSubPagesInOrder(layout_direction); + + for (const ImageFileInfo& file : files) { + const QString& file_path = file.fileInfo().absoluteFilePath(); + const std::vector& images = file.imageInfo(); + const auto num_images = static_cast(images.size()); + const int multi_page_base = num_images > 1 ? 1 : 0; + for (int i = 0; i < num_images; ++i) { + const ImageMetadata& metadata = images[i]; + const ImageId id(file_path, multi_page_base + i); + m_images.emplace_back(id, metadata, pages); } + } } ProjectPages::~ProjectPages() = default; Qt::LayoutDirection ProjectPages::layoutDirection() const { - if (m_subPagesInOrder[0] == PageId::LEFT_PAGE) { - return Qt::LeftToRight; - } else { - assert(m_subPagesInOrder[0] == PageId::RIGHT_PAGE); + if (m_subPagesInOrder[0] == PageId::LEFT_PAGE) { + return Qt::LeftToRight; + } else { + assert(m_subPagesInOrder[0] == PageId::RIGHT_PAGE); - return Qt::RightToLeft; - } + return Qt::RightToLeft; + } } void ProjectPages::initSubPagesInOrder(const Qt::LayoutDirection layout_direction) { - if (layout_direction == Qt::LeftToRight) { - m_subPagesInOrder[0] = PageId::LEFT_PAGE; - m_subPagesInOrder[1] = PageId::RIGHT_PAGE; - } else { - m_subPagesInOrder[0] = PageId::RIGHT_PAGE; - m_subPagesInOrder[1] = PageId::LEFT_PAGE; - } + if (layout_direction == Qt::LeftToRight) { + m_subPagesInOrder[0] = PageId::LEFT_PAGE; + m_subPagesInOrder[1] = PageId::RIGHT_PAGE; + } else { + m_subPagesInOrder[0] = PageId::RIGHT_PAGE; + m_subPagesInOrder[1] = PageId::LEFT_PAGE; + } } PageSequence ProjectPages::toPageSequence(const PageView view) const { - PageSequence pages; - - if (view == PAGE_VIEW) { - QMutexLocker locker(&m_mutex); - - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - const ImageDesc& image = m_images[i]; - assert(image.numLogicalPages >= 1 && image.numLogicalPages <= 2); - for (int j = 0; j < image.numLogicalPages; ++j) { - const PageId id(image.id, image.logicalPageToSubPage(j, m_subPagesInOrder)); - pages.append(PageInfo(id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, - image.rightHalfRemoved)); - } - } - } else { - assert(view == IMAGE_VIEW); + PageSequence pages; - QMutexLocker locker(&m_mutex); + if (view == PAGE_VIEW) { + QMutexLocker locker(&m_mutex); - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - const ImageDesc& image = m_images[i]; - const PageId id(image.id, PageId::SINGLE_PAGE); - pages.append( - PageInfo(id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved)); - } + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + const ImageDesc& image = m_images[i]; + assert(image.numLogicalPages >= 1 && image.numLogicalPages <= 2); + for (int j = 0; j < image.numLogicalPages; ++j) { + const PageId id(image.id, image.logicalPageToSubPage(j, m_subPagesInOrder)); + pages.append( + PageInfo(id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved)); + } + } + } else { + assert(view == IMAGE_VIEW); + + QMutexLocker locker(&m_mutex); + + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + const ImageDesc& image = m_images[i]; + const PageId id(image.id, PageId::SINGLE_PAGE); + pages.append(PageInfo(id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved)); } + } - return pages; + return pages; } // ProjectPages::toPageSequence void ProjectPages::listRelinkablePaths(const VirtualFunction& sink) const { - // It's generally a bad idea to do callbacks while holding an internal mutex, - // so we accumulate results into this vector first. - std::vector files; + // It's generally a bad idea to do callbacks while holding an internal mutex, + // so we accumulate results into this vector first. + std::vector files; - { - QMutexLocker locker(&m_mutex); + { + QMutexLocker locker(&m_mutex); - files.reserve(m_images.size()); - for (const ImageDesc& image : m_images) { - files.push_back(image.id.filePath()); - } + files.reserve(m_images.size()); + for (const ImageDesc& image : m_images) { + files.push_back(image.id.filePath()); } + } - for (const QString& file : files) { - sink(RelinkablePath(file, RelinkablePath::File)); - } + for (const QString& file : files) { + sink(RelinkablePath(file, RelinkablePath::File)); + } } void ProjectPages::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - for (ImageDesc& image : m_images) { - const RelinkablePath old_path(image.id.filePath(), RelinkablePath::File); - const QString new_path(relinker.substitutionPathFor(old_path)); - image.id.setFilePath(new_path); - } + for (ImageDesc& image : m_images) { + const RelinkablePath old_path(image.id.filePath(), RelinkablePath::File); + const QString new_path(relinker.substitutionPathFor(old_path)); + image.id.setFilePath(new_path); + } } void ProjectPages::setLayoutTypeFor(const ImageId& image_id, const LayoutType layout) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); - setLayoutTypeForImpl(image_id, layout, &was_modified); - } + { + QMutexLocker locker(&m_mutex); + setLayoutTypeForImpl(image_id, layout, &was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } void ProjectPages::setLayoutTypeForAllPages(const LayoutType layout) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); - setLayoutTypeForAllPagesImpl(layout, &was_modified); - } + { + QMutexLocker locker(&m_mutex); + setLayoutTypeForAllPagesImpl(layout, &was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } void ProjectPages::autoSetLayoutTypeFor(const ImageId& image_id, const OrthogonalRotation rotation) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); - autoSetLayoutTypeForImpl(image_id, rotation, &was_modified); - } + { + QMutexLocker locker(&m_mutex); + autoSetLayoutTypeForImpl(image_id, rotation, &was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } void ProjectPages::updateImageMetadata(const ImageId& image_id, const ImageMetadata& metadata) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); - updateImageMetadataImpl(image_id, metadata, &was_modified); - } + { + QMutexLocker locker(&m_mutex); + updateImageMetadataImpl(image_id, metadata, &was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } int ProjectPages::adviseNumberOfLogicalPages(const ImageMetadata& metadata, const OrthogonalRotation rotation) { - const QSize size(rotation.rotate(metadata.size())); - const QSize dpi(rotation.rotate(metadata.dpi().toSize())); - - if (size.width() * dpi.height() > size.height() * dpi.width()) { - return 2; - } else { - return 1; - } + const QSize size(rotation.rotate(metadata.size())); + const QSize dpi(rotation.rotate(metadata.dpi().toSize())); + + if (size.width() * dpi.height() > size.height() * dpi.width()) { + return 2; + } else { + return 1; + } } int ProjectPages::numImages() const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return static_cast(m_images.size()); + return static_cast(m_images.size()); } std::vector ProjectPages::insertImage(const ImageInfo& new_image, BeforeOrAfter before_or_after, const ImageId& existing, const PageView view) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); + { + QMutexLocker locker(&m_mutex); - return insertImageImpl(new_image, before_or_after, existing, view, was_modified); - } + return insertImageImpl(new_image, before_or_after, existing, view, was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } void ProjectPages::removePages(const std::set& pages) { - bool was_modified = false; + bool was_modified = false; - { - QMutexLocker locker(&m_mutex); - removePagesImpl(pages, was_modified); - } + { + QMutexLocker locker(&m_mutex); + removePagesImpl(pages, was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } } PageInfo ProjectPages::unremovePage(const PageId& page_id) { - bool was_modified = false; + bool was_modified = false; - PageInfo page_info; + PageInfo page_info; - { - QMutexLocker locker(&m_mutex); - page_info = unremovePageImpl(page_id, was_modified); - } + { + QMutexLocker locker(&m_mutex); + page_info = unremovePageImpl(page_id, was_modified); + } - if (was_modified) { - emit modified(); - } + if (was_modified) { + emit modified(); + } - return page_info; + return page_info; } bool ProjectPages::validateDpis() const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - for (const ImageDesc& image : m_images) { - if (!image.metadata.isDpiOK()) { - return false; - } + for (const ImageDesc& image : m_images) { + if (!image.metadata.isDpiOK()) { + return false; } + } - return true; + return true; } namespace { struct File { - QString fileName; - mutable std::vector metadata; + QString fileName; + mutable std::vector metadata; - explicit File(const QString& fname) : fileName(fname) { - } + explicit File(const QString& fname) : fileName(fname) {} - explicit operator ImageFileInfo() const { - return ImageFileInfo(fileName, metadata); - } + explicit operator ImageFileInfo() const { return ImageFileInfo(fileName, metadata); } }; } // anonymous namespace std::vector ProjectPages::toImageFileInfo() const { - using namespace boost::multi_index; + using namespace boost::multi_index; - multi_index_container>, sequenced<>>> files; + multi_index_container>, sequenced<>>> files; - { - QMutexLocker locker(&m_mutex); + { + QMutexLocker locker(&m_mutex); - for (const ImageDesc& image : m_images) { - const File file(image.id.filePath()); - files.insert(file).first->metadata.push_back(image.metadata); - } + for (const ImageDesc& image : m_images) { + const File file(image.id.filePath()); + files.insert(file).first->metadata.push_back(image.metadata); } + } - return std::vector(files.get<1>().begin(), files.get<1>().end()); + return std::vector(files.get<1>().begin(), files.get<1>().end()); } void ProjectPages::updateMetadataFrom(const std::vector& files) { - typedef std::unordered_map MetadataMap; - MetadataMap metadata_map; - - for (const ImageFileInfo& file : files) { - const QString file_path(file.fileInfo().absoluteFilePath()); - int page = 0; - for (const ImageMetadata& metadata : file.imageInfo()) { - metadata_map[ImageId(file_path, page)] = metadata; - ++page; - } + typedef std::unordered_map MetadataMap; + MetadataMap metadata_map; + + for (const ImageFileInfo& file : files) { + const QString file_path(file.fileInfo().absoluteFilePath()); + int page = 0; + for (const ImageMetadata& metadata : file.imageInfo()) { + metadata_map[ImageId(file_path, page)] = metadata; + ++page; } + } - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - for (ImageDesc& image : m_images) { - const MetadataMap::const_iterator it(metadata_map.find(image.id)); - if (it != metadata_map.end()) { - image.metadata = it->second; - } + for (ImageDesc& image : m_images) { + const MetadataMap::const_iterator it(metadata_map.find(image.id)); + if (it != metadata_map.end()) { + image.metadata = it->second; } + } } void ProjectPages::setLayoutTypeForImpl(const ImageId& image_id, const LayoutType layout, bool* modified) { - const int num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - ImageDesc& image = m_images[i]; - if (image.id == image_id) { - int adjusted_num_pages = num_pages; - if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { - // Both can't be removed, but we handle that case anyway - // by treating it like none are removed. - --adjusted_num_pages; - } - - const int delta = adjusted_num_pages - image.numLogicalPages; - if (delta == 0) { - break; - } - - image.numLogicalPages = adjusted_num_pages; - - *modified = true; - break; - } - } + const int num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + ImageDesc& image = m_images[i]; + if (image.id == image_id) { + int adjusted_num_pages = num_pages; + if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { + // Both can't be removed, but we handle that case anyway + // by treating it like none are removed. + --adjusted_num_pages; + } + + const int delta = adjusted_num_pages - image.numLogicalPages; + if (delta == 0) { + break; + } + + image.numLogicalPages = adjusted_num_pages; + + *modified = true; + break; + } + } } void ProjectPages::setLayoutTypeForAllPagesImpl(const LayoutType layout, bool* modified) { - const int num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - ImageDesc& image = m_images[i]; - - int adjusted_num_pages = num_pages; - if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { - // Both can't be removed, but we handle that case anyway - // by treating it like none are removed. - --adjusted_num_pages; - } + const int num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + ImageDesc& image = m_images[i]; - const int delta = adjusted_num_pages - image.numLogicalPages; - if (delta == 0) { - continue; - } + int adjusted_num_pages = num_pages; + if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { + // Both can't be removed, but we handle that case anyway + // by treating it like none are removed. + --adjusted_num_pages; + } - image.numLogicalPages = adjusted_num_pages; - *modified = true; + const int delta = adjusted_num_pages - image.numLogicalPages; + if (delta == 0) { + continue; } + + image.numLogicalPages = adjusted_num_pages; + *modified = true; + } } void ProjectPages::autoSetLayoutTypeForImpl(const ImageId& image_id, const OrthogonalRotation rotation, bool* modified) { - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - ImageDesc& image = m_images[i]; - if (image.id == image_id) { - int num_pages = adviseNumberOfLogicalPages(image.metadata, rotation); - if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { - // Both can't be removed, but we handle that case anyway - // by treating it like none are removed. - --num_pages; - } - - const int delta = num_pages - image.numLogicalPages; - if (delta == 0) { - break; - } - - image.numLogicalPages = num_pages; - - *modified = true; - break; - } - } + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + ImageDesc& image = m_images[i]; + if (image.id == image_id) { + int num_pages = adviseNumberOfLogicalPages(image.metadata, rotation); + if ((num_pages == 2) && (image.leftHalfRemoved != image.rightHalfRemoved)) { + // Both can't be removed, but we handle that case anyway + // by treating it like none are removed. + --num_pages; + } + + const int delta = num_pages - image.numLogicalPages; + if (delta == 0) { + break; + } + + image.numLogicalPages = num_pages; + + *modified = true; + break; + } + } } void ProjectPages::updateImageMetadataImpl(const ImageId& image_id, const ImageMetadata& metadata, bool* modified) { - const auto num_images = static_cast(m_images.size()); - for (int i = 0; i < num_images; ++i) { - ImageDesc& image = m_images[i]; - if (image.id == image_id) { - if (image.metadata != metadata) { - image.metadata = metadata; - *modified = true; - } - break; - } + const auto num_images = static_cast(m_images.size()); + for (int i = 0; i < num_images; ++i) { + ImageDesc& image = m_images[i]; + if (image.id == image_id) { + if (image.metadata != metadata) { + image.metadata = metadata; + *modified = true; + } + break; } + } } std::vector ProjectPages::insertImageImpl(const ImageInfo& new_image, @@ -434,168 +430,167 @@ std::vector ProjectPages::insertImageImpl(const ImageInfo& new_image, const ImageId& existing, const PageView view, bool& modified) { - std::vector logical_pages; + std::vector logical_pages; + + auto it(m_images.begin()); + const auto end(m_images.end()); + for (; it != end && it->id != existing; ++it) { + // Continue until we find the existing image. + } + if (it == end) { + // Existing image not found. + if (!((before_or_after == BEFORE) && existing.isNull())) { + return logical_pages; + } // Otherwise we can still handle that case. + } + if (before_or_after == AFTER) { + ++it; + } + + ImageDesc image_desc(new_image); + + // Enforce some rules. + if (image_desc.numLogicalPages == 2) { + image_desc.leftHalfRemoved = false; + image_desc.rightHalfRemoved = false; + } else if (image_desc.numLogicalPages != 1) { + return logical_pages; + } else if (image_desc.leftHalfRemoved && image_desc.rightHalfRemoved) { + image_desc.leftHalfRemoved = false; + image_desc.rightHalfRemoved = false; + } - auto it(m_images.begin()); - const auto end(m_images.end()); - for (; it != end && it->id != existing; ++it) { - // Continue until we find the existing image. - } - if (it == end) { - // Existing image not found. - if (!((before_or_after == BEFORE) && existing.isNull())) { - return logical_pages; - } // Otherwise we can still handle that case. - } - if (before_or_after == AFTER) { - ++it; - } + m_images.insert(it, image_desc); - ImageDesc image_desc(new_image); + PageInfo page_info_templ(PageId(new_image.id(), PageId::SINGLE_PAGE), image_desc.metadata, image_desc.numLogicalPages, + image_desc.leftHalfRemoved, image_desc.rightHalfRemoved); - // Enforce some rules. - if (image_desc.numLogicalPages == 2) { - image_desc.leftHalfRemoved = false; - image_desc.rightHalfRemoved = false; - } else if (image_desc.numLogicalPages != 1) { - return logical_pages; - } else if (image_desc.leftHalfRemoved && image_desc.rightHalfRemoved) { - image_desc.leftHalfRemoved = false; - image_desc.rightHalfRemoved = false; + if ((view == IMAGE_VIEW) + || ((image_desc.numLogicalPages == 1) && (image_desc.leftHalfRemoved == image_desc.rightHalfRemoved))) { + logical_pages.push_back(page_info_templ); + } else { + if ((image_desc.numLogicalPages == 2) || ((image_desc.numLogicalPages == 1) && image_desc.rightHalfRemoved)) { + page_info_templ.setId(PageId(new_image.id(), m_subPagesInOrder[0])); + logical_pages.push_back(page_info_templ); } - - m_images.insert(it, image_desc); - - PageInfo page_info_templ(PageId(new_image.id(), PageId::SINGLE_PAGE), image_desc.metadata, - image_desc.numLogicalPages, image_desc.leftHalfRemoved, image_desc.rightHalfRemoved); - - if ((view == IMAGE_VIEW) - || ((image_desc.numLogicalPages == 1) && (image_desc.leftHalfRemoved == image_desc.rightHalfRemoved))) { - logical_pages.push_back(page_info_templ); - } else { - if ((image_desc.numLogicalPages == 2) || ((image_desc.numLogicalPages == 1) && image_desc.rightHalfRemoved)) { - page_info_templ.setId(PageId(new_image.id(), m_subPagesInOrder[0])); - logical_pages.push_back(page_info_templ); - } - if ((image_desc.numLogicalPages == 2) || ((image_desc.numLogicalPages == 1) && image_desc.leftHalfRemoved)) { - page_info_templ.setId(PageId(new_image.id(), m_subPagesInOrder[1])); - logical_pages.push_back(page_info_templ); - } + if ((image_desc.numLogicalPages == 2) || ((image_desc.numLogicalPages == 1) && image_desc.leftHalfRemoved)) { + page_info_templ.setId(PageId(new_image.id(), m_subPagesInOrder[1])); + logical_pages.push_back(page_info_templ); } + } - return logical_pages; + return logical_pages; } // ProjectPages::insertImageImpl void ProjectPages::removePagesImpl(const std::set& to_remove, bool& modified) { - const auto to_remove_end(to_remove.end()); - - std::vector new_images; - new_images.reserve(m_images.size()); - int new_total_logical_pages = 0; - - const auto num_old_images = static_cast(m_images.size()); - for (int i = 0; i < num_old_images; ++i) { - ImageDesc image(m_images[i]); - - if (to_remove.find(PageId(image.id, PageId::SINGLE_PAGE)) != to_remove_end) { - image.numLogicalPages = 0; - modified = true; - } else { - if (to_remove.find(PageId(image.id, PageId::LEFT_PAGE)) != to_remove_end) { - image.leftHalfRemoved = true; - --image.numLogicalPages; - modified = true; - } - if (to_remove.find(PageId(image.id, PageId::RIGHT_PAGE)) != to_remove_end) { - image.rightHalfRemoved = true; - --image.numLogicalPages; - modified = true; - } - } - - if (image.numLogicalPages > 0) { - new_images.push_back(image); - new_total_logical_pages += new_images.back().numLogicalPages; - } - } - - new_images.swap(m_images); -} // ProjectPages::removePagesImpl + const auto to_remove_end(to_remove.end()); -PageInfo ProjectPages::unremovePageImpl(const PageId& page_id, bool& modified) { - if (page_id.subPage() == PageId::SINGLE_PAGE) { - // These can't be unremoved. - return PageInfo(); - } - - auto it(m_images.begin()); - const auto end(m_images.end()); - for (; it != end && it->id != page_id.imageId(); ++it) { - // Continue until we find the corresponding image. - } - if (it == end) { - // The corresponding image wasn't found. - return PageInfo(); - } + std::vector new_images; + new_images.reserve(m_images.size()); + int new_total_logical_pages = 0; - ImageDesc& image = *it; + const auto num_old_images = static_cast(m_images.size()); + for (int i = 0; i < num_old_images; ++i) { + ImageDesc image(m_images[i]); - if (image.numLogicalPages != 1) { - return PageInfo(); + if (to_remove.find(PageId(image.id, PageId::SINGLE_PAGE)) != to_remove_end) { + image.numLogicalPages = 0; + modified = true; + } else { + if (to_remove.find(PageId(image.id, PageId::LEFT_PAGE)) != to_remove_end) { + image.leftHalfRemoved = true; + --image.numLogicalPages; + modified = true; + } + if (to_remove.find(PageId(image.id, PageId::RIGHT_PAGE)) != to_remove_end) { + image.rightHalfRemoved = true; + --image.numLogicalPages; + modified = true; + } } - if ((page_id.subPage() == PageId::LEFT_PAGE) && image.leftHalfRemoved) { - image.leftHalfRemoved = false; - } else if ((page_id.subPage() == PageId::RIGHT_PAGE) && image.rightHalfRemoved) { - image.rightHalfRemoved = false; - } else { - return PageInfo(); + if (image.numLogicalPages > 0) { + new_images.push_back(image); + new_total_logical_pages += new_images.back().numLogicalPages; } + } - image.numLogicalPages = 2; + new_images.swap(m_images); +} // ProjectPages::removePagesImpl - return PageInfo(page_id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved); +PageInfo ProjectPages::unremovePageImpl(const PageId& page_id, bool& modified) { + if (page_id.subPage() == PageId::SINGLE_PAGE) { + // These can't be unremoved. + return PageInfo(); + } + + auto it(m_images.begin()); + const auto end(m_images.end()); + for (; it != end && it->id != page_id.imageId(); ++it) { + // Continue until we find the corresponding image. + } + if (it == end) { + // The corresponding image wasn't found. + return PageInfo(); + } + + ImageDesc& image = *it; + + if (image.numLogicalPages != 1) { + return PageInfo(); + } + + if ((page_id.subPage() == PageId::LEFT_PAGE) && image.leftHalfRemoved) { + image.leftHalfRemoved = false; + } else if ((page_id.subPage() == PageId::RIGHT_PAGE) && image.rightHalfRemoved) { + image.rightHalfRemoved = false; + } else { + return PageInfo(); + } + + image.numLogicalPages = 2; + + return PageInfo(page_id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved); } // ProjectPages::unremovePageImpl /*========================= ProjectPages::ImageDesc ======================*/ ProjectPages::ImageDesc::ImageDesc(const ImageInfo& image_info) - : id(image_info.id()), - metadata(image_info.metadata()), - numLogicalPages(image_info.numSubPages()), - leftHalfRemoved(image_info.leftHalfRemoved()), - rightHalfRemoved(image_info.rightHalfRemoved()) { -} + : id(image_info.id()), + metadata(image_info.metadata()), + numLogicalPages(image_info.numSubPages()), + leftHalfRemoved(image_info.leftHalfRemoved()), + rightHalfRemoved(image_info.rightHalfRemoved()) {} ProjectPages::ImageDesc::ImageDesc(const ImageId& id, const ImageMetadata& metadata, const Pages pages) - : id(id), metadata(metadata), leftHalfRemoved(false), rightHalfRemoved(false) { - switch (pages) { - case ONE_PAGE: - numLogicalPages = 1; - break; - case TWO_PAGES: - numLogicalPages = 2; - break; - case AUTO_PAGES: - numLogicalPages = adviseNumberOfLogicalPages(metadata, OrthogonalRotation()); - break; - } + : id(id), metadata(metadata), leftHalfRemoved(false), rightHalfRemoved(false) { + switch (pages) { + case ONE_PAGE: + numLogicalPages = 1; + break; + case TWO_PAGES: + numLogicalPages = 2; + break; + case AUTO_PAGES: + numLogicalPages = adviseNumberOfLogicalPages(metadata, OrthogonalRotation()); + break; + } } PageId::SubPage ProjectPages::ImageDesc::logicalPageToSubPage(const int logical_page, const PageId::SubPage* sub_pages_in_order) const { - assert(numLogicalPages >= 1 && numLogicalPages <= 2); - assert(logical_page >= 0 && logical_page < numLogicalPages); - - if (numLogicalPages == 1) { - if (leftHalfRemoved && !rightHalfRemoved) { - return PageId::RIGHT_PAGE; - } else if (rightHalfRemoved && !leftHalfRemoved) { - return PageId::LEFT_PAGE; - } else { - return PageId::SINGLE_PAGE; - } + assert(numLogicalPages >= 1 && numLogicalPages <= 2); + assert(logical_page >= 0 && logical_page < numLogicalPages); + + if (numLogicalPages == 1) { + if (leftHalfRemoved && !rightHalfRemoved) { + return PageId::RIGHT_PAGE; + } else if (rightHalfRemoved && !leftHalfRemoved) { + return PageId::LEFT_PAGE; } else { - return sub_pages_in_order[logical_page]; + return PageId::SINGLE_PAGE; } + } else { + return sub_pages_in_order[logical_page]; + } } diff --git a/ProjectPages.h b/ProjectPages.h index ca4c04c80..9b5bcf31b 100644 --- a/ProjectPages.h +++ b/ProjectPages.h @@ -19,22 +19,22 @@ #ifndef PROJECT_PAGES_H_ #define PROJECT_PAGES_H_ -#include "NonCopyable.h" -#include "ref_countable.h" -#include "ImageMetadata.h" -#include "ImageId.h" -#include "PageId.h" -#include "PageInfo.h" -#include "PageView.h" -#include "BeforeOrAfter.h" -#include "VirtualFunction.h" -#include #include +#include #include #include +#include #include #include -#include +#include "BeforeOrAfter.h" +#include "ImageId.h" +#include "ImageMetadata.h" +#include "NonCopyable.h" +#include "PageId.h" +#include "PageInfo.h" +#include "PageView.h" +#include "VirtualFunction.h" +#include "ref_countable.h" class ImageFileInfo; class ImageInfo; @@ -45,131 +45,131 @@ class AbstractRelinker; class QDomElement; class ProjectPages : public QObject, public ref_countable { - Q_OBJECT - DECLARE_NON_COPYABLE(ProjectPages) + Q_OBJECT + DECLARE_NON_COPYABLE(ProjectPages) -public: - enum Pages { ONE_PAGE, TWO_PAGES, AUTO_PAGES }; + public: + enum Pages { ONE_PAGE, TWO_PAGES, AUTO_PAGES }; - enum LayoutType { ONE_PAGE_LAYOUT, TWO_PAGE_LAYOUT }; + enum LayoutType { ONE_PAGE_LAYOUT, TWO_PAGE_LAYOUT }; - explicit ProjectPages(Qt::LayoutDirection layout_direction = Qt::LeftToRight); + explicit ProjectPages(Qt::LayoutDirection layout_direction = Qt::LeftToRight); - ProjectPages(const std::vector& images, Qt::LayoutDirection layout_direction); + ProjectPages(const std::vector& images, Qt::LayoutDirection layout_direction); - ProjectPages(const std::vector& files, Pages pages, Qt::LayoutDirection layout_direction); + ProjectPages(const std::vector& files, Pages pages, Qt::LayoutDirection layout_direction); - ~ProjectPages() override; + ~ProjectPages() override; - Qt::LayoutDirection layoutDirection() const; + Qt::LayoutDirection layoutDirection() const; - PageSequence toPageSequence(PageView view) const; + PageSequence toPageSequence(PageView view) const; - void listRelinkablePaths(const VirtualFunction& sink) const; + void listRelinkablePaths(const VirtualFunction& sink) const; - /** - * \note It's up to the caller to make sure different paths aren't collapsed into one. - * Having the same page more the once in a project is not supported by Scan Tailor. - */ - void performRelinking(const AbstractRelinker& relinker); + /** + * \note It's up to the caller to make sure different paths aren't collapsed into one. + * Having the same page more the once in a project is not supported by Scan Tailor. + */ + void performRelinking(const AbstractRelinker& relinker); - void setLayoutTypeFor(const ImageId& image_id, LayoutType layout); + void setLayoutTypeFor(const ImageId& image_id, LayoutType layout); - void setLayoutTypeForAllPages(LayoutType layout); + void setLayoutTypeForAllPages(LayoutType layout); - void autoSetLayoutTypeFor(const ImageId& image_id, OrthogonalRotation rotation); + void autoSetLayoutTypeFor(const ImageId& image_id, OrthogonalRotation rotation); - void updateImageMetadata(const ImageId& image_id, const ImageMetadata& metadata); + void updateImageMetadata(const ImageId& image_id, const ImageMetadata& metadata); - static int adviseNumberOfLogicalPages(const ImageMetadata& metadata, OrthogonalRotation rotation); + static int adviseNumberOfLogicalPages(const ImageMetadata& metadata, OrthogonalRotation rotation); - int numImages() const; + int numImages() const; - /** - * \brief Insert an image before or after the existing one. - * - * The caller has to make sure he is not inserting an image that already - * exists in this ProjectPages. Requesting to insert a new image - * BEFORE the null one is legal and means inserting it at the end. - * - * \param new_image The image to insert. - * \param before_or_after Whether to insert before or after another image. - * \param existing The image we are inserting before or after. - * \param view This one only affects what is returned. - * \return One or two (or zero, if existing image wasn't found) logical - * page descriptors. If two are returned, they will be returned - * in the order dependent on the layout direction specified - * at construction time. - */ - std::vector insertImage(const ImageInfo& new_image, - BeforeOrAfter before_or_after, - const ImageId& existing, - PageView view); + /** + * \brief Insert an image before or after the existing one. + * + * The caller has to make sure he is not inserting an image that already + * exists in this ProjectPages. Requesting to insert a new image + * BEFORE the null one is legal and means inserting it at the end. + * + * \param new_image The image to insert. + * \param before_or_after Whether to insert before or after another image. + * \param existing The image we are inserting before or after. + * \param view This one only affects what is returned. + * \return One or two (or zero, if existing image wasn't found) logical + * page descriptors. If two are returned, they will be returned + * in the order dependent on the layout direction specified + * at construction time. + */ + std::vector insertImage(const ImageInfo& new_image, + BeforeOrAfter before_or_after, + const ImageId& existing, + PageView view); - void removePages(const std::set& pages); + void removePages(const std::set& pages); - /** - * \brief Unremoves half-a-page, if the other half is still present. - * - * \param page_id Left or right sub-page to restore. - * \return A PageInfo corresponding to the page restored or - * a null PageInfo if restoring failed. - */ - PageInfo unremovePage(const PageId& page_id); + /** + * \brief Unremoves half-a-page, if the other half is still present. + * + * \param page_id Left or right sub-page to restore. + * \return A PageInfo corresponding to the page restored or + * a null PageInfo if restoring failed. + */ + PageInfo unremovePage(const PageId& page_id); - /** - * \brief Check if all DPIs are OK, in terms of ImageMetadata::isDpiOK() - * - * \return true if all DPIs are OK, false if not. - */ - bool validateDpis() const; + /** + * \brief Check if all DPIs are OK, in terms of ImageMetadata::isDpiOK() + * + * \return true if all DPIs are OK, false if not. + */ + bool validateDpis() const; - std::vector toImageFileInfo() const; + std::vector toImageFileInfo() const; - void updateMetadataFrom(const std::vector& files); + void updateMetadataFrom(const std::vector& files); -signals: + signals: - void modified(); + void modified(); -private: - struct ImageDesc { - ImageId id; - ImageMetadata metadata; - int numLogicalPages; // 1 or 2 - bool leftHalfRemoved; // Both can't be true, and if one is true, - bool rightHalfRemoved; // then numLogicalPages is 1. + private: + struct ImageDesc { + ImageId id; + ImageMetadata metadata; + int numLogicalPages; // 1 or 2 + bool leftHalfRemoved; // Both can't be true, and if one is true, + bool rightHalfRemoved; // then numLogicalPages is 1. - explicit ImageDesc(const ImageInfo& image_info); + explicit ImageDesc(const ImageInfo& image_info); - ImageDesc(const ImageId& id, const ImageMetadata& metadata, Pages pages); + ImageDesc(const ImageId& id, const ImageMetadata& metadata, Pages pages); - PageId::SubPage logicalPageToSubPage(int logical_page, const PageId::SubPage* sub_pages_in_order) const; - }; + PageId::SubPage logicalPageToSubPage(int logical_page, const PageId::SubPage* sub_pages_in_order) const; + }; - void initSubPagesInOrder(Qt::LayoutDirection layout_direction); + void initSubPagesInOrder(Qt::LayoutDirection layout_direction); - void setLayoutTypeForImpl(const ImageId& image_id, LayoutType layout, bool* modified); + void setLayoutTypeForImpl(const ImageId& image_id, LayoutType layout, bool* modified); - void setLayoutTypeForAllPagesImpl(LayoutType layout, bool* modified); + void setLayoutTypeForAllPagesImpl(LayoutType layout, bool* modified); - void autoSetLayoutTypeForImpl(const ImageId& image_id, OrthogonalRotation rotation, bool* modified); + void autoSetLayoutTypeForImpl(const ImageId& image_id, OrthogonalRotation rotation, bool* modified); - void updateImageMetadataImpl(const ImageId& image_id, const ImageMetadata& metadata, bool* modified); + void updateImageMetadataImpl(const ImageId& image_id, const ImageMetadata& metadata, bool* modified); - std::vector insertImageImpl(const ImageInfo& new_image, - BeforeOrAfter before_or_after, - const ImageId& existing, - PageView view, - bool& modified); + std::vector insertImageImpl(const ImageInfo& new_image, + BeforeOrAfter before_or_after, + const ImageId& existing, + PageView view, + bool& modified); - void removePagesImpl(const std::set& pages, bool& modified); + void removePagesImpl(const std::set& pages, bool& modified); - PageInfo unremovePageImpl(const PageId& page_id, bool& modified); + PageInfo unremovePageImpl(const PageId& page_id, bool& modified); - mutable QMutex m_mutex; - std::vector m_images; - PageId::SubPage m_subPagesInOrder[2]; + mutable QMutex m_mutex; + std::vector m_images; + PageId::SubPage m_subPagesInOrder[2]; }; diff --git a/ProjectReader.cpp b/ProjectReader.cpp index c2f362f53..e7920c87d 100644 --- a/ProjectReader.cpp +++ b/ProjectReader.cpp @@ -17,306 +17,306 @@ */ #include "ProjectReader.h" -#include "ProjectPages.h" -#include "FileNameDisambiguator.h" +#include +#include #include "AbstractFilter.h" +#include "FileNameDisambiguator.h" +#include "ProjectPages.h" #include "XmlUnmarshaller.h" #include "version.h" -#include -#include -ProjectReader::ProjectReader(const QDomDocument& doc) : m_doc(doc), m_ptrDisambiguator(new FileNameDisambiguator) { - QDomElement project_el(m_doc.documentElement()); +ProjectReader::ProjectReader(const QDomDocument& doc) : m_doc(doc), m_disambiguator(new FileNameDisambiguator) { + QDomElement project_el(m_doc.documentElement()); + + m_version = project_el.attribute("version"); + if (m_version.isNull() || (m_version.toInt() != PROJECT_VERSION)) { + return; + } + + m_outDir = project_el.attribute("outputDirectory"); + + Qt::LayoutDirection layout_direction = Qt::LeftToRight; + if (project_el.attribute("layoutDirection") == "RTL") { + layout_direction = Qt::RightToLeft; + } + + const QDomElement dirs_el(project_el.namedItem("directories").toElement()); + if (dirs_el.isNull()) { + return; + } + processDirectories(dirs_el); + + const QDomElement files_el(project_el.namedItem("files").toElement()); + if (files_el.isNull()) { + return; + } + processFiles(files_el); + + const QDomElement images_el(project_el.namedItem("images").toElement()); + if (images_el.isNull()) { + return; + } + processImages(images_el, layout_direction); + + const QDomElement pages_el(project_el.namedItem("pages").toElement()); + if (pages_el.isNull()) { + return; + } + processPages(pages_el); + // Load naming disambiguator. This needs to be done after processing pages. + const QDomElement disambig_el(project_el.namedItem("file-name-disambiguation").toElement()); + m_disambiguator + = make_intrusive(disambig_el, boost::bind(&ProjectReader::expandFilePath, this, _1)); +} - m_version = project_el.attribute("version"); - if (m_version.isNull() || (m_version.toInt() != PROJECT_VERSION)) { - return; - } +ProjectReader::~ProjectReader() = default; - m_outDir = project_el.attribute("outputDirectory"); +void ProjectReader::readFilterSettings(const std::vector& filters) const { + QDomElement project_el(m_doc.documentElement()); + QDomElement filters_el(project_el.namedItem("filters").toElement()); + + auto it(filters.begin()); + const auto end(filters.end()); + for (; it != end; ++it) { + (*it)->loadSettings(*this, filters_el); + } +} - Qt::LayoutDirection layout_direction = Qt::LeftToRight; - if (project_el.attribute("layoutDirection") == "RTL") { - layout_direction = Qt::RightToLeft; - } +void ProjectReader::processDirectories(const QDomElement& dirs_el) { + const QString dir_tag_name("directory"); - const QDomElement dirs_el(project_el.namedItem("directories").toElement()); - if (dirs_el.isNull()) { - return; + QDomNode node(dirs_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } - processDirectories(dirs_el); - - const QDomElement files_el(project_el.namedItem("files").toElement()); - if (files_el.isNull()) { - return; + if (node.nodeName() != dir_tag_name) { + continue; } - processFiles(files_el); + QDomElement el(node.toElement()); - const QDomElement images_el(project_el.namedItem("images").toElement()); - if (images_el.isNull()) { - return; + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; } - processImages(images_el, layout_direction); - const QDomElement pages_el(project_el.namedItem("pages").toElement()); - if (pages_el.isNull()) { - return; + const QString path(el.attribute("path")); + if (path.isEmpty()) { + continue; } - processPages(pages_el); - // Load naming disambiguator. This needs to be done after processing pages. - const QDomElement disambig_el(project_el.namedItem("file-name-disambiguation").toElement()); - m_ptrDisambiguator - = make_intrusive(disambig_el, boost::bind(&ProjectReader::expandFilePath, this, _1)); + + m_dirMap.insert(DirMap::value_type(id, path)); + } } -ProjectReader::~ProjectReader() = default; +void ProjectReader::processFiles(const QDomElement& files_el) { + const QString file_tag_name("file"); -void ProjectReader::readFilterSettings(const std::vector& filters) const { - QDomElement project_el(m_doc.documentElement()); - QDomElement filters_el(project_el.namedItem("filters").toElement()); + QDomNode node(files_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != file_tag_name) { + continue; + } + QDomElement el(node.toElement()); - auto it(filters.begin()); - const auto end(filters.end()); - for (; it != end; ++it) { - (*it)->loadSettings(*this, filters_el); + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + const int dir_id = el.attribute("dirId").toInt(&ok); + if (!ok) { + continue; } -} -void ProjectReader::processDirectories(const QDomElement& dirs_el) { - const QString dir_tag_name("directory"); - - QDomNode node(dirs_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != dir_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const QString path(el.attribute("path")); - if (path.isEmpty()) { - continue; - } - - m_dirMap.insert(DirMap::value_type(id, path)); + const QString name(el.attribute("name")); + if (name.isEmpty()) { + continue; } -} -void ProjectReader::processFiles(const QDomElement& files_el) { - const QString file_tag_name("file"); - - QDomNode node(files_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != file_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - const int dir_id = el.attribute("dirId").toInt(&ok); - if (!ok) { - continue; - } - - const QString name(el.attribute("name")); - if (name.isEmpty()) { - continue; - } - - const QString dir_path(getDirPath(dir_id)); - if (dir_path.isEmpty()) { - continue; - } - - // Backwards compatibility. - const bool compat_multi_page = (el.attribute("multiPage") == "1"); - - const QString file_path(QDir(dir_path).filePath(name)); - const FileRecord rec(file_path, compat_multi_page); - m_fileMap.insert(FileMap::value_type(id, rec)); + const QString dir_path(getDirPath(dir_id)); + if (dir_path.isEmpty()) { + continue; } + + // Backwards compatibility. + const bool compat_multi_page = (el.attribute("multiPage") == "1"); + + const QString file_path(QDir(dir_path).filePath(name)); + const FileRecord rec(file_path, compat_multi_page); + m_fileMap.insert(FileMap::value_type(id, rec)); + } } // ProjectReader::processFiles void ProjectReader::processImages(const QDomElement& images_el, const Qt::LayoutDirection layout_direction) { - const QString image_tag_name("image"); - - std::vector images; - - QDomNode node(images_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != image_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - const int sub_pages = el.attribute("subPages").toInt(&ok); - if (!ok) { - continue; - } - const int file_id = el.attribute("fileId").toInt(&ok); - if (!ok) { - continue; - } - const int file_image = el.attribute("fileImage").toInt(&ok); - if (!ok) { - continue; - } - - const QString removed(el.attribute("removed")); - const bool left_half_removed = (removed == "L"); - const bool right_half_removed = (removed == "R"); - - const FileRecord file_record(getFileRecord(file_id)); - if (file_record.filePath.isEmpty()) { - continue; - } - const ImageId image_id(file_record.filePath, file_image + int(file_record.compatMultiPage)); - const ImageMetadata metadata(processImageMetadata(el)); - const ImageInfo image_info(image_id, metadata, sub_pages, left_half_removed, right_half_removed); - - images.push_back(image_info); - m_imageMap.insert(ImageMap::value_type(id, image_info)); + const QString image_tag_name("image"); + + std::vector images; + + QDomNode node(images_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != image_tag_name) { + continue; } + QDomElement el(node.toElement()); - if (!images.empty()) { - m_ptrPages = make_intrusive(images, layout_direction); + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; } + const int sub_pages = el.attribute("subPages").toInt(&ok); + if (!ok) { + continue; + } + const int file_id = el.attribute("fileId").toInt(&ok); + if (!ok) { + continue; + } + const int file_image = el.attribute("fileImage").toInt(&ok); + if (!ok) { + continue; + } + + const QString removed(el.attribute("removed")); + const bool left_half_removed = (removed == "L"); + const bool right_half_removed = (removed == "R"); + + const FileRecord file_record(getFileRecord(file_id)); + if (file_record.filePath.isEmpty()) { + continue; + } + const ImageId image_id(file_record.filePath, file_image + int(file_record.compatMultiPage)); + const ImageMetadata metadata(processImageMetadata(el)); + const ImageInfo image_info(image_id, metadata, sub_pages, left_half_removed, right_half_removed); + + images.push_back(image_info); + m_imageMap.insert(ImageMap::value_type(id, image_info)); + } + + if (!images.empty()) { + m_pages = make_intrusive(images, layout_direction); + } } // ProjectReader::processImages ImageMetadata ProjectReader::processImageMetadata(const QDomElement& image_el) { - QSize size; - Dpi dpi; + QSize size; + Dpi dpi; + + const QDomElement size_el(image_el.namedItem("size").toElement()); + if (!size_el.isNull()) { + size = XmlUnmarshaller::size(size_el); + } + const QDomElement dpi_el(image_el.namedItem("dpi").toElement()); + if (!dpi_el.isNull()) { + dpi = XmlUnmarshaller::dpi(dpi_el); + } + + return ImageMetadata(size, dpi); +} - const QDomElement size_el(image_el.namedItem("size").toElement()); - if (!size_el.isNull()) { - size = XmlUnmarshaller::size(size_el); +void ProjectReader::processPages(const QDomElement& pages_el) { + const QString page_tag_name("page"); + + QDomNode node(pages_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } - const QDomElement dpi_el(image_el.namedItem("dpi").toElement()); - if (!dpi_el.isNull()) { - dpi = XmlUnmarshaller::dpi(dpi_el); + if (node.nodeName() != page_tag_name) { + continue; } + QDomElement el(node.toElement()); - return ImageMetadata(size, dpi); -} + bool ok = true; -void ProjectReader::processPages(const QDomElement& pages_el) { - const QString page_tag_name("page"); - - QDomNode node(pages_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const int image_id = el.attribute("imageId").toInt(&ok); - if (!ok) { - continue; - } - - const PageId::SubPage sub_page = PageId::subPageFromString(el.attribute("subPage"), &ok); - if (!ok) { - continue; - } - - const ImageInfo image(getImageInfo(image_id)); - if (image.id().filePath().isEmpty()) { - continue; - } - - const PageId page_id(image.id(), sub_page); - m_pageMap.insert(PageMap::value_type(id, page_id)); - - if (el.attribute("selected") == "selected") { - m_selectedPage.set(page_id, PAGE_VIEW); - } + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const int image_id = el.attribute("imageId").toInt(&ok); + if (!ok) { + continue; + } + + const PageId::SubPage sub_page = PageId::subPageFromString(el.attribute("subPage"), &ok); + if (!ok) { + continue; + } + + const ImageInfo image(getImageInfo(image_id)); + if (image.id().filePath().isEmpty()) { + continue; + } + + const PageId page_id(image.id(), sub_page); + m_pageMap.insert(PageMap::value_type(id, page_id)); + + if (el.attribute("selected") == "selected") { + m_selectedPage.set(page_id, PAGE_VIEW); } + } } // ProjectReader::processPages QString ProjectReader::getDirPath(const int id) const { - const auto it(m_dirMap.find(id)); - if (it != m_dirMap.end()) { - return it->second; - } + const auto it(m_dirMap.find(id)); + if (it != m_dirMap.end()) { + return it->second; + } - return QString(); + return QString(); } ProjectReader::FileRecord ProjectReader::getFileRecord(int id) const { - const auto it(m_fileMap.find(id)); - if (it != m_fileMap.end()) { - return it->second; - } + const auto it(m_fileMap.find(id)); + if (it != m_fileMap.end()) { + return it->second; + } - return FileRecord(); + return FileRecord(); } QString ProjectReader::expandFilePath(const QString& path_shorthand) const { - bool ok = false; - const int file_id = path_shorthand.toInt(&ok); - if (!ok) { - return QString(); - } + bool ok = false; + const int file_id = path_shorthand.toInt(&ok); + if (!ok) { + return QString(); + } - return getFileRecord(file_id).filePath; + return getFileRecord(file_id).filePath; } ImageInfo ProjectReader::getImageInfo(int id) const { - auto it(m_imageMap.find(id)); - if (it != m_imageMap.end()) { - return it->second; - } + auto it(m_imageMap.find(id)); + if (it != m_imageMap.end()) { + return it->second; + } - return ImageInfo(); + return ImageInfo(); } ImageId ProjectReader::imageId(const int numeric_id) const { - auto it(m_imageMap.find(numeric_id)); - if (it != m_imageMap.end()) { - return it->second.id(); - } + auto it(m_imageMap.find(numeric_id)); + if (it != m_imageMap.end()) { + return it->second.id(); + } - return ImageId(); + return ImageId(); } PageId ProjectReader::pageId(int numeric_id) const { - auto it(m_pageMap.find(numeric_id)); - if (it != m_pageMap.end()) { - return it->second; - } + auto it(m_pageMap.find(numeric_id)); + if (it != m_pageMap.end()) { + return it->second; + } - return PageId(); + return PageId(); } diff --git a/ProjectReader.h b/ProjectReader.h index 1b580b5e6..63f8d7054 100644 --- a/ProjectReader.h +++ b/ProjectReader.h @@ -19,17 +19,17 @@ #ifndef PROJECTREADER_H_ #define PROJECTREADER_H_ +#include +#include +#include +#include +#include #include "ImageId.h" -#include "PageId.h" #include "ImageInfo.h" #include "ImageMetadata.h" +#include "PageId.h" #include "SelectedPage.h" #include "intrusive_ptr.h" -#include -#include -#include -#include -#include class QDomElement; class ProjectPages; @@ -37,89 +37,75 @@ class FileNameDisambiguator; class AbstractFilter; class ProjectReader { -public: - typedef intrusive_ptr FilterPtr; + public: + typedef intrusive_ptr FilterPtr; - explicit ProjectReader(const QDomDocument& doc); + explicit ProjectReader(const QDomDocument& doc); - ~ProjectReader(); + ~ProjectReader(); - void readFilterSettings(const std::vector& filters) const; + void readFilterSettings(const std::vector& filters) const; - bool success() const { - return (m_ptrPages != nullptr); - } + bool success() const { return (m_pages != nullptr); } - const QString& outputDirectory() const { - return m_outDir; - } + const QString& outputDirectory() const { return m_outDir; } - const QString& getVersion() const { - return m_version; - } + const QString& getVersion() const { return m_version; } - const intrusive_ptr& pages() const { - return m_ptrPages; - } + const intrusive_ptr& pages() const { return m_pages; } - const SelectedPage& selectedPage() const { - return m_selectedPage; - } + const SelectedPage& selectedPage() const { return m_selectedPage; } - const intrusive_ptr& namingDisambiguator() const { - return m_ptrDisambiguator; - } + const intrusive_ptr& namingDisambiguator() const { return m_disambiguator; } - ImageId imageId(int numeric_id) const; + ImageId imageId(int numeric_id) const; - PageId pageId(int numeric_id) const; + PageId pageId(int numeric_id) const; -private: - struct FileRecord { - QString filePath; - bool compatMultiPage; // Backwards compatibility. + private: + struct FileRecord { + QString filePath; + bool compatMultiPage; // Backwards compatibility. - FileRecord() : compatMultiPage(false) { - } + FileRecord() : compatMultiPage(false) {} - FileRecord(const QString& file_path, bool compat_multi_page) - : filePath(file_path), compatMultiPage(compat_multi_page) { - } - }; + FileRecord(const QString& file_path, bool compat_multi_page) + : filePath(file_path), compatMultiPage(compat_multi_page) {} + }; - typedef std::unordered_map DirMap; - typedef std::unordered_map FileMap; - typedef std::unordered_map ImageMap; - typedef std::unordered_map PageMap; + typedef std::unordered_map DirMap; + typedef std::unordered_map FileMap; + typedef std::unordered_map ImageMap; + typedef std::unordered_map PageMap; - void processDirectories(const QDomElement& dirs_el); + void processDirectories(const QDomElement& dirs_el); - void processFiles(const QDomElement& files_el); + void processFiles(const QDomElement& files_el); - void processImages(const QDomElement& images_el, Qt::LayoutDirection layout_direction); + void processImages(const QDomElement& images_el, Qt::LayoutDirection layout_direction); - ImageMetadata processImageMetadata(const QDomElement& image_el); + ImageMetadata processImageMetadata(const QDomElement& image_el); - void processPages(const QDomElement& pages_el); + void processPages(const QDomElement& pages_el); - QString getDirPath(int id) const; + QString getDirPath(int id) const; - FileRecord getFileRecord(int id) const; + FileRecord getFileRecord(int id) const; - QString expandFilePath(const QString& path_shorthand) const; + QString expandFilePath(const QString& path_shorthand) const; - ImageInfo getImageInfo(int id) const; + ImageInfo getImageInfo(int id) const; - QDomDocument m_doc; - QString m_outDir; - QString m_version; - DirMap m_dirMap; - FileMap m_fileMap; - ImageMap m_imageMap; - PageMap m_pageMap; - SelectedPage m_selectedPage; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrDisambiguator; + QDomDocument m_doc; + QString m_outDir; + QString m_version; + DirMap m_dirMap; + FileMap m_fileMap; + ImageMap m_imageMap; + PageMap m_pageMap; + SelectedPage m_selectedPage; + intrusive_ptr m_pages; + intrusive_ptr m_disambiguator; }; diff --git a/ProjectWriter.cpp b/ProjectWriter.cpp index 9b0fe2268..d07850800 100644 --- a/ProjectWriter.cpp +++ b/ProjectWriter.cpp @@ -17,19 +17,19 @@ */ #include "ProjectWriter.h" -#include "ProjectPages.h" -#include "PageView.h" -#include "PageInfo.h" -#include "PageId.h" -#include "ImageId.h" -#include "ImageMetadata.h" +#include +#include +#include +#include #include "AbstractFilter.h" #include "FileNameDisambiguator.h" +#include "ImageId.h" +#include "ImageMetadata.h" +#include "PageId.h" +#include "PageInfo.h" +#include "PageView.h" +#include "ProjectPages.h" #include "version.h" -#include -#include -#include -#include #ifndef Q_MOC_RUN @@ -37,229 +37,228 @@ #endif -#include #include +#include ProjectWriter::ProjectWriter(const intrusive_ptr& page_sequence, const SelectedPage& selected_page, const OutputFileNameGenerator& out_file_name_gen) - : m_pageSequence(page_sequence->toPageSequence(PAGE_VIEW)), - m_outFileNameGen(out_file_name_gen), - m_selectedPage(selected_page), - m_layoutDirection(page_sequence->layoutDirection()) { - int next_id = 1; - for (const PageInfo& page : m_pageSequence) { - const PageId& page_id = page.id(); - const ImageId& image_id = page_id.imageId(); - const QString& file_path = image_id.filePath(); - const QFileInfo file_info(file_path); - const QString dir_path(file_info.absolutePath()); - - m_metadataByImage[image_id] = page.metadata(); - - if (m_dirs.insert(Directory(dir_path, next_id)).second) { - ++next_id; - } - - if (m_files.insert(File(file_path, next_id)).second) { - ++next_id; - } - - if (m_images.insert(Image(page, next_id)).second) { - ++next_id; - } - - if (m_pages.insert(Page(page_id, next_id)).second) { - ++next_id; - } + : m_pageSequence(page_sequence->toPageSequence(PAGE_VIEW)), + m_outFileNameGen(out_file_name_gen), + m_selectedPage(selected_page), + m_layoutDirection(page_sequence->layoutDirection()) { + int next_id = 1; + for (const PageInfo& page : m_pageSequence) { + const PageId& page_id = page.id(); + const ImageId& image_id = page_id.imageId(); + const QString& file_path = image_id.filePath(); + const QFileInfo file_info(file_path); + const QString dir_path(file_info.absolutePath()); + + m_metadataByImage[image_id] = page.metadata(); + + if (m_dirs.insert(Directory(dir_path, next_id)).second) { + ++next_id; } -} -ProjectWriter::~ProjectWriter() = default; + if (m_files.insert(File(file_path, next_id)).second) { + ++next_id; + } -bool ProjectWriter::write(const QString& file_path, const std::vector& filters) const { - QDomDocument doc; - QDomElement root_el(doc.createElement("project")); - doc.appendChild(root_el); - root_el.setAttribute("version", PROJECT_VERSION); - root_el.setAttribute("outputDirectory", m_outFileNameGen.outDir()); - root_el.setAttribute("layoutDirection", m_layoutDirection == Qt::LeftToRight ? "LTR" : "RTL"); - - root_el.appendChild(processDirectories(doc)); - root_el.appendChild(processFiles(doc)); - root_el.appendChild(processImages(doc)); - root_el.appendChild(processPages(doc)); - root_el.appendChild(m_outFileNameGen.disambiguator()->toXml(doc, "file-name-disambiguation", - boost::bind(&ProjectWriter::packFilePath, this, _1))); - - QDomElement filters_el(doc.createElement("filters")); - root_el.appendChild(filters_el); - auto it(filters.begin()); - const auto end(filters.end()); - for (; it != end; ++it) { - filters_el.appendChild((*it)->saveSettings(*this, doc)); + if (m_images.insert(Image(page, next_id)).second) { + ++next_id; } - QFile file(file_path); - if (file.open(QIODevice::WriteOnly)) { - QTextStream strm(&file); - doc.save(strm, 2); - return true; + if (m_pages.insert(Page(page_id, next_id)).second) { + ++next_id; } + } +} - return false; +ProjectWriter::~ProjectWriter() = default; + +bool ProjectWriter::write(const QString& file_path, const std::vector& filters) const { + QDomDocument doc; + QDomElement root_el(doc.createElement("project")); + doc.appendChild(root_el); + root_el.setAttribute("version", PROJECT_VERSION); + root_el.setAttribute("outputDirectory", m_outFileNameGen.outDir()); + root_el.setAttribute("layoutDirection", m_layoutDirection == Qt::LeftToRight ? "LTR" : "RTL"); + + root_el.appendChild(processDirectories(doc)); + root_el.appendChild(processFiles(doc)); + root_el.appendChild(processImages(doc)); + root_el.appendChild(processPages(doc)); + root_el.appendChild(m_outFileNameGen.disambiguator()->toXml(doc, "file-name-disambiguation", + boost::bind(&ProjectWriter::packFilePath, this, _1))); + + QDomElement filters_el(doc.createElement("filters")); + root_el.appendChild(filters_el); + auto it(filters.begin()); + const auto end(filters.end()); + for (; it != end; ++it) { + filters_el.appendChild((*it)->saveSettings(*this, doc)); + } + + QFile file(file_path); + if (file.open(QIODevice::WriteOnly)) { + QTextStream strm(&file); + doc.save(strm, 2); + return true; + } + + return false; } // ProjectWriter::write QDomElement ProjectWriter::processDirectories(QDomDocument& doc) const { - QDomElement dirs_el(doc.createElement("directories")); + QDomElement dirs_el(doc.createElement("directories")); - for (const Directory& dir : m_dirs.get()) { - QDomElement dir_el(doc.createElement("directory")); - dir_el.setAttribute("id", dir.numericId); - dir_el.setAttribute("path", dir.path); - dirs_el.appendChild(dir_el); - } + for (const Directory& dir : m_dirs.get()) { + QDomElement dir_el(doc.createElement("directory")); + dir_el.setAttribute("id", dir.numericId); + dir_el.setAttribute("path", dir.path); + dirs_el.appendChild(dir_el); + } - return dirs_el; + return dirs_el; } QDomElement ProjectWriter::processFiles(QDomDocument& doc) const { - QDomElement files_el(doc.createElement("files")); - - for (const File& file : m_files.get()) { - const QFileInfo file_info(file.path); - const QString& dir_path = file_info.absolutePath(); - QDomElement file_el(doc.createElement("file")); - file_el.setAttribute("id", file.numericId); - file_el.setAttribute("dirId", dirId(dir_path)); - file_el.setAttribute("name", file_info.fileName()); - files_el.appendChild(file_el); - } - - return files_el; + QDomElement files_el(doc.createElement("files")); + + for (const File& file : m_files.get()) { + const QFileInfo file_info(file.path); + const QString& dir_path = file_info.absolutePath(); + QDomElement file_el(doc.createElement("file")); + file_el.setAttribute("id", file.numericId); + file_el.setAttribute("dirId", dirId(dir_path)); + file_el.setAttribute("name", file_info.fileName()); + files_el.appendChild(file_el); + } + + return files_el; } QDomElement ProjectWriter::processImages(QDomDocument& doc) const { - QDomElement images_el(doc.createElement("images")); - - for (const Image& image : m_images.get()) { - QDomElement image_el(doc.createElement("image")); - image_el.setAttribute("id", image.numericId); - image_el.setAttribute("subPages", image.numSubPages); - image_el.setAttribute("fileId", fileId(image.id.filePath())); - image_el.setAttribute("fileImage", image.id.page()); - if (image.leftHalfRemoved != image.rightHalfRemoved) { - // Both are not supposed to be removed. - image_el.setAttribute("removed", image.leftHalfRemoved ? "L" : "R"); - } - writeImageMetadata(doc, image_el, image.id); - images_el.appendChild(image_el); + QDomElement images_el(doc.createElement("images")); + + for (const Image& image : m_images.get()) { + QDomElement image_el(doc.createElement("image")); + image_el.setAttribute("id", image.numericId); + image_el.setAttribute("subPages", image.numSubPages); + image_el.setAttribute("fileId", fileId(image.id.filePath())); + image_el.setAttribute("fileImage", image.id.page()); + if (image.leftHalfRemoved != image.rightHalfRemoved) { + // Both are not supposed to be removed. + image_el.setAttribute("removed", image.leftHalfRemoved ? "L" : "R"); } + writeImageMetadata(doc, image_el, image.id); + images_el.appendChild(image_el); + } - return images_el; + return images_el; } void ProjectWriter::writeImageMetadata(QDomDocument& doc, QDomElement& image_el, const ImageId& image_id) const { - auto it(m_metadataByImage.find(image_id)); - assert(it != m_metadataByImage.end()); - const ImageMetadata& metadata = it->second; - - QDomElement size_el(doc.createElement("size")); - size_el.setAttribute("width", metadata.size().width()); - size_el.setAttribute("height", metadata.size().height()); - image_el.appendChild(size_el); - - QDomElement dpi_el(doc.createElement("dpi")); - dpi_el.setAttribute("horizontal", metadata.dpi().horizontal()); - dpi_el.setAttribute("vertical", metadata.dpi().vertical()); - image_el.appendChild(dpi_el); + auto it(m_metadataByImage.find(image_id)); + assert(it != m_metadataByImage.end()); + const ImageMetadata& metadata = it->second; + + QDomElement size_el(doc.createElement("size")); + size_el.setAttribute("width", metadata.size().width()); + size_el.setAttribute("height", metadata.size().height()); + image_el.appendChild(size_el); + + QDomElement dpi_el(doc.createElement("dpi")); + dpi_el.setAttribute("horizontal", metadata.dpi().horizontal()); + dpi_el.setAttribute("vertical", metadata.dpi().vertical()); + image_el.appendChild(dpi_el); } QDomElement ProjectWriter::processPages(QDomDocument& doc) const { - QDomElement pages_el(doc.createElement("pages")); - - const PageId sel_opt_1(m_selectedPage.get(IMAGE_VIEW)); - const PageId sel_opt_2(m_selectedPage.get(PAGE_VIEW)); - - PageId page_left; - PageId page_right; - if (sel_opt_2.subPage() == PageId::SINGLE_PAGE) { - // In case it was split later select first of its pages found - page_left = PageId(sel_opt_2.imageId(), PageId::LEFT_PAGE); - page_right = PageId(sel_opt_2.imageId(), PageId::RIGHT_PAGE); + QDomElement pages_el(doc.createElement("pages")); + + const PageId sel_opt_1(m_selectedPage.get(IMAGE_VIEW)); + const PageId sel_opt_2(m_selectedPage.get(PAGE_VIEW)); + + PageId page_left; + PageId page_right; + if (sel_opt_2.subPage() == PageId::SINGLE_PAGE) { + // In case it was split later select first of its pages found + page_left = PageId(sel_opt_2.imageId(), PageId::LEFT_PAGE); + page_right = PageId(sel_opt_2.imageId(), PageId::RIGHT_PAGE); + } + + + for (const PageInfo& page : m_pageSequence) { + const PageId& page_id = page.id(); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", pageId(page_id)); + page_el.setAttribute("imageId", imageId(page_id.imageId())); + page_el.setAttribute("subPage", page_id.subPageAsString()); + if ((page_id == sel_opt_1) || (page_id == sel_opt_2) || (page_id == page_left) || (page_id == page_right)) { + page_el.setAttribute("selected", "selected"); + page_left = page_right = PageId(); // if one of these match other shouldn't } + pages_el.appendChild(page_el); + } - - for (const PageInfo& page : m_pageSequence) { - const PageId& page_id = page.id(); - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", pageId(page_id)); - page_el.setAttribute("imageId", imageId(page_id.imageId())); - page_el.setAttribute("subPage", page_id.subPageAsString()); - if ((page_id == sel_opt_1) || (page_id == sel_opt_2) || (page_id == page_left) || (page_id == page_right)) { - page_el.setAttribute("selected", "selected"); - page_left = page_right = PageId(); // if one of these match other shouldn't - } - pages_el.appendChild(page_el); - } - - return pages_el; + return pages_el; } // ProjectWriter::processPages int ProjectWriter::dirId(const QString& dir_path) const { - const Directories::const_iterator it(m_dirs.find(dir_path)); - assert(it != m_dirs.end()); - return it->numericId; + const Directories::const_iterator it(m_dirs.find(dir_path)); + assert(it != m_dirs.end()); + return it->numericId; } int ProjectWriter::fileId(const QString& file_path) const { - const Files::const_iterator it(m_files.find(file_path)); - if (it != m_files.end()) { - return it->numericId; - } else { - return -1; - } + const Files::const_iterator it(m_files.find(file_path)); + if (it != m_files.end()) { + return it->numericId; + } else { + return -1; + } } QString ProjectWriter::packFilePath(const QString& file_path) const { - const Files::const_iterator it(m_files.find(file_path)); - if (it != m_files.end()) { - return QString::number(it->numericId); - } else { - return QString(); - } + const Files::const_iterator it(m_files.find(file_path)); + if (it != m_files.end()) { + return QString::number(it->numericId); + } else { + return QString(); + } } int ProjectWriter::imageId(const ImageId& image_id) const { - const Images::const_iterator it(m_images.find(image_id)); - assert(it != m_images.end()); - return it->numericId; + const Images::const_iterator it(m_images.find(image_id)); + assert(it != m_images.end()); + return it->numericId; } int ProjectWriter::pageId(const PageId& page_id) const { - const Pages::const_iterator it(m_pages.find(page_id)); - assert(it != m_pages.end()); - return it->numericId; + const Pages::const_iterator it(m_pages.find(page_id)); + assert(it != m_pages.end()); + return it->numericId; } void ProjectWriter::enumImagesImpl(const VirtualFunction& out) const { - for (const Image& image : m_images.get()) { - out(image.id, image.numericId); - } + for (const Image& image : m_images.get()) { + out(image.id, image.numericId); + } } void ProjectWriter::enumPagesImpl(const VirtualFunction& out) const { - for (const Page& page : m_pages.get()) { - out(page.id, page.numericId); - } + for (const Page& page : m_pages.get()) { + out(page.id, page.numericId); + } } /*======================== ProjectWriter::Image =========================*/ ProjectWriter::Image::Image(const PageInfo& page, int numeric_id) - : id(page.imageId()), - numericId(numeric_id), - numSubPages(page.imageSubPages()), - leftHalfRemoved(page.leftHalfRemoved()), - rightHalfRemoved(page.rightHalfRemoved()) { -} + : id(page.imageId()), + numericId(numeric_id), + numSubPages(page.imageSubPages()), + leftHalfRemoved(page.leftHalfRemoved()), + rightHalfRemoved(page.rightHalfRemoved()) {} diff --git a/ProjectWriter.h b/ProjectWriter.h index 02ca5f1fb..f9deaa781 100644 --- a/ProjectWriter.h +++ b/ProjectWriter.h @@ -19,21 +19,21 @@ #ifndef PROJECTWRITER_H_ #define PROJECTWRITER_H_ -#include "intrusive_ptr.h" -#include "PageSequence.h" -#include "OutputFileNameGenerator.h" +#include +#include +#include +#include +#include +#include +#include +#include #include "ImageId.h" +#include "OutputFileNameGenerator.h" #include "PageId.h" +#include "PageSequence.h" #include "SelectedPage.h" #include "VirtualFunction.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "intrusive_ptr.h" class AbstractFilter; class ProjectPages; @@ -42,142 +42,139 @@ class QDomDocument; class QDomElement; class ProjectWriter { - DECLARE_NON_COPYABLE(ProjectWriter) + DECLARE_NON_COPYABLE(ProjectWriter) -public: - typedef intrusive_ptr FilterPtr; + public: + typedef intrusive_ptr FilterPtr; - ProjectWriter(const intrusive_ptr& page_sequence, - const SelectedPage& selected_page, - const OutputFileNameGenerator& out_file_name_gen); + ProjectWriter(const intrusive_ptr& page_sequence, + const SelectedPage& selected_page, + const OutputFileNameGenerator& out_file_name_gen); - ~ProjectWriter(); + ~ProjectWriter(); - bool write(const QString& file_path, const std::vector& filters) const; + bool write(const QString& file_path, const std::vector& filters) const; - /** - * \p out will be called like this: out(ImageId, numeric_image_id) - */ - template - void enumImages(OutFunc out) const; + /** + * \p out will be called like this: out(ImageId, numeric_image_id) + */ + template + void enumImages(OutFunc out) const; - /** - * \p out will be called like this: out(LogicalPageId, numeric_page_id) - */ - template - void enumPages(OutFunc out) const; + /** + * \p out will be called like this: out(LogicalPageId, numeric_page_id) + */ + template + void enumPages(OutFunc out) const; -private: - struct Directory { - QString path; - int numericId; + private: + struct Directory { + QString path; + int numericId; - Directory(const QString& path, int numeric_id) : path(path), numericId(numeric_id) { - } - }; + Directory(const QString& path, int numeric_id) : path(path), numericId(numeric_id) {} + }; - struct File { - QString path; - int numericId; + struct File { + QString path; + int numericId; - File(const QString& path, int numeric_id) : path(path), numericId(numeric_id) { - } - }; + File(const QString& path, int numeric_id) : path(path), numericId(numeric_id) {} + }; - struct Image { - ImageId id; - int numericId; - int numSubPages; - bool leftHalfRemoved; - bool rightHalfRemoved; + struct Image { + ImageId id; + int numericId; + int numSubPages; + bool leftHalfRemoved; + bool rightHalfRemoved; - Image(const PageInfo& page_info, int numeric_id); - }; + Image(const PageInfo& page_info, int numeric_id); + }; - struct Page { - PageId id; - int numericId; + struct Page { + PageId id; + int numericId; - Page(const PageId& id, int numeric_id) : id(id), numericId(numeric_id) { - } - }; + Page(const PageId& id, int numeric_id) : id(id), numericId(numeric_id) {} + }; - class Sequenced; + class Sequenced; - typedef std::unordered_map MetadataByImage; + typedef std::unordered_map MetadataByImage; - typedef boost::multi_index::multi_index_container< - Directory, - boost::multi_index::indexed_by>, - boost::multi_index::sequenced>>> - Directories; + typedef boost::multi_index::multi_index_container< + Directory, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique>, + boost::multi_index::sequenced>>> + Directories; - typedef boost::multi_index::multi_index_container< - File, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique>, - boost::multi_index::sequenced>>> - Files; + typedef boost::multi_index::multi_index_container< + File, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique>, + boost::multi_index::sequenced>>> + Files; - typedef boost::multi_index::multi_index_container< - Image, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique>, - boost::multi_index::sequenced>>> - Images; + typedef boost::multi_index::multi_index_container< + Image, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique>, + boost::multi_index::sequenced>>> + Images; - typedef boost::multi_index::multi_index_container< - Page, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique>, - boost::multi_index::sequenced>>> - Pages; + typedef boost::multi_index::multi_index_container< + Page, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique>, + boost::multi_index::sequenced>>> + Pages; - QDomElement processDirectories(QDomDocument& doc) const; + QDomElement processDirectories(QDomDocument& doc) const; - QDomElement processFiles(QDomDocument& doc) const; + QDomElement processFiles(QDomDocument& doc) const; - QDomElement processImages(QDomDocument& doc) const; + QDomElement processImages(QDomDocument& doc) const; - QDomElement processPages(QDomDocument& doc) const; + QDomElement processPages(QDomDocument& doc) const; - void writeImageMetadata(QDomDocument& doc, QDomElement& image_el, const ImageId& image_id) const; + void writeImageMetadata(QDomDocument& doc, QDomElement& image_el, const ImageId& image_id) const; - int dirId(const QString& dir_path) const; + int dirId(const QString& dir_path) const; - int fileId(const QString& file_path) const; + int fileId(const QString& file_path) const; - QString packFilePath(const QString& file_path) const; + QString packFilePath(const QString& file_path) const; - int imageId(const ImageId& image_id) const; + int imageId(const ImageId& image_id) const; - int pageId(const PageId& page_id) const; + int pageId(const PageId& page_id) const; - void enumImagesImpl(const VirtualFunction& out) const; + void enumImagesImpl(const VirtualFunction& out) const; - void enumPagesImpl(const VirtualFunction& out) const; + void enumPagesImpl(const VirtualFunction& out) const; - PageSequence m_pageSequence; - OutputFileNameGenerator m_outFileNameGen; - SelectedPage m_selectedPage; - Directories m_dirs; - Files m_files; - Images m_images; - Pages m_pages; - MetadataByImage m_metadataByImage; - Qt::LayoutDirection m_layoutDirection; + PageSequence m_pageSequence; + OutputFileNameGenerator m_outFileNameGen; + SelectedPage m_selectedPage; + Directories m_dirs; + Files m_files; + Images m_images; + Pages m_pages; + MetadataByImage m_metadataByImage; + Qt::LayoutDirection m_layoutDirection; }; -template +template void ProjectWriter::enumImages(Callable out) const { - enumImagesImpl(ProxyFunction(out)); + enumImagesImpl(ProxyFunction(out)); } -template +template void ProjectWriter::enumPages(Callable out) const { - enumPagesImpl(ProxyFunction(out)); + enumPagesImpl(ProxyFunction(out)); } #endif // ifndef PROJECTWRITER_H_ diff --git a/README.md b/README.md index 0804839a1..9a66f6a5a 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,55 @@ -Scan Tailor Advanced -======================== +ScanTailor Advanced +=================== -The Scan Tailor version that merges the features of the `Scan Tailor Featured` and `Scan Tailor Enhanced` versions, -brings new ones and fixes. +The ScanTailor version that merges the features of the `ScanTailor Featured` and `ScanTailor Enhanced` versions, +brings new ones and fixes. #### Contents: * [Description](#description) * [Features](#features) - * [**Scan Tailor Enhanced** features](#scan-tailor-enhanced-features) - * [Auto margins \[improved\]](#auto-margins-improved) - * [Page detect \[reworked\]](#page-detect-reworked) - * [Deviation \[reworked\]](#deviation-reworked) - * [Picture shape \[reworked\]](#picture-shape-reworked) - * [Multi column thumbnails view \[reworked\]](#multi-column-thumbnails-view-reworked) - * [**Scan Tailor Featured** features](#scan-tailor-featured-features) - * [Scan Tailor Featured fixes & improvements](#scan-tailor-featured-fixes--improvements) - * [Line vertical dragging on dewarp](#line-vertical-dragging-on-dewarp) - * [Square picture zones \[reworked\]](#square-picture-zones-reworked) - * [Auto save project \[optimized\]](#auto-save-project-optimized) - * [Quadro Zoner \[reworked\]](#quadro-zoner-reworked) - * [Marginal dewarping](#marginal-dewarping) - * [**Scan Tailor Advanced** features](#scan-tailor-advanced-features) - * [Scan Tailor Advanced fixes & improvements](#scan-tailor-advanced-fixes--improvements) - * [Light and Dark color schemes](#light-and-dark-color-schemes) - * [Multi-threading support for batch processing](#multi-threading-support-for-batch-processing) - * [Full control over settings on output](#full-control-over-settings-on-output) - * [Filling outside areas](#filling-outside-areas) - * [Tiff compression](#tiff-compression) - * [Adaptive binarization](#adaptive-binarization) - * [Splitting output](#splitting-output) - * [Original background](#original-background) - * [Color segmenter and posterization](#color-segmenter-and-posterization) - * [Rectangular picture shape](#rectangular-picture-shape) - * [Page area](#page-area) - * [New zone interaction modes](#new-zone-interaction-modes) - * [Saving zoom and focus on switching output tabs](#saving-zoom-and-focus-on-switching-output-tabs) - * [Measurement units system](#measurement-units-system) - * [Status bar panel](#status-bar-panel) - * [Default parameters](#default-parameters) - * [Collapsible filter options](#collapsible-filter-options) - * [Auto adjusting content area](#auto-adjusting-content-area) + * [**ScanTailor Enhanced**](#scantailor-enhanced) + * [Auto margins \[improved\]](#auto-margins-improved) + * [Page detect \[reworked\]](#page-detect-reworked) + * [Deviation \[reworked\]](#deviation-reworked) + * [Picture shape \[reworked\]](#picture-shape-reworked) + * [Multi column thumbnails view \[reworked\]](#multi-column-thumbnails-view-reworked) + * [**ScanTailor Featured**](#scantailor-featured) + * [ScanTailor Featured fixes & improvements](#scantailor-featured-fixes--improvements) + * [Line vertical dragging on dewarp](#line-vertical-dragging-on-dewarp) + * [Square picture zones \[reworked\]](#square-picture-zones-reworked) + * [Auto save project \[optimized\]](#auto-save-project-optimized) + * [Quadro Zoner \[reworked\]](#quadro-zoner-reworked) + * [Marginal dewarping](#marginal-dewarping) + * [**ScanTailor Universal**](#scantailor-universal) + * [ScanTailor Universal fixes & improvements](#scantailor-universal-fixes--improvements) + * [**ScanTailor Advanced**](#scantailor-advanced-features) + * [ScanTailor Advanced fixes & improvements](#scantailor-advanced-fixes--improvements) + * [Light and Dark color schemes](#light-and-dark-color-schemes) + * [Multi-threading support for batch processing](#multi-threading-support-for-batch-processing) + * [Full control over settings on output](#full-control-over-settings-on-output) + * [Filling outside areas](#filling-outside-areas) + * [Tiff compression](#tiff-compression) + * [Adaptive binarization](#adaptive-binarization) + * [Splitting output](#splitting-output) + * [Original background](#original-background) + * [Color segmenter and posterization](#color-segmenter-and-posterization) + * [Rectangular picture shape](#rectangular-picture-shape) + * [New zone interaction modes](#new-zone-interaction-modes) + * [Saving zoom and focus on switching output tabs](#saving-zoom-and-focus-on-switching-output-tabs) + * [Measurement units system](#measurement-units-system) + * [Status bar panel](#status-bar-panel) + * [Default parameters](#default-parameters) + * [Collapsible filter options](#collapsible-filter-options) + * [Auto adjusting content area](#auto-adjusting-content-area) + * [Black on white detection](#black-on-white-detection) + * [Guides](#guides) * [Building](#building) Description ------------ -Scan Tailor is an interactive post-processing tool for scanned pages. +ScanTailor is an interactive post-processing tool for scanned pages. It performs operations such as: - [page splitting](https://github.com/scantailor/scantailor/wiki/Split-Pages), - [deskewing](https://github.com/scantailor/scantailor/wiki/Deskew), @@ -55,285 +58,327 @@ It performs operations such as: - ... and others. You give it raw scans, and you get pages ready to be printed or assembled into a PDF - or [DjVu](http://elpa.gnu.org/packages/djvu.html) file. Scanning, optical character recognition, - and assembling multi-page documents are out of scope of this project. +or [DjVu](http://elpa.gnu.org/packages/djvu.html) file. Scanning, optical character recognition, +and assembling multi-page documents are out of scope of this project. Features ---------- -#### **Scan Tailor Enhanced** features +#### ScanTailor Enhanced * ##### Auto margins \[improved\] - Auto margins feature allows keep page content on original place. In the Margins step - you can choose from Auto, Manual (default) and Original mode. The manual mode - is the original one. Auto mode try to decide if it is better to align page top, - bottom or center. Original mode keeps page on their vertical original position. - - *This feature has been improved. See [page area](#page-area) feature description.* - *Also see [Scan Tailor Advanced fixes & improvements](#scan-tailor-advanced-fixes--improvements)* + Auto margins feature allows keep page content on original place. In the Margins step + you can choose from Auto, Manual (default) and Original mode. The manual mode + is the original one. Auto mode try to decide if it is better to align page top, + bottom or center. Original mode keeps page on their vertical original position. + + *This feature has been improved. See [page area](#page-area) feature description.* + *Also see [ScanTailor Advanced fixes & improvements](#scantailor-advanced-fixes--improvements)* * ##### Page detect \[reworked\] - Page detect feature allows detect page in black margins or switch off page content - detection and keep original page layout. - - *This feature has been reworked and is now a part of the [page area](#page-area) feature.* + Page detect feature allows detect page in black margins or switch off page content + detection and keep original page layout. + + *This feature has been reworked.* + *See [ScanTailor Advanced fixes & improvements](#scantailor-advanced-fixes--improvements) for more information.* * ##### Deviation \[reworked\] - Deviation feature enables highlighting of different pages. Highlighted in red are pages - from Deskew filter with too high skew, from Select Content filter pages with different - size of content and in Margins filter are highlighted pages which does not match others. - - *This feature has been reworked. See [Scan Tailor Advanced fixes & improvements](#scan-tailor-advanced-fixes--improvements) for more information.* + Deviation feature enables highlighting of different pages. Highlighted in red are pages + from Deskew filter with too high skew, from Select Content filter pages with different + size of content and in Margins filter are highlighted pages which does not match others. + + *This feature has been reworked. See [ScanTailor Advanced fixes & improvements](#scantailor-advanced-fixes--improvements) for more information.* * ##### Picture shape \[reworked\] - Picture shape feature adds option for mixed pages to choose from free shape and rectangular - shape images. This patch does not improve the original algoritm but creates from the - detected "blobs" rectangular shapes and the rectangles that intersects joins to one. - - *This feature has been reworked. See [rectangular picture shape](#rectangular-picture-shape) feature description.* + Picture shape feature adds option for mixed pages to choose from free shape and rectangular + shape images. This patch does not improve the original algoritm but creates from the + detected "blobs" rectangular shapes and the rectangles that intersects joins to one. + + *This feature has been reworked. See [rectangular picture shape](#rectangular-picture-shape) feature description.* * ##### Multi column thumbnails view \[reworked\] - This allows to expand and un-dock thumbnails view to see more thumbnails at a time. - - *This feature had performance and drawing issues and has been reworked.* + This allows to expand and un-dock thumbnails view to see more thumbnails at a time. + + *This feature had performance and drawing issues and has been reworked.* -#### **Scan Tailor Featured** features - -* ##### Scan Tailor Featured fixes & improvements - * Deleted 3 Red Points - The 3 central red points on the topmost (bottom-most) horizontal blue line of the dewarping - mesh are now eliminated. - * Manual dewarping mode auto switch - The dewarping mode is now set to MANUAL (from OFF) after the user has moved the dewarping mesh. - * Auto dewarping vertical half correction - This patch corrects the original auto-dewarping in half - the cases when it fails. If the vertical content boundary angle (calculated by auto-dewarping) - exceeds an empirical value (2.75 degrees from vertical), the patch adds a new point to - the distortion model (with the coordinates equal to the neighboring points) to make - this boundary vertical. The patch works ONLY for the linear end of the top (bottom) - horizontal line of the blue mesh (and not for the opposite curved end). +#### ScanTailor Featured + +* ##### ScanTailor Featured fixes & improvements + * Deleted 3 Red Points. + The 3 central red points on the topmost (bottom-most) horizontal blue line of the dewarping + mesh are now eliminated. + * Manual dewarping mode auto switch. + The dewarping mode is now set to MANUAL (from OFF) after the user has moved the dewarping mesh. + * Auto dewarping vertical half correction. + This patch corrects the original auto-dewarping in half + the cases when it fails. If the vertical content boundary angle (calculated by auto-dewarping) + exceeds an empirical value (2.75 degrees from vertical), the patch adds a new point to + the distortion model (with the coordinates equal to the neighboring points) to make + this boundary vertical. The patch works ONLY for the linear end of the top (bottom) + horizontal line of the blue mesh (and not for the opposite curved end). * ##### Line vertical dragging on dewarp - You can move the topmost (bottom-most) horizontal blue line of the dewarping mesh up and - down as a whole - if you grab it at the most left (right) red point - holding down the CTRL key. + You can move the topmost (bottom-most) horizontal blue line of the dewarping mesh up and + down as a whole - if you grab it at the most left (right) red point - holding down the CTRL key. * ##### Square picture zones \[reworked\] - You can create the rectangular picture zones - holding down the CTRL key. - You can move the (rectangular) picture zones corners in an orthogonal manner - holding down the CTRL key. - - *This feature has been reworked and is now a part of [new zone interaction modes](#new-zone-interaction-modes) feature.* + You can create the rectangular picture zones - holding down the CTRL key. + You can move the (rectangular) picture zones corners in an orthogonal manner - holding down the CTRL key. + + *This feature has been reworked and is now a part of [new zone interaction modes](#new-zone-interaction-modes) feature.* * ##### Auto save project \[optimized\] - Set the "auto-save project" checked in the Settings menu and you will get - your project auto-saved provided you have originally saved your new project. - Works at the batch processing too. - - *This feature had performance issues and has been optimized.* + Set the "auto-save project" checked in the Settings menu and you will get + your project auto-saved provided you have originally saved your new project. + Works at the batch processing too. + + *This feature had performance issues and has been optimized.* * ##### Quadro Zoner \[reworked\] - Another rectangular picture zone shape. This option is based on [Picture shape](#picture-shape), - [Square picture zones](#square-picture-zones). It squeezes every Picture shape zone down to the real - rectangular picture outline and then replaces it (the resulting raster zone) by a vector rectangular zone, - so that a user could easily adjust it afterwards (by moving its corners in an orthogonal manner). - - *This feature has been reworked. See [rectangular picture shape](#rectangular-picture-shape) feature description.* + Another rectangular picture zone shape. This option is based on [Picture shape](#picture-shape), + [Square picture zones](#square-picture-zones). It squeezes every Picture shape zone down to the real + rectangular picture outline and then replaces it (the resulting raster zone) by a vector rectangular zone, + so that a user could easily adjust it afterwards (by moving its corners in an orthogonal manner). + + *This feature has been reworked. See [rectangular picture shape](#rectangular-picture-shape) feature description.* * ##### Marginal dewarping - An automatic dewarping mode. Works ONLY with such raw scans that have the top and - bottom curved page borders (on the black background). It automatically sets the red points - of the blue mesh along these borders (to create a distortion model) and then dewarps the scan - according to them. Works best on the low-curved scans. + An automatic dewarping mode. Works ONLY with such raw scans that have the top and + bottom curved page borders (on the black background). It automatically sets the red points + of the blue mesh along these borders (to create a distortion model) and then dewarps the scan + according to them. Works best on the low-curved scans. -\**Other features of this version, such as Export, Dont_Equalize_Illumination_Pic_Zones, Original_Foreground_Mixed - has't been moved due to dirty realization. Their functionality is fully covered by - [full control over settings on output](#full-control-over-settings-on-output) and - [splitting output](#splitting-output) features.* +*Note: Other features of this version, such as Export, Dont_Equalize_Illumination_Pic_Zones, Original_Foreground_Mixed +has't been moved due to dirty realization. Their functionality is fully covered by +[full control over settings on output](#full-control-over-settings-on-output) and +[splitting output](#splitting-output) features.* -#### **Scan Tailor Advanced** features +#### ScanTailor Universal -* ##### Scan Tailor Advanced fixes & improvements - * Portability. - The setting is stored in the folder with a program. - - * Page splitting had an influence on output only in b&w mode with dewarping disabled. - Now it works in all the modes. - - * Page layout and all the other views now consider splitting settings. - Corresponding improvements are done to thumbnails. - - * Changed Scan Tailor behavior on page split stage. - 1. Reworked apply cut feature. Now on applying cut to the pages with different dimensions - than the page the cut applied to, Scan Tailor tries to adapt cutters instead of fully - rejecting the cut setting and switching to auto mode for those pages as it was before. - The later was annoying as pages could be similar and had the difference in a few pixels. - 2. Added check to reject invalid cut settings in manual mode. - 3. UI: Added cutters interaction between each other. They can't more intersect each other, - which created a wrong page layout configuration before. - - * Optimized memory usage on the output stage. +* ##### ScanTailor Universal fixes & improvements + * Improvements for the thumbnail view. + 1. More accurate multi-column list handling. + 2. Scaling thumbnails via **`Alt+Wheel`**. - * Reworking on [multi column thumbnails view](#multi-column-thumbnails-view-reworked) feature from ver. Enhanced. - Now thumbnails are shown evenly. - - * Added option to control highlighting (with red asterisks) the thumbnails of pages with high deviation. - The option refreshes the thumbnails instantly. + * Fixed some bugs of official and Enhanced version. + +#### ScanTailor Advanced + +* ##### ScanTailor Advanced fixes & improvements + * Portability. + The settings and program files are stored in the folder with the application. + *Note: If installed into a system directory, where config and data files can't be written into the + folder with the application executable, ScanTailor Advanced works as a standalone app and stores + its settings and application data in the appropriate system specific paths.* + + * Page splitting settings now influence on the output by filling offcut. + Fill offcut option has been added. + + * Page layout and all the other views now consider splitting settings. + Corresponding improvements are done for thumbnails. + + * Changed ScanTailor behavior on page split stage. + 1. Reworked apply cut feature. Now on applying cut to the pages with different dimensions + than the page the cut applied to, ScanTailor tries to adapt cutters instead of fully + rejecting the cut setting and switching to auto mode for those pages as it was before. + The later was annoying as pages could be similar and had the difference in a few pixels. + 2. Added check to reject invalid cut settings in manual mode. + 3. UI: Added cutters interaction between each other. They can't more intersect each other, + which created a wrong page layout configuration before. + + * Optimized memory usage on the output stage. + + * Reworking on [multi column thumbnails view](#multi-column-thumbnails-view-reworked) feature from ver. Enhanced. + Now thumbnails are shown evenly. + + * Added option to control highlighting the thumbnails of pages with high deviation with red asterisks. + The option refreshes the thumbnails instantly. + + * Deviation feature reworked. + 1. A deviation provider implemented. + It supports caching and recalculates the values on demand. There isn't more any necessity to store deviation in page parameters and so in the project file, that approach caused some problems as the deviation is not actually a page parameter and depends on all the pages in the project. + 2. Added sorting by decreasing deviation. + + * Page/content boxes and auto margins features fixes & improvements. + 1. Added a feature of dragging both content and page areas by using **`Shift+LMB`** combination. + 2. A page box implementation reworked. Now it's interactive and can be adjusted by the same way as a content box is done. + 3. The page rectangle does not require refreshing page and won't be reset on the content area changes. + 4. Implemented applying the page/content boxes to the other pages automatically correcting the position of the boxes. + 5. Added width and height parameters to regulate the page box size in manual mode. + 6. Auto margins option has been moved out of the alignment settings and does no more force to use only the original layout. + 7. Auto margins feature now considers page box changes made at the selection content stage. + 8. Other bug fixes and improvements. + + * Auto and original alignment modes reworked: + 1. The original and auto alignment modes didn't work correctly due to the error in code. + 2. Both the modes didn't work rightly after select content stage or reopening the project file, always requiring secondary batch processing of every page at margins stage to work correctly. + 3. Reworked calculation method for the original alignment. Now it is more precise. + 4. Original alignment mode now considers the page box from 4th stage. + 5. Fixed behaviour of horizontal alignment, when the original mode enabled, and auto margins has been enabled/disabled. Also on applying auto-margins / original alignment to the set of pages, that is now set correctly for each page. + 6. Added ability to separately control vertical and horizontal automatic alignment when auto or original alignment mode enabled. + + * Changed the way of the adjustment of the despeckle strength. + Now that's set via the slider. It allows to adjust the despeckle strength more smoothly and exactly. + Value 1.0 matches the old cautious mode, 2.0 - normal and 3.0 - aggressive. - * Deviation feature reworked. - 1. A deviation provider implemented. - It supports caching and recalculates the values on demand. There isn't more any necessity to store deviation in page parameters and so in the project file, that approach caused some problems as the deviation is not actually a page parameter and depends on all the pages in the project. - 2. Added sorting by decreasing deviation. - - * Auto and original alignment modes reworked: - 1. The original and auto alignment modes didn't work correctly due to the error in code. - 2. Both the modes didn't work rightly after select content stage or reopening the project file, always requiring secondary batch processing of every page at margins stage to work correctly. - 3. Reworked calculation method for the original alignment. Now it is more precise. - 4. Original alignment mode now considers the page box from 4th stage. - 5. Fixed behaviour of horizontal alignment, when the original mode enabled, and auto margins has been enabled/disabled. Also on applying auto-margins / original alignment to the set of pages, that is now set correctly for each page. - 6. Added ability to separately control vertical and horizontal automatic alignment when auto or original alignment mode enabled. - - * Fixed other bugs of official, Enhanced and Featured versions and made lots of other improvements. + * Improvements on the thumbnails view: + * Saving selection of pages on filter switch. + * Separate highlighting for selection leader in thumbnails. + * Navigating between selected pages. + Use **`Shift+PgUp/Q`** and **`Shift+PgDown/W`** to navigate between selected pages. + + * Added options in the settings to manage the quality and size of thumbnails. + + * Fixed other bugs of official, Enhanced and Featured versions and made lots of other improvements. * ##### Light and Dark color schemes - You can choose a desired color scheme in settings. + You can choose a desired color scheme in settings. * ##### Multi-threading support for batch processing - This significantly increases the speed of processing. The count of threads to use can be - adjusted while processing. - - **Warning!** More threads requires more memory to use. Exclude situations of that to be overflowed. + This significantly increases the speed of processing. The count of threads to use can be + adjusted while processing. + + **Warning!** More threads requires more memory to use. Exclude situations of that to be overflowed. * ##### Full control over settings on output - This feature enables to control cut margins, normalizing illumination before binarization, - normalizing illumination in color areas options, Savitzky-Golay and morphological smoothing on output - in any mode (of course, those setting that can be applied in the current mode). + This feature enables to control filling margins, normalizing illumination before binarization, + normalizing illumination in color areas and Savitzky-Golay and morphological smoothing options at the output stage + in any mode (of course, those setting that can be applied in the current mode). * ##### Filling outside areas - Now outside pixels can be filled with the background color of the page. - - Added filling setting with the following options: - 1. Background: estimate the background and fill outside pixels with its color. - 2. White: always fill with white. + Now outside pixels can be filled with the background color of the page. + + Added filling setting with the following options: + 1. Background: estimate the background and fill outside pixels with its color. + 2. White: always fill with white. * ##### Tiff compression - Tiff compression options allow to disable or change compression method in tiff files. - - There are two options in settings dialog: B&W and color compression. - 1. The B&W one has None, LZW, Deflate and CCITT G4 (Default) options. - 2. The color one has None, LZW (Default), Deflate and JPEG options. + Tiff compression options allow to disable or change compression method in tiff files. + + There are two options in settings dialog: B&W and color compression. + 1. The B&W one has None, LZW, Deflate and CCITT G4 (Default) options. + 2. The color one has None, LZW (Default), Deflate and JPEG options. * ##### Adaptive binarization - Sauvola and Wolf binarization algorithms have been added. They can be applied when - normalizing illumination does not help. + Sauvola and Wolf binarization algorithms have been added. They can be applied when + normalizing illumination does not help. * ##### Splitting output - The feature allows to split the mixed output scans into the pairs of a foreground (letters) - and background (images) layer. - - You can choose between B&W or color (original) foreground. - - It can be useful: - - for the further DjVu encoding, - - to apply different filters to letters and images, which when being applied to the whole - image gives worse results. - - to apply a binarization to the letters from a third party app without affecting the images. + The feature allows to split the mixed output scans into the pairs of a foreground (letters) + and background (images) layer. + + You can choose between B&W or color (original) foreground. + + It can be useful: + * for the further DjVu encoding, + * to apply different filters to letters and images, which when being applied to the whole + image gives worse results. + * to apply a binarization to the letters from a third party app without affecting the images. - *Note: That does not rename files to 0001, 0002... It can be made by a third party app, for example - [Bulk Rename Utility](http://www.bulkrenameutility.co.uk/Main_Intro.php)* - - * ##### Original background - This feature is a part of the [splitting output](#splitting-output) feature. - - It allows to preserve the original image background in the format ready for the further processing, when BW foreground is used. - It can be used to encode into DjVu the pages with the complex background using the semi-auto "split layers" method which gives much higher quality results than DjVu auto segmenter. - Also this feature can be used to extract high contrast elements of gradient images into the foreground layer by using second processing of the layer with pictures ("background"). - - Properties of the original background: - * Original background images are saved into "original_background" folder in "out" directory. - * Pure black (`#000000`) and white (`#ffffff`) colors of original background image are reserved into `#010101` and `#fefefe`, respectively. - * Picture zones are marked with black when the BW content is marked with white. This property allow to use "select by color" feature of an image editor to select needed areas for their further processing, for example, apply blur to white holes and their nearest areas to get an effective compression level of the background layer in DjVu. - * Filling zones feature also removes trash and speckles from the original background when applied to the foreground layer. - - * ##### Color segmenter and posterization - Color segmentation and posterization (color quantization) features have been implemented. - - Color segmentation allows to split the image into color segments and colorize b&w mask. - Posterization allows to reduce the number of colors of the image by grouping similar colors. - The main use of posterization is to be applied to segmented image to get an indexed image, that can then be encoded into DjVu as the foreground layer. It allows to create high-quality DjVu files with color text and elements having maximal compression level. - Posterization can also be used in color mode and can be applied to usual color gradient images for different purposes, for example, to increase their compression efficiency. - - * ##### Rectangular picture shape - "Quadro" picture shape mode from Featured was merged with Rectangular one from Enhanced. Also removed restriction of ver. Featured on deleting all the auto zones. Before it resulted in resetting all the auto zones back. - Added sensitivity option. If sensitivity equals 25%, the results will be the same as they were in old "Quadro" mode, if 100% - as in old "Rectangular". - - * ##### Page area - This feature is a further development of the auto margins and page box features from version Enhanced. - - Selection content stage changes: - 1. A page box implementation reworked. Now it's interactive and can be adjusted by the same way as a content box is done. - 2. The page rectangle does not require refreshing page and won't be reset on the content area changes. - 3. The page rectangle is now drawn in the thumbnails. - 4. Implemented the new feature of applying the page box set manually to the other pages. For pages of a different size ST automatically corrects page box place. The same change has been made to applying the content box feature. - 5. Added a feature of dragging both content and page areas by using **`Shift+LMB`** combination. - 6. Added width and height parameters to regulate the page box size in manual mode. - 7. A page area is now allowed to be out of the page bounds in manual mode. - - Page layout stage changes: - 1. Auto margins option has been moved out of alignment settings and does no more force to use only the original layout. - 2. Auto margins feature now considers page box changes on the 4th stage. - 3. Corresponding bug fixes. - - Owing to the changes above, there appeared a new way to set margins for books and documents, pages of which have similar formatting. At content selection stage we just set page box and content box to a page, applying those content and page boxes to the pages with similar formatting, use auto margins for those pages at page layout stage instead of adjusting the margins relative to the content box, and then go to the output. - - * ##### New zone interaction modes - **`Shift+LMB`** when the cursor is over a zone - drag the zone. - **`Ctrl+Shift+LMB`** when the cursor is over a zone - drag the zone copying. - **`Del`** when the cursor is over a zone - delete the zone. - **`Ctrl`** when dragging a zone vertex - make the angle of the vertex right. - **`Ctrl+Alt+Click`** - copy the latest created zone to the cursor position. - **`Ctrl`** when creating a new zone - switch to rectangular mode. - **`Alt`** when creating a new zone - switch to lasso (free drawing) mode. - - * ##### Saving zoom and focus on switching output tabs - The save is precise and considers the images transformations. - Also added a feature of swithing the output tabs by using **`Ctrl+1..5`** keys combinations. - - * ##### Measurement units system - The settings are available in the main window menu. - Available units: pixels, millimetres, centimetres and inches. - - The system affects every aspect of the program, so, for example, it's now possible to adjust margins in pixels, but not only in millimetres or inches. - - * ##### Status bar panel - The panel shows the next information: mouse position relative to the image, physical size of the image, position of the selected page in current order and the page name and type (`[L]` or `[R]` - left or right page, if the page has been splitted). - - This feature is also affected by [measurement units system](#measurement-units-system). - - * ##### Default parameters - Default parameters system supporting custom profiles has been implemented. + *Note: That does not rename files to 0001, 0002... It can be made by a third party app, for example + [Bulk Rename Utility](http://www.bulkrenameutility.co.uk/Main_Intro.php)* - The system allows to manage the default filter settings for every stage. - Those filter parameters will be set as defaults for any new project created. - - For example, it allows to set your own default margins standard, but not default 5, 10, 5, 10 mm, and so for the other parameters. +* ##### Original background + This feature is a part of the [splitting output](#splitting-output) feature. + + It allows to preserve the original image background in the format ready for the further processing, when BW foreground is used. + It can be used to encode into DjVu the pages with the complex background using the semi-auto "split layers" method which gives much higher quality results than DjVu auto segmenter. + Also this feature can be used to extract high contrast elements of gradient images into the foreground layer by using second processing of the layer with pictures ("background"). + + Properties of the original background: + * Original background images are saved into "original_background" folder in "out" directory. + * Pure black (`#000000`) and white (`#ffffff`) colors of original background image are reserved into `#010101` and `#fefefe`, respectively. + * Picture zones are marked with black when the BW content is marked with white. This property allow to use "select by color" feature of an image editor to select needed areas for their further processing, for example, apply blur to white holes and their nearest areas to get an effective compression level of the background layer in DjVu. + * Filling zones feature also removes trash and speckles from the original background when applied to the foreground layer. + +* ##### Color segmenter and posterization + Color segmentation and posterization (color quantization) features have been implemented. + + Color segmentation allows to split the image into color segments and colorize b&w mask. + Posterization allows to reduce the number of colors of the image by grouping similar colors. + The main use of posterization is to be applied to segmented image to get an indexed image, that can then be encoded into DjVu as the foreground layer. It allows to create high-quality DjVu files with color text and elements having maximal compression level. + Posterization can also be used in color mode and can be applied to usual color gradient images for different purposes, for example, to increase their compression efficiency. + +* ##### Rectangular picture shape + "Quadro" picture shape mode from Featured was merged with Rectangular one from Enhanced. Also removed restriction of ver. Featured on deleting all the auto zones. Before it resulted in resetting all the auto zones back. + Added sensitivity option. If sensitivity equals 25%, the results will be the same as they were in old "Quadro" mode, if 100% - as in old "Rectangular". + +* ##### New zone interaction modes + **`Shift+LMB`** on a zone - drag the zone. + **`Ctrl+Shift+LMB`** on a zone - drag the zone copying. + **`Del`** when the cursor is over a zone - delete the zone. + Hold **`Ctrl`** when dragging a zone vertex - make the angle of the vertex right. + **`Ctrl+Alt+Click`** - copy the latest created zone to the cursor position. + **`Ctrl`** while creating a new zone - switch to rectangular mode. + Hold **`Shift+Alt+LMB`** while creating a new zone - use lasso (free drawing) mode. + +* ##### Saving zoom and focus on switching output tabs + The save is precise and considers the images transformations. + Also added a feature of swithing the output tabs by using **`Ctrl+1..5`** keys combinations. + +* ##### Measurement units system + The settings are available in the main window menu. + Available units: pixels, millimetres, centimetres and inches. + + The system affects every aspect of the program, so, for example, it's now possible to adjust margins in pixels, but not only in millimetres or inches. - 1. There are two default profiles: "Default" and "Source". The "Default" profile represents default ST filter settings, the "Source" one represents the settings giving the source as output without any changes. - 2. A user can create its own profiles. User profiles are stored in config/profiles folder. - 3. The system consider the units settings from the [measurement units system](#measurement-units-system). Units are stored in the profile and ST automatically converts the values if needed. +* ##### Status bar panel + The panel shows the next information: mouse position relative to the image, physical size of the image, position of the selected page in current order and the page name and type (`[L]` or `[R]` - left or right page, if the page has been splitted). + + This feature is also affected by [measurement units system](#measurement-units-system). - * ##### Collapsible filter options. - Now group boxes containing filter options can be collapsed/expanded. - The collapse status is preserved between restarts of the application. - - * ##### Auto adjusting content area. - - Use **`double-click`** on content to automatically adjust the content area. - If the content is outside the area, the later will automatically be expanded and adjusted to the content at the position where double-click has been, - otherwise the area edge, nearest to that position, will be adjusted. - - It's much faster now to correct the content area if, for example, the page number has been missed by the auto algorithm. - It is no more required to manually and laboriously move the corners and edges of the content box. +* ##### Default parameters + Default parameters system supporting custom profiles has been implemented. + + The system allows to manage the default filter settings for every stage. + Those filter parameters will be set as defaults for any new project created. + + For example, it allows to set your own default margins standard, but not default 5, 10, 5, 10 mm, and so for the other parameters. + + Peculiarities: + 1. There are two default profiles: "Default" and "Source". The "Default" profile represents default ST filter settings, the "Source" one represents the settings giving the source as output without any changes. + 2. A user can create its own profiles. User profiles are stored in `config/profiles` folder or in an system specific one for application data. + 3. The system consider the units settings from the [measurement units system](#measurement-units-system). Units are stored in the profile and ST automatically converts the values if needed. + +* ##### Collapsible filter options. + Now group boxes containing filter options can be collapsed/expanded. + The collapse status is preserved between restarts of the application. +* ##### Auto adjusting content area. + Use **`double-click`** on content to automatically adjust the content area. + If the content is outside the area, the later will automatically be expanded and adjusted to the content at the position where double-click has been, + otherwise the area edge, nearest to that position, will be adjusted. + + It's much faster now to correct the content area if, for example, the page number has been missed by the auto algorithm. + It is no more required to manually and laboriously move the corners and edges of the content box. + +* ##### Black on white detection + This feature allows to process images with light content on dark background correctly by correcting auto algorithms. + + Peculiarities: + 1. Auto detection of pages with light content on dark background can be enabled or disabled in the settings. + Auto detection at the output stage is controlled separately. + 2. There is per page control over the mode in the output filter options. + +* ##### Guides + This feature gives you a more flexible and precise way of positioning content in the page layout. + These are horizontal or vertical lines you can display on a page at the margins stage \(when aligning enabled\). + *Note: Guides are adaptive to the page soft margins, i.e. when the latter changed the guides on the page + are automatically adjusted to match the new content position without requiring any manual re-adjusting.* + + Capabilities: + * **`Right-click`** to create/remove guides from the **context menu** called. + * **`Right-click`** on a guide to delete that guide from the **context menu** called. + * **`Shift+LMB`** - drag the guide under the cursor. + * **`Shift/Ctrl+LMB`** on the content rectangle - drag the page content. + Hold **`Shift`** pressed to restrict moving along the horizontal axis only or **`Ctrl`** for the vertical one. + Hold **`Shift+Ctrl`** for usual dragging. + * **`Double-click`** on content - automatically attach that content to the nearest guide. + Hold **`Shift`** pressed to select vertical guides only or **`Ctrl`** for horizontal ones. + Hold **`Shift+Ctrl`** to attach that to both the nearest vertical and horizontal guides. + * Use the **context menu** to enable/disable showing the hard margins rectangle. + Building ---------- diff --git a/RecentProjects.cpp b/RecentProjects.cpp index 908ed64c0..1c1b5a67c 100644 --- a/RecentProjects.cpp +++ b/RecentProjects.cpp @@ -17,63 +17,63 @@ */ #include "RecentProjects.h" -#include #include +#include void RecentProjects::read() { - QSettings settings; - std::list new_list; + QSettings settings; + std::list new_list; - const int size = settings.beginReadArray("project/recent"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - const QString path(settings.value("path").toString()); - new_list.push_back(path); - } - settings.endArray(); + const int size = settings.beginReadArray("project/recent"); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + const QString path(settings.value("path").toString()); + new_list.push_back(path); + } + settings.endArray(); - m_projectFiles.swap(new_list); + m_projectFiles.swap(new_list); } void RecentProjects::write(const int max_items) const { - QSettings settings; - settings.beginWriteArray("project/recent"); - int idx = 0; - for (const QString& path : m_projectFiles) { - if (idx >= max_items) { - break; - } - settings.setArrayIndex(idx); - settings.setValue("path", path); - ++idx; + QSettings settings; + settings.beginWriteArray("project/recent"); + int idx = 0; + for (const QString& path : m_projectFiles) { + if (idx >= max_items) { + break; } - settings.endArray(); + settings.setArrayIndex(idx); + settings.setValue("path", path); + ++idx; + } + settings.endArray(); } bool RecentProjects::validate() { - bool all_ok = true; + bool all_ok = true; - auto it(m_projectFiles.begin()); - const auto end(m_projectFiles.end()); - while (it != end) { - if (QFile::exists(*it)) { - ++it; - } else { - m_projectFiles.erase(it++); - all_ok = false; - } + auto it(m_projectFiles.begin()); + const auto end(m_projectFiles.end()); + while (it != end) { + if (QFile::exists(*it)) { + ++it; + } else { + m_projectFiles.erase(it++); + all_ok = false; } + } - return all_ok; + return all_ok; } void RecentProjects::setMostRecent(const QString& file_path) { - const auto begin(m_projectFiles.begin()); - const auto end(m_projectFiles.end()); - auto it(std::find(begin, end, file_path)); - if (it != end) { - m_projectFiles.splice(begin, m_projectFiles, it); - } else { - m_projectFiles.push_front(file_path); - } + const auto begin(m_projectFiles.begin()); + const auto end(m_projectFiles.end()); + auto it(std::find(begin, end, file_path)); + if (it != end) { + m_projectFiles.splice(begin, m_projectFiles, it); + } else { + m_projectFiles.push_front(file_path); + } } diff --git a/RecentProjects.h b/RecentProjects.h index 13730bb79..de0ac9072 100644 --- a/RecentProjects.h +++ b/RecentProjects.h @@ -20,64 +20,62 @@ #define RECENT_PROJECTS_H_ #include -#include #include +#include class RecentProjects { -public: - /** - * \brief The default value for max_items parameters of - * write() and enumerate(). - */ - enum { DEFAULT_MAX_ITEMS = 7 }; - - /** - * \brief Reads the list of recent projects from QSettings - * without validating them. - * - * The current list will be overwritten. - */ - void read(); - - /** - * \brief Removes non-existing project files. - * - * \return true if no projects were removed, false otherwise. - */ - bool validate(); - - /** - * \brief Appends a project to the list or moves it to the - * top of the list, if it was already there. - */ - void setMostRecent(const QString& file_path); - - void write(int max_items = DEFAULT_MAX_ITEMS) const; - - bool isEmpty() const { - return m_projectFiles.empty(); - } - - /** - * \brief Calls out((const QString&)file_path) for every entry. - * - * Modifying this object from the callback is not allowed. - */ - template - void enumerate(Out out, int max_items = DEFAULT_MAX_ITEMS) const; - -private: - std::list m_projectFiles; + public: + /** + * \brief The default value for max_items parameters of + * write() and enumerate(). + */ + enum { DEFAULT_MAX_ITEMS = 7 }; + + /** + * \brief Reads the list of recent projects from QSettings + * without validating them. + * + * The current list will be overwritten. + */ + void read(); + + /** + * \brief Removes non-existing project files. + * + * \return true if no projects were removed, false otherwise. + */ + bool validate(); + + /** + * \brief Appends a project to the list or moves it to the + * top of the list, if it was already there. + */ + void setMostRecent(const QString& file_path); + + void write(int max_items = DEFAULT_MAX_ITEMS) const; + + bool isEmpty() const { return m_projectFiles.empty(); } + + /** + * \brief Calls out((const QString&)file_path) for every entry. + * + * Modifying this object from the callback is not allowed. + */ + template + void enumerate(Out out, int max_items = DEFAULT_MAX_ITEMS) const; + + private: + std::list m_projectFiles; }; -template +template void RecentProjects::enumerate(Out out, int max_items) const { - std::list::const_iterator it(m_projectFiles.begin()); - const std::list::const_iterator end(m_projectFiles.end()); - for (; it != end && max_items > 0; ++it, --max_items) { - out(*it); - } + std::list::const_iterator it(m_projectFiles.begin()); + const std::list::const_iterator end(m_projectFiles.end()); + for (; it != end && max_items > 0; ++it, --max_items) { + out(*it); + } } #endif // ifndef RECENT_PROJECTS_H_ diff --git a/RelinkablePath.cpp b/RelinkablePath.cpp index 5ac87912e..9e3c477a2 100644 --- a/RelinkablePath.cpp +++ b/RelinkablePath.cpp @@ -19,41 +19,40 @@ #include "RelinkablePath.h" #include -RelinkablePath::RelinkablePath(const QString& path, Type type) : m_normalizedPath(normalize(path)), m_type(type) { -} +RelinkablePath::RelinkablePath(const QString& path, Type type) : m_normalizedPath(normalize(path)), m_type(type) {} QString RelinkablePath::normalize(const QString& path) { - QString front_slashes(path); - front_slashes.replace(QChar('\\'), QLatin1String("/")); + QString front_slashes(path); + front_slashes.replace(QChar('\\'), QLatin1String("/")); - QStringList new_components; - for (const QString& comp : front_slashes.split(QChar('/'), QString::KeepEmptyParts)) { - if (comp.isEmpty()) { - if (new_components.isEmpty() + QStringList new_components; + for (const QString& comp : front_slashes.split(QChar('/'), QString::KeepEmptyParts)) { + if (comp.isEmpty()) { + if (new_components.isEmpty() #if _WIN32 - || (new_components.size() == 1 && new_components.front().isEmpty()) + || (new_components.size() == 1 && new_components.front().isEmpty()) #endif - ) { - new_components.push_back(comp); - } else { - // This will get rid of redundant slashes, including the trailing slash. - continue; - } - } else if (comp == ".") { - continue; - } else if (comp == "..") { - if (new_components.isEmpty()) { - return QString(); // Error. - } - const QString& last_comp = new_components.back(); - if (last_comp.isEmpty() || last_comp.endsWith(QChar(':'))) { - return QString(); // Error. - } - new_components.pop_back(); - } else { - new_components.push_back(comp); - } + ) { + new_components.push_back(comp); + } else { + // This will get rid of redundant slashes, including the trailing slash. + continue; + } + } else if (comp == ".") { + continue; + } else if (comp == "..") { + if (new_components.isEmpty()) { + return QString(); // Error. + } + const QString& last_comp = new_components.back(); + if (last_comp.isEmpty() || last_comp.endsWith(QChar(':'))) { + return QString(); // Error. + } + new_components.pop_back(); + } else { + new_components.push_back(comp); } + } - return new_components.join(QChar('/')); + return new_components.join(QChar('/')); } // RelinkablePath::normalize diff --git a/RelinkablePath.h b/RelinkablePath.h index c7839f6e9..9e55919ae 100644 --- a/RelinkablePath.h +++ b/RelinkablePath.h @@ -25,35 +25,31 @@ * \brief Represents a file or directory. */ class RelinkablePath { - // Member-wise copying is OK. -public: - enum Type { File, Dir }; - - RelinkablePath(const QString& path, Type type); - - const QString& normalizedPath() const { - return m_normalizedPath; - } - - Type type() const { - return m_type; - } - - /** - * Performs the following operations on the path: - * \li Converts backwards slashes to forward slashes. - * \li Eliminates redundant slashes. - * \li Eliminates "/./" and resolves "/../" components. - * \li Removes trailing slashes. - * - * \return The normalized string on success or an empty string on failure. - * Failure can happen because of unresolvable "/../" components. - */ - static QString normalize(const QString& path); - -private: - QString m_normalizedPath; - Type m_type; + // Member-wise copying is OK. + public: + enum Type { File, Dir }; + + RelinkablePath(const QString& path, Type type); + + const QString& normalizedPath() const { return m_normalizedPath; } + + Type type() const { return m_type; } + + /** + * Performs the following operations on the path: + * \li Converts backwards slashes to forward slashes. + * \li Eliminates redundant slashes. + * \li Eliminates "/./" and resolves "/../" components. + * \li Removes trailing slashes. + * + * \return The normalized string on success or an empty string on failure. + * Failure can happen because of unresolvable "/../" components. + */ + static QString normalize(const QString& path); + + private: + QString m_normalizedPath; + Type m_type; }; diff --git a/RelinkablePathVisualization.cpp b/RelinkablePathVisualization.cpp index d9d1f3de6..705056331 100644 --- a/RelinkablePathVisualization.cpp +++ b/RelinkablePathVisualization.cpp @@ -17,130 +17,126 @@ */ #include "RelinkablePathVisualization.h" -#include "RelinkablePath.h" -#include "QtSignalForwarder.h" -#include "ColorSchemeManager.h" #include -#include #include +#include #include -#include #include +#include #include #include +#include "ColorSchemeManager.h" +#include "QtSignalForwarder.h" +#include "RelinkablePath.h" struct RelinkablePathVisualization::PathComponent { - QString label; - QString prefixPath; // Including the component itself. - QString suffixPath; // Rest of the path. - RelinkablePath::Type type; // File or Dir. - bool exists; - - PathComponent(const QString& lbl, const QString& prefix_path, const QString& suffix_path, RelinkablePath::Type t) - : label(lbl), prefixPath(prefix_path), suffixPath(suffix_path), type(t), exists(false) { - } + QString label; + QString prefixPath; // Including the component itself. + QString suffixPath; // Rest of the path. + RelinkablePath::Type type; // File or Dir. + bool exists; + + PathComponent(const QString& lbl, const QString& prefix_path, const QString& suffix_path, RelinkablePath::Type t) + : label(lbl), prefixPath(prefix_path), suffixPath(suffix_path), type(t), exists(false) {} }; class RelinkablePathVisualization::ComponentButton : public QPushButton { -public: - explicit ComponentButton(QWidget* parent = nullptr) : QPushButton(parent) { - } + public: + explicit ComponentButton(QWidget* parent = nullptr) : QPushButton(parent) {} -protected: - void paintEvent(QPaintEvent* evt) override; + protected: + void paintEvent(QPaintEvent* evt) override; }; RelinkablePathVisualization::RelinkablePathVisualization(QWidget* parent) - : QWidget(parent), m_pLayout(new QHBoxLayout(this)) { - m_pLayout->setSpacing(0); - m_pLayout->setMargin(0); + : QWidget(parent), m_layout(new QHBoxLayout(this)) { + m_layout->setSpacing(0); + m_layout->setMargin(0); } void RelinkablePathVisualization::clear() { - for (int count; (count = m_pLayout->count());) { - QLayoutItem* item = m_pLayout->takeAt(count - 1); - if (QWidget* wgt = item->widget()) { - wgt->deleteLater(); // We may be called from the widget's signal. - } - delete item; + for (int count; (count = m_layout->count());) { + QLayoutItem* item = m_layout->takeAt(count - 1); + if (QWidget* wgt = item->widget()) { + wgt->deleteLater(); // We may be called from the widget's signal. } + delete item; + } } void RelinkablePathVisualization::setPath(const RelinkablePath& path, bool clickable) { - clear(); + clear(); - QStringList components(path.normalizedPath().split(QChar('/'), QString::SkipEmptyParts)); - if (components.empty()) { - return; - } + QStringList components(path.normalizedPath().split(QChar('/'), QString::SkipEmptyParts)); + if (components.empty()) { + return; + } - // prefix_path is the path up to and including the current component. - QString prefix_path; + // prefix_path is the path up to and including the current component. + QString prefix_path; - if (path.normalizedPath().startsWith(QLatin1String("//"))) { - // That's the Windows \\host\path thing. - components.front().prepend(QLatin1String("//")); - } else if (path.normalizedPath().startsWith(QChar('/'))) { - // Unix paths start with a slash. - prefix_path += QChar('/'); - } + if (path.normalizedPath().startsWith(QLatin1String("//"))) { + // That's the Windows \\host\path thing. + components.front().prepend(QLatin1String("//")); + } else if (path.normalizedPath().startsWith(QChar('/'))) { + // Unix paths start with a slash. + prefix_path += QChar('/'); + } - std::vector path_components; - path_components.reserve(components.size()); + std::vector path_components; + path_components.reserve(components.size()); - for (QStringList::const_iterator it(components.begin()); it != components.end(); ++it) { - const QString& component = *it; + for (QStringList::const_iterator it(components.begin()); it != components.end(); ++it) { + const QString& component = *it; - if (!prefix_path.isEmpty() && !prefix_path.endsWith('/')) { - prefix_path += QChar('/'); - } - prefix_path += component; - // Rest of the path. - QString suffix_path; - for (QStringList::const_iterator it2(it); ++it2 != components.end();) { - if (!suffix_path.isEmpty()) { - suffix_path += QChar('/'); - } - suffix_path += *it2; - } - - path_components.emplace_back(component, prefix_path, suffix_path, RelinkablePath::Dir); + if (!prefix_path.isEmpty() && !prefix_path.endsWith('/')) { + prefix_path += QChar('/'); + } + prefix_path += component; + // Rest of the path. + QString suffix_path; + for (QStringList::const_iterator it2(it); ++it2 != components.end();) { + if (!suffix_path.isEmpty()) { + suffix_path += QChar('/'); + } + suffix_path += *it2; } - // The last path component is either a file or a dir, while all the previous ones are dirs. - path_components.back().type = path.type(); + path_components.emplace_back(component, prefix_path, suffix_path, RelinkablePath::Dir); + } - checkForExistence(path_components); + // The last path component is either a file or a dir, while all the previous ones are dirs. + path_components.back().type = path.type(); - int component_idx = -1; - for (PathComponent& path_component : path_components) { - ++component_idx; - auto* btn = new ComponentButton(this); - m_pLayout->addWidget(btn); - btn->setText(path_component.label.replace(QChar('/'), QChar('\\'))); - btn->setEnabled(clickable); - if (clickable) { - btn->setCursor(QCursor(Qt::PointingHandCursor)); - } - stylePathComponentButton(btn, path_component.exists); + checkForExistence(path_components); - new QtSignalForwarder(btn, SIGNAL(clicked()), - boost::bind(&RelinkablePathVisualization::onClicked, this, component_idx, - path_component.prefixPath, path_component.suffixPath, path_component.type)); + int component_idx = -1; + for (PathComponent& path_component : path_components) { + ++component_idx; + auto* btn = new ComponentButton(this); + m_layout->addWidget(btn); + btn->setText(path_component.label.replace(QChar('/'), QChar('\\'))); + btn->setEnabled(clickable); + if (clickable) { + btn->setCursor(QCursor(Qt::PointingHandCursor)); } + stylePathComponentButton(btn, path_component.exists); - m_pLayout->addStretch(); + new QtSignalForwarder(btn, SIGNAL(clicked()), + boost::bind(&RelinkablePathVisualization::onClicked, this, component_idx, + path_component.prefixPath, path_component.suffixPath, path_component.type)); + } + + m_layout->addStretch(); } // RelinkablePathVisualization::setPath void RelinkablePathVisualization::stylePathComponentButton(QAbstractButton* btn, bool exists) { - const QColor border_color(ColorSchemeManager::instance() - ->getColorParam("relinkable_path_visualization_border_color", - palette().color(QPalette::Window).darker(150)) - .color()); + const QColor border_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::RelinkablePathVisualizationBorder, palette().color(QPalette::Window).darker(150)); - QString style = "QAbstractButton {\n" + QString style = "QAbstractButton {\n" " border: 2px solid " + border_color.name() + ";\n" @@ -150,16 +146,16 @@ void RelinkablePathVisualization::stylePathComponentButton(QAbstractButton* btn, " margin-right: 1px;\n" " min-width: 2em;\n" " font-weight: bold;\n"; - if (exists) { - style += " color: #3a5827;\n" + if (exists) { + style += " color: #3a5827;\n" " background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #fff, " "stop: 1 #89e74a);\n"; - } else { - style += " color: #6f2719;\n" + } else { + style += " color: #6f2719;\n" " background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #fff, " "stop: 1 #ff674b);\n"; - } - style += "}\n" + } + style += "}\n" "QAbstractButton:hover {\n" " color: #333;\n" " background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #fff, stop: 1 " @@ -171,121 +167,121 @@ void RelinkablePathVisualization::stylePathComponentButton(QAbstractButton* btn, "#ddd);\n" "}\n"; - btn->setStyleSheet(style); + btn->setStyleSheet(style); } // RelinkablePathVisualization::stylePathComponentButton void RelinkablePathVisualization::paintEvent(QPaintEvent* evt) { - const int total_items = m_pLayout->count(); // Note that there is an extra stretch item. - for (int i = 0; i < total_items; ++i) { - QWidget* widget = m_pLayout->itemAt(i)->widget(); - if (!widget) { - continue; - } + const int total_items = m_layout->count(); // Note that there is an extra stretch item. + for (int i = 0; i < total_items; ++i) { + QWidget* widget = m_layout->itemAt(i)->widget(); + if (!widget) { + continue; + } - QStyleOption option; - option.initFrom(widget); - - if (option.state & QStyle::State_MouseOver) { - widget->setProperty("highlightEnforcer", true); - // Update the forceHighlight attribute for all buttons. - for (int j = 0; j < total_items; ++j) { - widget = m_pLayout->itemAt(j)->widget(); - if (widget) { - const bool highlight = j <= i; - if (widget->property("forceHighlight").toBool() != highlight) { - widget->setProperty("forceHighlight", highlight); - widget->update(); - } - } - } - break; - } else if (widget->property("highlightEnforcer").toBool()) { - widget->setProperty("highlightEnforcer", false); - - // Update the forceHighlight attribute for all buttons. - for (int j = 0; j < total_items; ++j) { - widget = m_pLayout->itemAt(j)->widget(); - if (widget) { - const bool highlight = false; - if (widget->property("forceHighlight").toBool() != highlight) { - widget->setProperty("forceHighlight", highlight); - widget->update(); - } - } - } - break; + QStyleOption option; + option.initFrom(widget); + + if (option.state & QStyle::State_MouseOver) { + widget->setProperty("highlightEnforcer", true); + // Update the forceHighlight attribute for all buttons. + for (int j = 0; j < total_items; ++j) { + widget = m_layout->itemAt(j)->widget(); + if (widget) { + const bool highlight = j <= i; + if (widget->property("forceHighlight").toBool() != highlight) { + widget->setProperty("forceHighlight", highlight); + widget->update(); + } } + } + break; + } else if (widget->property("highlightEnforcer").toBool()) { + widget->setProperty("highlightEnforcer", false); + + // Update the forceHighlight attribute for all buttons. + for (int j = 0; j < total_items; ++j) { + widget = m_layout->itemAt(j)->widget(); + if (widget) { + const bool highlight = false; + if (widget->property("forceHighlight").toBool() != highlight) { + widget->setProperty("forceHighlight", highlight); + widget->update(); + } + } + } + break; } + } } // RelinkablePathVisualization::paintEvent void RelinkablePathVisualization::onClicked(int component_idx, const QString& prefix_path, const QString& suffix_path, int type) { - // We'd like highlighting to stick until this method returns. + // We'd like highlighting to stick until this method returns. - for (int i = 0; i <= component_idx; ++i) { - QWidget* widget = m_pLayout->itemAt(i)->widget(); - if (widget) { - widget->setProperty("stickHighlight", true); - } + for (int i = 0; i <= component_idx; ++i) { + QWidget* widget = m_layout->itemAt(i)->widget(); + if (widget) { + widget->setProperty("stickHighlight", true); } - - emit clicked(prefix_path, suffix_path, type); - // Note that clear() or setPath() might have been called by a signal handler. - const int total_items = m_pLayout->count(); // Note that there is an extra stretch item. - for (int i = 0; i <= component_idx && i < total_items; ++i) { - QWidget* widget = m_pLayout->itemAt(i)->widget(); - if (widget) { - widget->setProperty("stickHighlight", false); - } + } + + emit clicked(prefix_path, suffix_path, type); + // Note that clear() or setPath() might have been called by a signal handler. + const int total_items = m_layout->count(); // Note that there is an extra stretch item. + for (int i = 0; i <= component_idx && i < total_items; ++i) { + QWidget* widget = m_layout->itemAt(i)->widget(); + if (widget) { + widget->setProperty("stickHighlight", false); } + } } void RelinkablePathVisualization::checkForExistence(std::vector& components) { - // Instead of calling QFile::exists() [which also works on directories] - // for every component, we use binary search. That's especially important - // when dealing with network paths. - if (components.empty()) { - return; + // Instead of calling QFile::exists() [which also works on directories] + // for every component, we use binary search. That's especially important + // when dealing with network paths. + if (components.empty()) { + return; + } + + if (QFile::exists(components.back().prefixPath)) { + for (PathComponent& comp : components) { + comp.exists = true; } - if (QFile::exists(components.back().prefixPath)) { - for (PathComponent& comp : components) { - comp.exists = true; - } + return; + } - return; - } - - int left = -1; // Existing component (unless -1). - auto right = static_cast(components.size() - 1); // Non-existing component (we checked it above). - while (right - left > 1) { - const int mid = (left + right + 1) >> 1; - if (QFile::exists(components[mid].prefixPath)) { - left = mid; - } else { - right = mid; - } + int left = -1; // Existing component (unless -1). + auto right = static_cast(components.size() - 1); // Non-existing component (we checked it above). + while (right - left > 1) { + const int mid = (left + right + 1) >> 1; + if (QFile::exists(components[mid].prefixPath)) { + left = mid; + } else { + right = mid; } + } - for (auto i = static_cast(components.size() - 1); i >= 0; --i) { - components[i].exists = (i < right); - } + for (auto i = static_cast(components.size() - 1); i >= 0; --i) { + components[i].exists = (i < right); + } } // RelinkablePathVisualization::checkForExistence /*============================ ComponentButton ============================*/ void RelinkablePathVisualization::ComponentButton::paintEvent(QPaintEvent* evt) { - QStyleOptionButton option; - option.initFrom(this); - option.text = text(); - if (property("forceHighlight").toBool() || property("stickHighlight").toBool()) { - option.state |= QStyle::State_MouseOver; - } - - // Prevent weird looking font effects for disabled buttons with Windows XP style. - option.state |= QStyle::State_Enabled; - - QStylePainter painter(this); - painter.drawControl(QStyle::CE_PushButton, option); + QStyleOptionButton option; + option.initFrom(this); + option.text = text(); + if (property("forceHighlight").toBool() || property("stickHighlight").toBool()) { + option.state |= QStyle::State_MouseOver; + } + + // Prevent weird looking font effects for disabled buttons with Windows XP style. + option.state |= QStyle::State_Enabled; + + QStylePainter painter(this); + painter.drawControl(QStyle::CE_PushButton, option); } diff --git a/RelinkablePathVisualization.h b/RelinkablePathVisualization.h index e73681303..a5cb2c06d 100644 --- a/RelinkablePathVisualization.h +++ b/RelinkablePathVisualization.h @@ -19,42 +19,42 @@ #ifndef RELINKABLE_PATH_VISUALIZATION_H_ #define RELINKABLE_PATH_VISUALIZATION_H_ -#include #include +#include class RelinkablePath; class QHBoxLayout; class QAbstractButton; class RelinkablePathVisualization : public QWidget { - Q_OBJECT -public: - explicit RelinkablePathVisualization(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit RelinkablePathVisualization(QWidget* parent = nullptr); - void clear(); + void clear(); - void setPath(const RelinkablePath& path, bool clickable); + void setPath(const RelinkablePath& path, bool clickable); -signals: + signals: - /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ - void clicked(const QString& prefix_path, const QString& suffix_path, int type); + /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ + void clicked(const QString& prefix_path, const QString& suffix_path, int type); -protected: - void paintEvent(QPaintEvent* evt) override; + protected: + void paintEvent(QPaintEvent* evt) override; -private: - struct PathComponent; + private: + struct PathComponent; - class ComponentButton; + class ComponentButton; - void onClicked(int component_idx, const QString& prefix_path, const QString& suffix_path, int type); + void onClicked(int component_idx, const QString& prefix_path, const QString& suffix_path, int type); - void stylePathComponentButton(QAbstractButton* btn, bool exists); + void stylePathComponentButton(QAbstractButton* btn, bool exists); - static void checkForExistence(std::vector& components); + static void checkForExistence(std::vector& components); - QHBoxLayout* m_pLayout; + QHBoxLayout* m_layout; }; diff --git a/RelinkingDialog.cpp b/RelinkingDialog.cpp index de6ee1dc9..3202ccff8 100644 --- a/RelinkingDialog.cpp +++ b/RelinkingDialog.cpp @@ -17,114 +17,114 @@ */ #include "RelinkingDialog.h" -#include "RelinkingSortingModel.h" #include #include #include +#include "RelinkingSortingModel.h" RelinkingDialog::RelinkingDialog(const QString& project_file_path, QWidget* parent) - : QDialog(parent), - m_pSortingModel(new RelinkingSortingModel), - m_projectFileDir(QFileInfo(project_file_path).path()) { - ui.setupUi(this); - m_pSortingModel->setSourceModel(&m_model); - ui.listView->setModel(m_pSortingModel); - ui.listView->setTextElideMode(Qt::ElideMiddle); - ui.errorLabel->setVisible(false); - ui.undoButton->setVisible(false); - - connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - SLOT(selectionChanged(const QItemSelection&, const QItemSelection&))); - - connect(ui.pathVisualization, SIGNAL(clicked(const QString&, const QString&, int)), - SLOT(pathButtonClicked(const QString&, const QString&, int))); - - connect(ui.undoButton, SIGNAL(clicked()), SLOT(undoButtonClicked())); - - disconnect(ui.buttonBox, SIGNAL(accepted())); - connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); + : QDialog(parent), + m_sortingModel(new RelinkingSortingModel), + m_projectFileDir(QFileInfo(project_file_path).path()) { + ui.setupUi(this); + m_sortingModel->setSourceModel(&m_model); + ui.listView->setModel(m_sortingModel); + ui.listView->setTextElideMode(Qt::ElideMiddle); + ui.errorLabel->setVisible(false); + ui.undoButton->setVisible(false); + + connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + SLOT(selectionChanged(const QItemSelection&, const QItemSelection&))); + + connect(ui.pathVisualization, SIGNAL(clicked(const QString&, const QString&, int)), + SLOT(pathButtonClicked(const QString&, const QString&, int))); + + connect(ui.undoButton, SIGNAL(clicked()), SLOT(undoButtonClicked())); + + disconnect(ui.buttonBox, SIGNAL(accepted())); + connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); } void RelinkingDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - if (selected.isEmpty()) { - ui.pathVisualization->clear(); - ui.pathVisualization->setVisible(false); - } else { - ui.undoButton->setVisible(false); - - const QModelIndex index(selected.front().topLeft()); - const QString path(index.data(m_model.UncommittedPathRole).toString()); - const int type = index.data(m_model.TypeRole).toInt(); - ui.pathVisualization->setPath(RelinkablePath(path, (RelinkablePath::Type) type), /*clickable=*/true); - ui.pathVisualization->setVisible(true); - - if (ui.errorLabel->isVisible()) { - m_model.rollbackChanges(); - } else { - m_model.commitChanges(); - } - } - - ui.errorLabel->setVisible(false); -} - -void RelinkingDialog::pathButtonClicked(const QString& prefix_path, const QString& suffix_path, const int type) { - assert(!prefix_path.endsWith(QChar('/')) && !prefix_path.endsWith(QChar('\\'))); - assert(!suffix_path.startsWith(QChar('/')) && !suffix_path.startsWith(QChar('\\'))); + if (selected.isEmpty()) { + ui.pathVisualization->clear(); + ui.pathVisualization->setVisible(false); + } else { + ui.undoButton->setVisible(false); - QString replacement_path; + const QModelIndex index(selected.front().topLeft()); + const QString path(index.data(m_model.UncommittedPathRole).toString()); + const int type = index.data(m_model.TypeRole).toInt(); + ui.pathVisualization->setPath(RelinkablePath(path, (RelinkablePath::Type) type), /*clickable=*/true); + ui.pathVisualization->setVisible(true); - if (type == RelinkablePath::File) { - const QDir dir(QFileInfo(prefix_path).dir()); - replacement_path = QFileDialog::getOpenFileName( - this, tr("Substitution File for %1").arg(QDir::toNativeSeparators(prefix_path)), - dir.exists() ? dir.path() : m_projectFileDir, QString(), nullptr, QFileDialog::DontUseNativeDialog); + if (ui.errorLabel->isVisible()) { + m_model.rollbackChanges(); } else { - const QDir dir(prefix_path); - replacement_path = QFileDialog::getExistingDirectory( - this, tr("Substitution Directory for %1").arg(QDir::toNativeSeparators(prefix_path)), - dir.exists() ? prefix_path : m_projectFileDir, QFileDialog::DontUseNativeDialog); - } - // So what's wrong with native dialogs? The one for directory selection won't show files - // at all (if you ask it to, the non-native dialog will appear), which is inconvenient - // in this situation. So, if one of them has to be non-native, the other was made - // non-native as well, for consistency reasons. - replacement_path = RelinkablePath::normalize(replacement_path); - - if (replacement_path.isEmpty()) { - return; + m_model.commitChanges(); } + } - if (prefix_path == replacement_path) { - return; - } - - QString new_path(replacement_path); - new_path += QChar('/'); - new_path += suffix_path; - - m_model.replacePrefix(prefix_path, replacement_path, (RelinkablePath::Type) type); - - if (m_model.checkForMerges()) { - ui.errorLabel->setText(tr("This change would merge several files into one.")); - ui.errorLabel->setVisible(true); - ui.pathVisualization->clear(); - ui.pathVisualization->setVisible(false); - } else { - ui.pathVisualization->setPath(RelinkablePath(new_path, (RelinkablePath::Type) type), /*clickable=*/false); - ui.pathVisualization->setVisible(true); - } + ui.errorLabel->setVisible(false); +} - ui.undoButton->setVisible(true); - ui.listView->update(); +void RelinkingDialog::pathButtonClicked(const QString& prefix_path, const QString& suffix_path, const int type) { + assert(!prefix_path.endsWith(QChar('/')) && !prefix_path.endsWith(QChar('\\'))); + assert(!suffix_path.startsWith(QChar('/')) && !suffix_path.startsWith(QChar('\\'))); + + QString replacement_path; + + if (type == RelinkablePath::File) { + const QDir dir(QFileInfo(prefix_path).dir()); + replacement_path = QFileDialog::getOpenFileName( + this, tr("Substitution File for %1").arg(QDir::toNativeSeparators(prefix_path)), + dir.exists() ? dir.path() : m_projectFileDir, QString(), nullptr, QFileDialog::DontUseNativeDialog); + } else { + const QDir dir(prefix_path); + replacement_path = QFileDialog::getExistingDirectory( + this, tr("Substitution Directory for %1").arg(QDir::toNativeSeparators(prefix_path)), + dir.exists() ? prefix_path : m_projectFileDir, QFileDialog::DontUseNativeDialog); + } + // So what's wrong with native dialogs? The one for directory selection won't show files + // at all (if you ask it to, the non-native dialog will appear), which is inconvenient + // in this situation. So, if one of them has to be non-native, the other was made + // non-native as well, for consistency reasons. + replacement_path = RelinkablePath::normalize(replacement_path); + + if (replacement_path.isEmpty()) { + return; + } + + if (prefix_path == replacement_path) { + return; + } + + QString new_path(replacement_path); + new_path += QChar('/'); + new_path += suffix_path; + + m_model.replacePrefix(prefix_path, replacement_path, (RelinkablePath::Type) type); + + if (m_model.checkForMerges()) { + ui.errorLabel->setText(tr("This change would merge several files into one.")); + ui.errorLabel->setVisible(true); + ui.pathVisualization->clear(); + ui.pathVisualization->setVisible(false); + } else { + ui.pathVisualization->setPath(RelinkablePath(new_path, (RelinkablePath::Type) type), /*clickable=*/false); + ui.pathVisualization->setVisible(true); + } + + ui.undoButton->setVisible(true); + ui.listView->update(); } // RelinkingDialog::pathButtonClicked void RelinkingDialog::undoButtonClicked() { - m_model.rollbackChanges(); // Has to go before selectionChanged() - selectionChanged(ui.listView->selectionModel()->selection(), QItemSelection()); + m_model.rollbackChanges(); // Has to go before selectionChanged() + selectionChanged(ui.listView->selectionModel()->selection(), QItemSelection()); } void RelinkingDialog::commitChanges() { - m_model.commitChanges(); - accept(); + m_model.commitChanges(); + accept(); } diff --git a/RelinkingDialog.h b/RelinkingDialog.h index 023f351c6..232eaa8e1 100644 --- a/RelinkingDialog.h +++ b/RelinkingDialog.h @@ -19,12 +19,12 @@ #ifndef RELINKING_DIALOG_H_ #define RELINKING_DIALOG_H_ -#include "ui_RelinkingDialog.h" -#include "RelinkingModel.h" -#include "RelinkablePath.h" +#include #include "AbstractRelinker.h" +#include "RelinkablePath.h" +#include "RelinkingModel.h" #include "intrusive_ptr.h" -#include +#include "ui_RelinkingDialog.h" class RelinkingSortingModel; class QAbstractButton; @@ -32,43 +32,41 @@ class QItemSelection; class QString; class RelinkingDialog : public QDialog { - Q_OBJECT -public: - explicit RelinkingDialog(const QString& project_file_path, QWidget* parent = nullptr); - - ProxyFunction pathCollector() { - return ProxyFunction(m_model); - } - - /** - * This method guarantees that - * \code - * dialog->relinker().release() == dialog->relinker().release() - * \endcode - * will hold true for the lifetime of the dialog. - * This allows you to take the relinker right after construction - * and then use it when accepted() signal is emitted. - */ - intrusive_ptr relinker() const { - return m_model.relinker(); - } - -private slots: - - void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); - - /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ - void pathButtonClicked(const QString& prefix_path, const QString& suffix_path, int type); - - void undoButtonClicked(); - - void commitChanges(); - -private: - Ui::RelinkingDialog ui; - RelinkingModel m_model; - RelinkingSortingModel* m_pSortingModel; - QString m_projectFileDir; + Q_OBJECT + public: + explicit RelinkingDialog(const QString& project_file_path, QWidget* parent = nullptr); + + ProxyFunction pathCollector() { + return ProxyFunction(m_model); + } + + /** + * This method guarantees that + * \code + * dialog->relinker().release() == dialog->relinker().release() + * \endcode + * will hold true for the lifetime of the dialog. + * This allows you to take the relinker right after construction + * and then use it when accepted() signal is emitted. + */ + intrusive_ptr relinker() const { return m_model.relinker(); } + + private slots: + + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + + /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ + void pathButtonClicked(const QString& prefix_path, const QString& suffix_path, int type); + + void undoButtonClicked(); + + void commitChanges(); + + private: + Ui::RelinkingDialog ui; + RelinkingModel m_model; + RelinkingSortingModel* m_sortingModel; + QString m_projectFileDir; }; diff --git a/RelinkingListView.cpp b/RelinkingListView.cpp index 348a4d3a1..397ed533b 100644 --- a/RelinkingListView.cpp +++ b/RelinkingListView.cpp @@ -17,148 +17,144 @@ */ #include "RelinkingListView.h" -#include "RelinkingModel.h" -#include #include +#include +#include "RelinkingModel.h" class RelinkingListView::Delegate : public QStyledItemDelegate { -public: - explicit Delegate(RelinkingListView* owner) : QStyledItemDelegate(owner), m_pOwner(owner) { - } + public: + explicit Delegate(RelinkingListView* owner) : QStyledItemDelegate(owner), m_owner(owner) {} - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { - m_pOwner->maybeDrawStatusLayer(painter, index, option.rect); - QStyledItemDelegate::paint(painter, option, index); - } + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + m_owner->maybeDrawStatusLayer(painter, index, option.rect); + QStyledItemDelegate::paint(painter, option, index); + } -private: - RelinkingListView* m_pOwner; + private: + RelinkingListView* m_owner; }; class RelinkingListView::IndicationGroup { -public: - QRect rect; - int status; + public: + QRect rect; + int status; - IndicationGroup(const QRect& r, int st) : rect(r), status(st) { - } + IndicationGroup(const QRect& r, int st) : rect(r), status(st) {} }; class RelinkingListView::GroupAggregator { -public: - void process(const QRect& rect, int status); + public: + void process(const QRect& rect, int status); - const std::vector& groups() const { - return m_groups; - } + const std::vector& groups() const { return m_groups; } -private: - std::vector m_groups; + private: + std::vector m_groups; }; RelinkingListView::RelinkingListView(QWidget* parent) : QListView(parent), m_statusLayerDrawn(false) { - setItemDelegate(new Delegate(this)); + setItemDelegate(new Delegate(this)); } void RelinkingListView::paintEvent(QPaintEvent* e) { - m_statusLayerDrawn = false; - QListView::paintEvent(e); + m_statusLayerDrawn = false; + QListView::paintEvent(e); } void RelinkingListView::maybeDrawStatusLayer(QPainter* painter, const QModelIndex& item_index, const QRect& item_paint_rect) { - if (m_statusLayerDrawn) { - return; - } - - painter->save(); - // Now, the painter is configured for drawing an ItemView cell. - // We can't be sure about its origin (widget top-left, viewport top-left?) - // and its clipping region. The origin is not hard to figure out, - // while the clipping region we are just going to reset. - painter->translate(item_paint_rect.topLeft() - visualRect(item_index).topLeft()); - painter->setClipRect(viewport()->rect()); - drawStatusLayer(painter); - painter->restore(); - - m_statusLayerDrawn = true; + if (m_statusLayerDrawn) { + return; + } + + painter->save(); + // Now, the painter is configured for drawing an ItemView cell. + // We can't be sure about its origin (widget top-left, viewport top-left?) + // and its clipping region. The origin is not hard to figure out, + // while the clipping region we are just going to reset. + painter->translate(item_paint_rect.topLeft() - visualRect(item_index).topLeft()); + painter->setClipRect(viewport()->rect()); + drawStatusLayer(painter); + painter->restore(); + + m_statusLayerDrawn = true; } void RelinkingListView::drawStatusLayer(QPainter* painter) { - const QRect drawing_rect(viewport()->rect()); - QModelIndex top_index(this->indexAt(drawing_rect.topLeft())); - if (!top_index.isValid()) { - // No [visible] elements at all? - return; - } - - if (top_index.row() > 0) { - // The appearence of any element actually depends on its neighbours, - // so we start with the element above our topmost visible one. - top_index = top_index.sibling(top_index.row() - 1, 0); + const QRect drawing_rect(viewport()->rect()); + QModelIndex top_index(this->indexAt(drawing_rect.topLeft())); + if (!top_index.isValid()) { + // No [visible] elements at all? + return; + } + + if (top_index.row() > 0) { + // The appearence of any element actually depends on its neighbours, + // so we start with the element above our topmost visible one. + top_index = top_index.sibling(top_index.row() - 1, 0); + } + + GroupAggregator group_aggregator; + const int rows = top_index.model()->rowCount(top_index.parent()); + + for (int row = top_index.row(); row < rows; ++row) { + const QModelIndex index(top_index.sibling(row, 0)); + const QRect item_rect(visualRect(index)); + + QRect rect(drawing_rect); + rect.setTop(item_rect.top()); + rect.setBottom(item_rect.bottom()); + rect.setWidth(item_rect.height()); + rect.moveRight(drawing_rect.right()); + + const int status = index.data(RelinkingModel::UncommittedStatusRole).toInt(); + group_aggregator.process(rect, status); + + if ((row != top_index.row()) && !item_rect.intersects(drawing_rect)) { + // Note that we intentionally break *after* processing + // the first invisible item. That's because the appearence + // of its immediate predecessor depends on it. The topmost item + // is allowed to be invisible, as it's processed for the same reason, + // that is to make its immediate neighbour to display correctly. + break; } - - GroupAggregator group_aggregator; - const int rows = top_index.model()->rowCount(top_index.parent()); - - for (int row = top_index.row(); row < rows; ++row) { - const QModelIndex index(top_index.sibling(row, 0)); - const QRect item_rect(visualRect(index)); - - QRect rect(drawing_rect); - rect.setTop(item_rect.top()); - rect.setBottom(item_rect.bottom()); - rect.setWidth(item_rect.height()); - rect.moveRight(drawing_rect.right()); - - const int status = index.data(RelinkingModel::UncommittedStatusRole).toInt(); - group_aggregator.process(rect, status); - - if ((row != top_index.row()) && !item_rect.intersects(drawing_rect)) { - // Note that we intentionally break *after* processing - // the first invisible item. That's because the appearence - // of its immediate predecessor depends on it. The topmost item - // is allowed to be invisible, as it's processed for the same reason, - // that is to make its immediate neighbour to display correctly. - break; - } - } - - painter->setRenderHint(QPainter::Antialiasing); - - // Prepare for drawing existing items. - QPen pen(QColor(0x3a5827)); - pen.setWidthF(1.5); - QBrush brush(QColor(0x89e74a)); - - // Draw existing, then missing items. - for (int status = RelinkingModel::Exists, i = 0; i < 2; ++i) { - painter->setPen(pen); - painter->setBrush(brush); - - for (const IndicationGroup& group : group_aggregator.groups()) { - if (group.status == status) { - const qreal radius = 0.5 * group.rect.width(); - QRectF rect(group.rect); - rect.adjust(pen.widthF(), pen.widthF(), -pen.widthF(), -pen.widthF()); - painter->drawRoundedRect(rect, radius, radius); - } - } - // Prepare for drawing missing items. - status = RelinkingModel::Missing; - pen.setColor(QColor(0x6f2719)); - brush.setColor(QColor(0xff674b)); + } + + painter->setRenderHint(QPainter::Antialiasing); + + // Prepare for drawing existing items. + QPen pen(QColor(0x3a5827)); + pen.setWidthF(1.5); + QBrush brush(QColor(0x89e74a)); + + // Draw existing, then missing items. + for (int status = RelinkingModel::Exists, i = 0; i < 2; ++i) { + painter->setPen(pen); + painter->setBrush(brush); + + for (const IndicationGroup& group : group_aggregator.groups()) { + if (group.status == status) { + const qreal radius = 0.5 * group.rect.width(); + QRectF rect(group.rect); + rect.adjust(pen.widthF(), pen.widthF(), -pen.widthF(), -pen.widthF()); + painter->drawRoundedRect(rect, radius, radius); + } } + // Prepare for drawing missing items. + status = RelinkingModel::Missing; + pen.setColor(QColor(0x6f2719)); + brush.setColor(QColor(0xff674b)); + } } // RelinkingListView::drawStatusLayer void RelinkingListView::GroupAggregator::process(const QRect& rect, int status) { - if (m_groups.empty() || (m_groups.back().status != status)) { - m_groups.emplace_back(rect, status); - } else { - m_groups.back().rect |= rect; - } + if (m_groups.empty() || (m_groups.back().status != status)) { + m_groups.emplace_back(rect, status); + } else { + m_groups.back().rect |= rect; + } } diff --git a/RelinkingListView.h b/RelinkingListView.h index 5c2d91cc8..c3604dde6 100644 --- a/RelinkingListView.h +++ b/RelinkingListView.h @@ -26,23 +26,23 @@ class QRect; class QModelIndex; class RelinkingListView : public QListView { -public: - explicit RelinkingListView(QWidget* parent = nullptr); + public: + explicit RelinkingListView(QWidget* parent = nullptr); -protected: - void paintEvent(QPaintEvent* e) override; + protected: + void paintEvent(QPaintEvent* e) override; -private: - class Delegate; - class IndicationGroup; + private: + class Delegate; + class IndicationGroup; - class GroupAggregator; + class GroupAggregator; - void maybeDrawStatusLayer(QPainter* painter, const QModelIndex& item_index, const QRect& item_paint_rect); + void maybeDrawStatusLayer(QPainter* painter, const QModelIndex& item_index, const QRect& item_paint_rect); - void drawStatusLayer(QPainter* painter); + void drawStatusLayer(QPainter* painter); - bool m_statusLayerDrawn; + bool m_statusLayerDrawn; }; diff --git a/RelinkingModel.cpp b/RelinkingModel.cpp index 3ce511918..40e500fe1 100644 --- a/RelinkingModel.cpp +++ b/RelinkingModel.cpp @@ -17,441 +17,426 @@ */ #include "RelinkingModel.h" -#include "PayloadEvent.h" -#include "OutOfMemoryHandler.h" -#include +#include #include +#include #include -#include #include -#include +#include #include #include -#include +#include +#include "OutOfMemoryHandler.h" +#include "PayloadEvent.h" class RelinkingModel::StatusUpdateResponse { -public: - StatusUpdateResponse(const QString& path, int row, Status status) : m_path(path), m_row(row), m_status(status) { - } + public: + StatusUpdateResponse(const QString& path, int row, Status status) : m_path(path), m_row(row), m_status(status) {} - const QString& path() const { - return m_path; - } + const QString& path() const { return m_path; } - int row() const { - return m_row; - } + int row() const { return m_row; } - Status status() const { - return m_status; - } + Status status() const { return m_status; } -private: - QString m_path; - int m_row; - Status m_status; + private: + QString m_path; + int m_row; + Status m_status; }; class RelinkingModel::StatusUpdateThread : private QThread { -public: - explicit StatusUpdateThread(RelinkingModel* owner); - - /** This will signal the thread to stop and wait for it to happen. */ - ~StatusUpdateThread() override; - - /** - * Requests are served from last to first. - * Requesting the same item multiple times will just move the existing - * record to the top of the stack. - */ - void requestStatusUpdate(const QString& path, int row); - -private: - struct Task { - QString path; - int row; - - Task(const QString& p, int r) : path(p), row(r) { - } - }; - - class OrderedByPathTag; - class OrderedByPriorityTag; - - typedef boost::multi_index_container< - Task, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique, - boost::multi_index::member>, - boost::multi_index::sequenced>>> - TaskList; - - typedef TaskList::index::type TasksByPath; - typedef TaskList::index::type TasksByPriority; - - void run() override; - - RelinkingModel* m_pOwner; - TaskList m_tasks; - TasksByPath& m_rTasksByPath; - TasksByPriority& m_rTasksByPriority; - QString m_pathBeingProcessed; - QMutex m_mutex; - QWaitCondition m_cond; - bool m_exiting; + public: + explicit StatusUpdateThread(RelinkingModel* owner); + + /** This will signal the thread to stop and wait for it to happen. */ + ~StatusUpdateThread() override; + + /** + * Requests are served from last to first. + * Requesting the same item multiple times will just move the existing + * record to the top of the stack. + */ + void requestStatusUpdate(const QString& path, int row); + + private: + struct Task { + QString path; + int row; + + Task(const QString& p, int r) : path(p), row(r) {} + }; + + class OrderedByPathTag; + class OrderedByPriorityTag; + + typedef boost::multi_index_container< + Task, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique, + boost::multi_index::member>, + boost::multi_index::sequenced>>> + TaskList; + + typedef TaskList::index::type TasksByPath; + typedef TaskList::index::type TasksByPriority; + + void run() override; + + RelinkingModel* m_owner; + TaskList m_tasks; + TasksByPath& m_tasksByPath; + TasksByPriority& m_tasksByPriority; + QString m_pathBeingProcessed; + QMutex m_mutex; + QWaitCondition m_cond; + bool m_exiting; }; /*============================ RelinkingModel =============================*/ RelinkingModel::RelinkingModel() - : m_fileIcon(":/icons/file-16.png"), - m_folderIcon(":/icons/folder-16.png"), - m_ptrRelinker(new Relinker), - m_ptrStatusUpdateThread(new StatusUpdateThread(this)), - m_haveUncommittedChanges(true) { -} + : m_fileIcon(":/icons/file-16.png"), + m_folderIcon(":/icons/folder-16.png"), + m_relinker(new Relinker), + m_statusUpdateThread(new StatusUpdateThread(this)), + m_haveUncommittedChanges(true) {} RelinkingModel::~RelinkingModel() = default; int RelinkingModel::rowCount(const QModelIndex& parent) const { - if (!parent.isValid()) { - return static_cast(m_items.size()); - } else { - return 0; - } + if (!parent.isValid()) { + return static_cast(m_items.size()); + } else { + return 0; + } } QVariant RelinkingModel::data(const QModelIndex& index, int role) const { - const Item& item = m_items[index.row()]; - - switch (role) { - case TypeRole: - return item.type; - case UncommittedStatusRole: - return item.uncommittedStatus; - case UncommittedPathRole: - return item.uncommittedPath; - case Qt::DisplayRole: - if (item.uncommittedPath.startsWith(QChar('/')) && !item.uncommittedPath.startsWith(QLatin1String("//"))) { - // "//" indicates a network path - return item.uncommittedPath; - } else { - return QDir::toNativeSeparators(item.uncommittedPath); - } - case Qt::DecorationRole: - return (item.type == RelinkablePath::Dir) ? m_folderIcon : m_fileIcon; - case Qt::BackgroundColorRole: - return QColor(Qt::transparent); - default: - break; - } - - return QVariant(); + const Item& item = m_items[index.row()]; + + switch (role) { + case TypeRole: + return item.type; + case UncommittedStatusRole: + return item.uncommittedStatus; + case UncommittedPathRole: + return item.uncommittedPath; + case Qt::DisplayRole: + if (item.uncommittedPath.startsWith(QChar('/')) && !item.uncommittedPath.startsWith(QLatin1String("//"))) { + // "//" indicates a network path + return item.uncommittedPath; + } else { + return QDir::toNativeSeparators(item.uncommittedPath); + } + case Qt::DecorationRole: + return (item.type == RelinkablePath::Dir) ? m_folderIcon : m_fileIcon; + case Qt::BackgroundColorRole: + return QColor(Qt::transparent); + default: + break; + } + + return QVariant(); } void RelinkingModel::addPath(const RelinkablePath& path) { - const QString& normalized_path(path.normalizedPath()); + const QString& normalized_path(path.normalizedPath()); - const std::pair::iterator, bool> ins(m_origPathSet.insert(path.normalizedPath())); - if (!ins.second) { - // Not inserted because identical path is already there. - return; - } + const std::pair::iterator, bool> ins(m_origPathSet.insert(path.normalizedPath())); + if (!ins.second) { + // Not inserted because identical path is already there. + return; + } - beginInsertRows(QModelIndex(), static_cast(m_items.size()), static_cast(m_items.size())); - m_items.emplace_back(path); - endInsertRows(); + beginInsertRows(QModelIndex(), static_cast(m_items.size()), static_cast(m_items.size())); + m_items.emplace_back(path); + endInsertRows(); - requestStatusUpdate(index(static_cast(m_items.size() - 1))); + requestStatusUpdate(index(static_cast(m_items.size() - 1))); } void RelinkingModel::replacePrefix(const QString& prefix, const QString& replacement, RelinkablePath::Type type) { - QString slash_terminated_prefix(prefix); - ensureEndsWithSlash(slash_terminated_prefix); - - int modified_rowspan_begin = -1; - - int row = -1; - for (Item& item : m_items) { - ++row; - bool modified = false; - - if (type == RelinkablePath::File) { - if ((item.type == RelinkablePath::File) && (item.uncommittedPath == prefix)) { - item.uncommittedPath = replacement; - modified = true; - } - } else { - assert(type == RelinkablePath::Dir); - if (item.uncommittedPath.startsWith(slash_terminated_prefix)) { - const int suffix_len = item.uncommittedPath.length() - slash_terminated_prefix.length() + 1; - item.uncommittedPath = replacement + item.uncommittedPath.right(suffix_len); - modified = true; - } else if (item.uncommittedPath == prefix) { - item.uncommittedPath = replacement; - modified = true; - } - } - - if (modified) { - m_haveUncommittedChanges = true; - if (modified_rowspan_begin == -1) { - modified_rowspan_begin = row; - } - emit dataChanged(index(modified_rowspan_begin), index(row)); - requestStatusUpdate(index(row)); // This sets item.changedStatus to StatusUpdatePending. - } else { - if (modified_rowspan_begin != -1) { - emit dataChanged(index(modified_rowspan_begin), index(row)); - modified_rowspan_begin = -1; - } - } + QString slash_terminated_prefix(prefix); + ensureEndsWithSlash(slash_terminated_prefix); + + int modified_rowspan_begin = -1; + + int row = -1; + for (Item& item : m_items) { + ++row; + bool modified = false; + + if (type == RelinkablePath::File) { + if ((item.type == RelinkablePath::File) && (item.uncommittedPath == prefix)) { + item.uncommittedPath = replacement; + modified = true; + } + } else { + assert(type == RelinkablePath::Dir); + if (item.uncommittedPath.startsWith(slash_terminated_prefix)) { + const int suffix_len = item.uncommittedPath.length() - slash_terminated_prefix.length() + 1; + item.uncommittedPath = replacement + item.uncommittedPath.right(suffix_len); + modified = true; + } else if (item.uncommittedPath == prefix) { + item.uncommittedPath = replacement; + modified = true; + } } - if (modified_rowspan_begin != -1) { + if (modified) { + m_haveUncommittedChanges = true; + if (modified_rowspan_begin == -1) { + modified_rowspan_begin = row; + } + emit dataChanged(index(modified_rowspan_begin), index(row)); + requestStatusUpdate(index(row)); // This sets item.changedStatus to StatusUpdatePending. + } else { + if (modified_rowspan_begin != -1) { emit dataChanged(index(modified_rowspan_begin), index(row)); + modified_rowspan_begin = -1; + } } + } + + if (modified_rowspan_begin != -1) { + emit dataChanged(index(modified_rowspan_begin), index(row)); + } } // RelinkingModel::replacePrefix bool RelinkingModel::checkForMerges() const { - std::vector new_paths; - new_paths.reserve(m_items.size()); + std::vector new_paths; + new_paths.reserve(m_items.size()); - for (const Item& item : m_items) { - new_paths.push_back(item.uncommittedPath); - } + for (const Item& item : m_items) { + new_paths.push_back(item.uncommittedPath); + } - std::sort(new_paths.begin(), new_paths.end()); + std::sort(new_paths.begin(), new_paths.end()); - return std::adjacent_find(new_paths.begin(), new_paths.end()) != new_paths.end(); + return std::adjacent_find(new_paths.begin(), new_paths.end()) != new_paths.end(); } void RelinkingModel::commitChanges() { - if (!m_haveUncommittedChanges) { - return; - } - - Relinker new_relinker; - int modified_rowspan_begin = -1; - - int row = -1; - for (Item& item : m_items) { - ++row; - - if (item.committedPath != item.uncommittedPath) { - item.committedPath = item.uncommittedPath; - item.committedStatus = item.uncommittedStatus; - new_relinker.addMapping(item.origPath, item.committedPath); - if (modified_rowspan_begin == -1) { - modified_rowspan_begin = row; - } - } else { - if (modified_rowspan_begin != -1) { - emit dataChanged(index(modified_rowspan_begin), index(row)); - modified_rowspan_begin = -1; - } - } - } - - if (modified_rowspan_begin != -1) { + if (!m_haveUncommittedChanges) { + return; + } + + Relinker new_relinker; + int modified_rowspan_begin = -1; + + int row = -1; + for (Item& item : m_items) { + ++row; + + if (item.committedPath != item.uncommittedPath) { + item.committedPath = item.uncommittedPath; + item.committedStatus = item.uncommittedStatus; + new_relinker.addMapping(item.origPath, item.committedPath); + if (modified_rowspan_begin == -1) { + modified_rowspan_begin = row; + } + } else { + if (modified_rowspan_begin != -1) { emit dataChanged(index(modified_rowspan_begin), index(row)); + modified_rowspan_begin = -1; + } } + } + + if (modified_rowspan_begin != -1) { + emit dataChanged(index(modified_rowspan_begin), index(row)); + } - m_ptrRelinker->swap(new_relinker); - m_haveUncommittedChanges = false; + m_relinker->swap(new_relinker); + m_haveUncommittedChanges = false; } // RelinkingModel::commitChanges void RelinkingModel::rollbackChanges() { - if (!m_haveUncommittedChanges) { - return; - } - - int modified_rowspan_begin = -1; - - int row = -1; - for (Item& item : m_items) { - ++row; - - if (item.uncommittedPath != item.committedPath) { - item.uncommittedPath = item.committedPath; - item.uncommittedStatus = item.committedStatus; - if (modified_rowspan_begin == -1) { - modified_rowspan_begin = row; - } - } else { - if (modified_rowspan_begin != -1) { - emit dataChanged(index(modified_rowspan_begin), index(row)); - modified_rowspan_begin = -1; - } - } - } - - if (modified_rowspan_begin != -1) { + if (!m_haveUncommittedChanges) { + return; + } + + int modified_rowspan_begin = -1; + + int row = -1; + for (Item& item : m_items) { + ++row; + + if (item.uncommittedPath != item.committedPath) { + item.uncommittedPath = item.committedPath; + item.uncommittedStatus = item.committedStatus; + if (modified_rowspan_begin == -1) { + modified_rowspan_begin = row; + } + } else { + if (modified_rowspan_begin != -1) { emit dataChanged(index(modified_rowspan_begin), index(row)); + modified_rowspan_begin = -1; + } } + } + + if (modified_rowspan_begin != -1) { + emit dataChanged(index(modified_rowspan_begin), index(row)); + } - m_haveUncommittedChanges = false; + m_haveUncommittedChanges = false; } // RelinkingModel::rollbackChanges void RelinkingModel::ensureEndsWithSlash(QString& str) { - if (!str.endsWith(QChar('/'))) { - str += QChar('/'); - } + if (!str.endsWith(QChar('/'))) { + str += QChar('/'); + } } void RelinkingModel::requestStatusUpdate(const QModelIndex& index) { - assert(index.isValid()); + assert(index.isValid()); - Item& item = m_items[index.row()]; - item.uncommittedStatus = StatusUpdatePending; + Item& item = m_items[index.row()]; + item.uncommittedStatus = StatusUpdatePending; - m_ptrStatusUpdateThread->requestStatusUpdate(item.uncommittedPath, index.row()); + m_statusUpdateThread->requestStatusUpdate(item.uncommittedPath, index.row()); } void RelinkingModel::customEvent(QEvent* event) { - typedef PayloadEvent ResponseEvent; - auto* evt = dynamic_cast(event); - assert(evt); - - const StatusUpdateResponse& response = evt->payload(); - if ((response.row() < 0) || (response.row() >= int(m_items.size()))) { - return; - } - - Item& item = m_items[response.row()]; - if (item.uncommittedPath == response.path()) { - item.uncommittedStatus = response.status(); - } - if (item.committedPath == response.path()) { - item.committedStatus = response.status(); - } - - emit dataChanged(index(response.row()), index(response.row())); + typedef PayloadEvent ResponseEvent; + auto* evt = dynamic_cast(event); + assert(evt); + + const StatusUpdateResponse& response = evt->payload(); + if ((response.row() < 0) || (response.row() >= int(m_items.size()))) { + return; + } + + Item& item = m_items[response.row()]; + if (item.uncommittedPath == response.path()) { + item.uncommittedStatus = response.status(); + } + if (item.committedPath == response.path()) { + item.committedStatus = response.status(); + } + + emit dataChanged(index(response.row()), index(response.row())); } /*========================== StatusUpdateThread =========================*/ RelinkingModel::StatusUpdateThread::StatusUpdateThread(RelinkingModel* owner) - : QThread(owner), - m_pOwner(owner), - m_tasks(), - m_rTasksByPath(m_tasks.get()), - m_rTasksByPriority(m_tasks.get()), - m_exiting(false) { -} + : QThread(owner), + m_owner(owner), + m_tasks(), + m_tasksByPath(m_tasks.get()), + m_tasksByPriority(m_tasks.get()), + m_exiting(false) {} RelinkingModel::StatusUpdateThread::~StatusUpdateThread() { - { - QMutexLocker locker(&m_mutex); - m_exiting = true; - } + { + QMutexLocker locker(&m_mutex); + m_exiting = true; + } - m_cond.wakeAll(); - wait(); + m_cond.wakeAll(); + wait(); } void RelinkingModel::StatusUpdateThread::requestStatusUpdate(const QString& path, int row) { - const QMutexLocker locker(&m_mutex); - if (m_exiting) { - return; - } + const QMutexLocker locker(&m_mutex); + if (m_exiting) { + return; + } - if (path == m_pathBeingProcessed) { - // This task is currently in progress. - return; - } + if (path == m_pathBeingProcessed) { + // This task is currently in progress. + return; + } - const std::pair ins(m_rTasksByPath.insert(Task(path, row))); + const std::pair ins(m_tasksByPath.insert(Task(path, row))); - // Whether inserted or being already there, move it to the front of priority queue. - m_rTasksByPriority.relocate(m_rTasksByPriority.end(), m_tasks.project(ins.first)); + // Whether inserted or being already there, move it to the front of priority queue. + m_tasksByPriority.relocate(m_tasksByPriority.end(), m_tasks.project(ins.first)); - if (!isRunning()) { - start(); - } + if (!isRunning()) { + start(); + } - m_cond.wakeOne(); + m_cond.wakeOne(); } void RelinkingModel::StatusUpdateThread::run() try { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - class MutexUnlocker { - public: - explicit MutexUnlocker(QMutex* mutex) : m_pMutex(mutex) { - mutex->unlock(); - } + class MutexUnlocker { + public: + explicit MutexUnlocker(QMutex* mutex) : m_mutex(mutex) { mutex->unlock(); } - ~MutexUnlocker() { - m_pMutex->lock(); - } + ~MutexUnlocker() { m_mutex->lock(); } - private: - QMutex* const m_pMutex; - }; + private: + QMutex* const m_mutex; + }; - for (;;) { - if (m_exiting) { - break; - } - - if (m_tasks.empty()) { - m_cond.wait(&m_mutex); - } + while (true) { + if (m_exiting) { + break; + } - if (m_tasks.empty()) { - continue; - } + if (m_tasks.empty()) { + m_cond.wait(&m_mutex); + } - const Task task(m_rTasksByPriority.front()); - m_pathBeingProcessed = task.path; - m_rTasksByPriority.pop_front(); + if (m_tasks.empty()) { + continue; + } - { - const MutexUnlocker unlocker(&m_mutex); + const Task task(m_tasksByPriority.front()); + m_pathBeingProcessed = task.path; + m_tasksByPriority.pop_front(); - const bool exists = QFile::exists(task.path); - const StatusUpdateResponse response(task.path, task.row, exists ? Exists : Missing); - QCoreApplication::postEvent(m_pOwner, new PayloadEvent(response)); - } + { + const MutexUnlocker unlocker(&m_mutex); - m_pathBeingProcessed.clear(); + const bool exists = QFile::exists(task.path); + const StatusUpdateResponse response(task.path, task.row, exists ? Exists : Missing); + QCoreApplication::postEvent(m_owner, new PayloadEvent(response)); } + + m_pathBeingProcessed.clear(); + } } // RelinkingModel::StatusUpdateThread::run catch (const std::bad_alloc&) { - OutOfMemoryHandler::instance().handleOutOfMemorySituation(); + OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } /*================================ Item =================================*/ RelinkingModel::Item::Item(const RelinkablePath& path) - : origPath(path.normalizedPath()), - committedPath(path.normalizedPath()), - uncommittedPath(path.normalizedPath()), - type(path.type()), - committedStatus(StatusUpdatePending), - uncommittedStatus(StatusUpdatePending) { -} + : origPath(path.normalizedPath()), + committedPath(path.normalizedPath()), + uncommittedPath(path.normalizedPath()), + type(path.type()), + committedStatus(StatusUpdatePending), + uncommittedStatus(StatusUpdatePending) {} /*============================== Relinker ================================*/ void RelinkingModel::Relinker::addMapping(const QString& from, const QString& to) { - m_mappings[from] = to; + m_mappings[from] = to; } QString RelinkingModel::Relinker::substitutionPathFor(const RelinkablePath& path) const { - const auto it(m_mappings.find(path.normalizedPath())); - if (it != m_mappings.end()) { - return it->second; - } else { - return path.normalizedPath(); - } + const auto it(m_mappings.find(path.normalizedPath())); + if (it != m_mappings.end()) { + return it->second; + } else { + return path.normalizedPath(); + } } void RelinkingModel::Relinker::swap(Relinker& other) { - m_mappings.swap(other.m_mappings); + m_mappings.swap(other.m_mappings); } diff --git a/RelinkingModel.h b/RelinkingModel.h index d9f0c1f14..22d2af8f2 100644 --- a/RelinkingModel.h +++ b/RelinkingModel.h @@ -19,120 +19,116 @@ #ifndef RELINKING_MODEL_H_ #define RELINKING_MODEL_H_ -#include "NonCopyable.h" -#include "VirtualFunction.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" -#include "intrusive_ptr.h" -#include "Hashes.h" -#include -#include #include +#include #include +#include #include #include #include -#include #include #include +#include +#include "AbstractRelinker.h" +#include "Hashes.h" +#include "NonCopyable.h" +#include "RelinkablePath.h" +#include "VirtualFunction.h" +#include "intrusive_ptr.h" class RelinkingModel : public QAbstractListModel { - DECLARE_NON_COPYABLE(RelinkingModel) + DECLARE_NON_COPYABLE(RelinkingModel) -public: - enum Status { Exists, Missing, StatusUpdatePending }; + public: + enum Status { Exists, Missing, StatusUpdatePending }; - enum { TypeRole = Qt::UserRole, UncommittedPathRole, UncommittedStatusRole }; + enum { TypeRole = Qt::UserRole, UncommittedPathRole, UncommittedStatusRole }; - RelinkingModel(); + RelinkingModel(); - ~RelinkingModel() override; + ~RelinkingModel() override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - /** - * This method guarantees that - * \code - * model.relinker().release() == model.relinker().release() - * \endcode - * will hold true for the lifetime of RelinkingModel object. - * This allows you to take the relinker right after construction - * and then use it when accepted() signal is emitted. - */ - intrusive_ptr relinker() const { - return m_ptrRelinker; - } + /** + * This method guarantees that + * \code + * model.relinker().release() == model.relinker().release() + * \endcode + * will hold true for the lifetime of RelinkingModel object. + * This allows you to take the relinker right after construction + * and then use it when accepted() signal is emitted. + */ + intrusive_ptr relinker() const { return m_relinker; } - void operator()(const RelinkablePath& path) { - addPath(path); - } + void operator()(const RelinkablePath& path) { addPath(path); } - void addPath(const RelinkablePath& path); + void addPath(const RelinkablePath& path); - void replacePrefix(const QString& prefix, const QString& replacement, RelinkablePath::Type type); + void replacePrefix(const QString& prefix, const QString& replacement, RelinkablePath::Type type); - /** - * Returns true if we have different original paths remapped to the same one. - * Checking is done on uncommitted paths. - */ - bool checkForMerges() const; + /** + * Returns true if we have different original paths remapped to the same one. + * Checking is done on uncommitted paths. + */ + bool checkForMerges() const; - void commitChanges(); + void commitChanges(); - void rollbackChanges(); + void rollbackChanges(); - void requestStatusUpdate(const QModelIndex& index); + void requestStatusUpdate(const QModelIndex& index); -protected: - void customEvent(QEvent* event) override; + protected: + void customEvent(QEvent* event) override; -private: - class StatusUpdateThread; - class StatusUpdateResponse; + private: + class StatusUpdateThread; + class StatusUpdateResponse; - /** Stands for File System Object (file or directory). */ - struct Item { - QString origPath; + /** Stands for File System Object (file or directory). */ + struct Item { + QString origPath; - /**< That's the path passed through addPath(). It never changes. */ - QString committedPath; - QString uncommittedPath; + /**< That's the path passed through addPath(). It never changes. */ + QString committedPath; + QString uncommittedPath; - /**< Same as committedPath when m_haveUncommittedChanges == false. */ - RelinkablePath::Type type; - Status committedStatus; - Status uncommittedStatus; + /**< Same as committedPath when m_haveUncommittedChanges == false. */ + RelinkablePath::Type type; + Status committedStatus; + Status uncommittedStatus; - /**< Same as committedStatus when m_haveUncommittedChanges == false. */ + /**< Same as committedStatus when m_haveUncommittedChanges == false. */ - explicit Item(const RelinkablePath& path); - }; + explicit Item(const RelinkablePath& path); + }; - class Relinker : public AbstractRelinker { - public: - void addMapping(const QString& from, const QString& to); + class Relinker : public AbstractRelinker { + public: + void addMapping(const QString& from, const QString& to); - /** Returns path.normalizedPath() if there is no mapping for it. */ - QString substitutionPathFor(const RelinkablePath& path) const override; + /** Returns path.normalizedPath() if there is no mapping for it. */ + QString substitutionPathFor(const RelinkablePath& path) const override; - void swap(Relinker& other); + void swap(Relinker& other); - private: - std::unordered_map> m_mappings; - }; + private: + std::unordered_map> m_mappings; + }; - static void ensureEndsWithSlash(QString& str); + static void ensureEndsWithSlash(QString& str); - QPixmap m_fileIcon; - QPixmap m_folderIcon; - std::vector m_items; - std::set m_origPathSet; - const intrusive_ptr m_ptrRelinker; - std::unique_ptr m_ptrStatusUpdateThread; - bool m_haveUncommittedChanges; + QPixmap m_fileIcon; + QPixmap m_folderIcon; + std::vector m_items; + std::set m_origPathSet; + const intrusive_ptr m_relinker; + std::unique_ptr m_statusUpdateThread; + bool m_haveUncommittedChanges; }; diff --git a/RelinkingSortingModel.cpp b/RelinkingSortingModel.cpp index d04b8d79f..a766f9ed7 100644 --- a/RelinkingSortingModel.cpp +++ b/RelinkingSortingModel.cpp @@ -17,44 +17,44 @@ */ #include "RelinkingSortingModel.h" -#include "RelinkingModel.h" #include +#include "RelinkingModel.h" RelinkingSortingModel::RelinkingSortingModel(QObject* parent) : QSortFilterProxyModel(parent) { - setDynamicSortFilter(true); - sort(0); + setDynamicSortFilter(true); + sort(0); } bool RelinkingSortingModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { - const int left_status = left.data(RelinkingModel::UncommittedStatusRole).toInt(); - const int right_status = right.data(RelinkingModel::UncommittedStatusRole).toInt(); - if (left_status != right_status) { - if (left_status == RelinkingModel::Missing) { - return true; // Missing items go before other ones. - } else if (right_status == RelinkingModel::Missing) { - return false; - } else if (left_status == RelinkingModel::StatusUpdatePending) { - return true; // These go after missing ones. - } else if (right_status == RelinkingModel::StatusUpdatePending) { - return false; - } - assert(!"Unreachable"); + const int left_status = left.data(RelinkingModel::UncommittedStatusRole).toInt(); + const int right_status = right.data(RelinkingModel::UncommittedStatusRole).toInt(); + if (left_status != right_status) { + if (left_status == RelinkingModel::Missing) { + return true; // Missing items go before other ones. + } else if (right_status == RelinkingModel::Missing) { + return false; + } else if (left_status == RelinkingModel::StatusUpdatePending) { + return true; // These go after missing ones. + } else if (right_status == RelinkingModel::StatusUpdatePending) { + return false; } - - const int left_type = left.data(RelinkingModel::TypeRole).toInt(); - const int right_type = right.data(RelinkingModel::TypeRole).toInt(); - const QString left_path(left.data(RelinkingModel::UncommittedPathRole).toString()); - const QString right_path(right.data(RelinkingModel::UncommittedPathRole).toString()); - - if (left_type != right_type) { - // Directories go above their siblings. - const QString left_dir(left_path.left(left_path.lastIndexOf(QChar('/')))); - const QString right_dir(right_path.left(right_path.lastIndexOf(QChar('/')))); - if (left_dir == right_dir) { - return left_type == RelinkablePath::Dir; - } + assert(!"Unreachable"); + } + + const int left_type = left.data(RelinkingModel::TypeRole).toInt(); + const int right_type = right.data(RelinkingModel::TypeRole).toInt(); + const QString left_path(left.data(RelinkingModel::UncommittedPathRole).toString()); + const QString right_path(right.data(RelinkingModel::UncommittedPathRole).toString()); + + if (left_type != right_type) { + // Directories go above their siblings. + const QString left_dir(left_path.left(left_path.lastIndexOf(QChar('/')))); + const QString right_dir(right_path.left(right_path.lastIndexOf(QChar('/')))); + if (left_dir == right_dir) { + return left_type == RelinkablePath::Dir; } + } - // The last resort is lexicographical ordering. - return left_path < right_path; + // The last resort is lexicographical ordering. + return left_path < right_path; } // RelinkingSortingModel::lessThan diff --git a/RelinkingSortingModel.h b/RelinkingSortingModel.h index 43e637f78..a5f3e781e 100644 --- a/RelinkingSortingModel.h +++ b/RelinkingSortingModel.h @@ -22,11 +22,11 @@ #include class RelinkingSortingModel : public QSortFilterProxyModel { -public: - explicit RelinkingSortingModel(QObject* parent = nullptr); + public: + explicit RelinkingSortingModel(QObject* parent = nullptr); -protected: - bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + protected: + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; }; diff --git a/SelectedPage.cpp b/SelectedPage.cpp index 171d73428..95e743ecf 100644 --- a/SelectedPage.cpp +++ b/SelectedPage.cpp @@ -19,19 +19,19 @@ #include "SelectedPage.h" SelectedPage::SelectedPage(const PageId& page_id, PageView view) { - set(page_id, view); + set(page_id, view); } void SelectedPage::set(const PageId& page_id, PageView view) { - if ((view == PAGE_VIEW) || (page_id.imageId() != m_pageId.imageId())) { - m_pageId = page_id; - } + if ((view == PAGE_VIEW) || (page_id.imageId() != m_pageId.imageId())) { + m_pageId = page_id; + } } PageId SelectedPage::get(PageView view) const { - if (view == PAGE_VIEW) { - return m_pageId; - } else { - return PageId(m_pageId.imageId(), PageId::SINGLE_PAGE); - } + if (view == PAGE_VIEW) { + return m_pageId; + } else { + return PageId(m_pageId.imageId(), PageId::SINGLE_PAGE); + } } diff --git a/SelectedPage.h b/SelectedPage.h index 88e293569..2b4c38a68 100644 --- a/SelectedPage.h +++ b/SelectedPage.h @@ -35,21 +35,19 @@ * the sub-page, while get(IMAGE_VIEW) will always return SINGLE_PAGE sub-pages. */ class SelectedPage { -public: - SelectedPage() = default; + public: + SelectedPage() = default; - SelectedPage(const PageId& page_id, PageView view); + SelectedPage(const PageId& page_id, PageView view); - bool isNull() const { - return m_pageId.isNull(); - } + bool isNull() const { return m_pageId.isNull(); } - void set(const PageId& page_id, PageView view); + void set(const PageId& page_id, PageView view); - PageId get(PageView view) const; + PageId get(PageView view) const; -private: - PageId m_pageId; + private: + PageId m_pageId; }; diff --git a/SettingsDialog.cpp b/SettingsDialog.cpp index 53626b9fc..023151090 100644 --- a/SettingsDialog.cpp +++ b/SettingsDialog.cpp @@ -17,106 +17,139 @@ */ #include "SettingsDialog.h" -#include "OpenGLSupport.h" -#include "Application.h" -#include -#include #include +#include #include +#include +#include +#include "Application.h" +#include "OpenGLSupport.h" SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) { - ui.setupUi(this); - - QSettings settings; - - if (!OpenGLSupport::supported()) { - ui.enableOpenglCb->setChecked(false); - ui.enableOpenglCb->setEnabled(false); - ui.openglDeviceLabel->setEnabled(false); - ui.openglDeviceLabel->setText(tr("Your hardware / driver don't provide the necessary features")); - } else { - ui.enableOpenglCb->setChecked(settings.value("settings/enable_opengl", false).toBool()); - const QString openglDevicePattern = ui.openglDeviceLabel->text(); - ui.openglDeviceLabel->setText(openglDevicePattern.arg(OpenGLSupport::deviceName())); - } - - ui.colorSchemeBox->addItem(tr("Dark")); - ui.colorSchemeBox->addItem(tr("Light")); - QString val = settings.value("settings/color_scheme", "dark").toString(); - if (val == "light") { - ui.colorSchemeBox->setCurrentIndex(1); - } else { - ui.colorSchemeBox->setCurrentIndex(0); + ui.setupUi(this); + + QSettings settings; + + if (!OpenGLSupport::supported()) { + ui.enableOpenglCb->setChecked(false); + ui.enableOpenglCb->setEnabled(false); + ui.openglDeviceLabel->setEnabled(false); + ui.openglDeviceLabel->setText(tr("Your hardware / driver don't provide the necessary features")); + } else { + ui.enableOpenglCb->setChecked(settings.value("settings/enable_opengl", false).toBool()); + const QString openglDevicePattern = ui.openglDeviceLabel->text(); + ui.openglDeviceLabel->setText(openglDevicePattern.arg(OpenGLSupport::deviceName())); + } + + ui.colorSchemeBox->addItem(tr("Dark")); + ui.colorSchemeBox->addItem(tr("Light")); + ui.colorSchemeBox->addItem(tr("Native")); + QString val = settings.value("settings/color_scheme", "dark").toString(); + if (val == "native") { + ui.colorSchemeBox->setCurrentIndex(2); + } else if (val == "light") { + ui.colorSchemeBox->setCurrentIndex(1); + } else { + ui.colorSchemeBox->setCurrentIndex(0); + } + + ui.tiffCompressionBWBox->addItem(tr("None"), COMPRESSION_NONE); + ui.tiffCompressionBWBox->addItem(tr("LZW"), COMPRESSION_LZW); + ui.tiffCompressionBWBox->addItem(tr("Deflate"), COMPRESSION_DEFLATE); + ui.tiffCompressionBWBox->addItem(tr("CCITT G4"), COMPRESSION_CCITTFAX4); + + ui.tiffCompressionBWBox->setCurrentIndex( + ui.tiffCompressionBWBox->findData(settings.value("settings/bw_compression", COMPRESSION_CCITTFAX4).toInt())); + + ui.tiffCompressionColorBox->addItem(tr("None"), COMPRESSION_NONE); + ui.tiffCompressionColorBox->addItem(tr("LZW"), COMPRESSION_LZW); + ui.tiffCompressionColorBox->addItem(tr("Deflate"), COMPRESSION_DEFLATE); + ui.tiffCompressionColorBox->addItem(tr("JPEG"), COMPRESSION_JPEG); + + ui.tiffCompressionColorBox->setCurrentIndex( + ui.tiffCompressionColorBox->findData(settings.value("settings/color_compression", COMPRESSION_LZW).toInt())); + + if (auto* app = dynamic_cast(qApp)) { + for (const QString& locale : app->getLanguagesList()) { + QString languageName = QLocale::languageToString(QLocale(locale).language()); + ui.languageBox->addItem(languageName, locale); } - ui.tiffCompressionBWBox->addItem(tr("None"), COMPRESSION_NONE); - ui.tiffCompressionBWBox->addItem(tr("LZW"), COMPRESSION_LZW); - ui.tiffCompressionBWBox->addItem(tr("Deflate"), COMPRESSION_DEFLATE); - ui.tiffCompressionBWBox->addItem(tr("CCITT G4"), COMPRESSION_CCITTFAX4); - - ui.tiffCompressionBWBox->setCurrentIndex(ui.tiffCompressionBWBox->findData( - settings.value("settings/bw_compression", COMPRESSION_CCITTFAX4).toInt())); - - ui.tiffCompressionColorBox->addItem(tr("None"), COMPRESSION_NONE); - ui.tiffCompressionColorBox->addItem(tr("LZW"), COMPRESSION_LZW); - ui.tiffCompressionColorBox->addItem(tr("Deflate"), COMPRESSION_DEFLATE); - ui.tiffCompressionColorBox->addItem(tr("JPEG"), COMPRESSION_JPEG); - - ui.tiffCompressionColorBox->setCurrentIndex(ui.tiffCompressionColorBox->findData( - settings.value("settings/color_compression", COMPRESSION_LZW).toInt())); - - if (auto* app = dynamic_cast(qApp)) { - for (const QString& locale : app->getLanguagesList()) { - QString languageName = QLocale::languageToString(QLocale(locale).language()); - ui.languageBox->addItem(languageName, locale); - } - - ui.languageBox->setCurrentIndex(ui.languageBox->findData(app->getCurrentLocale())); - - ui.languageBox->setEnabled(ui.languageBox->count() > 1); - } - - ui.deskewDeviationCoefSB->setValue(settings.value("settings/deskewDeviationCoef", 1.5).toDouble()); - ui.deskewDeviationThresholdSB->setValue(settings.value("settings/deskewDeviationThreshold", 1.0).toDouble()); - ui.selectContentDeviationCoefSB->setValue(settings.value("settings/selectContentDeviationCoef", 0.35).toDouble()); - ui.selectContentDeviationThresholdSB->setValue( - settings.value("settings/selectContentDeviationThreshold", 1.0).toDouble()); - ui.marginsDeviationCoefSB->setValue(settings.value("settings/marginsDeviationCoef", 0.35).toDouble()); - ui.marginsDeviationThresholdSB->setValue(settings.value("settings/marginsDeviationThreshold", 1.0).toDouble()); - - connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); - ui.autoSaveProjectCB->setChecked(settings.value("settings/auto_save_project").toBool()); - ui.highlightDeviationCB->setChecked(settings.value("settings/highlight_deviation", true).toBool()); - - connect(ui.colorSchemeBox, static_cast(&QComboBox::currentIndexChanged), [this](int) { - QMessageBox::information(this, tr("Information"), - tr("ScanTailor need to be restarted to apply the color scheme changes.")); - }); + ui.languageBox->setCurrentIndex(ui.languageBox->findData(app->getCurrentLocale())); + + ui.languageBox->setEnabled(ui.languageBox->count() > 1); + } + + ui.blackOnWhiteDetectionCB->setChecked(settings.value("settings/blackOnWhiteDetection", true).toBool()); + ui.blackOnWhiteDetectionAtOutputCB->setEnabled(ui.blackOnWhiteDetectionCB->isChecked()); + ui.blackOnWhiteDetectionAtOutputCB->setChecked( + settings.value("settings/blackOnWhiteDetectionAtOutput", true).toBool()); + connect(ui.blackOnWhiteDetectionCB, SIGNAL(clicked(bool)), SLOT(blackOnWhiteDetectionToggled(bool))); + + ui.deskewDeviationCoefSB->setValue(settings.value("settings/deskewDeviationCoef", 1.5).toDouble()); + ui.deskewDeviationThresholdSB->setValue(settings.value("settings/deskewDeviationThreshold", 1.0).toDouble()); + ui.selectContentDeviationCoefSB->setValue(settings.value("settings/selectContentDeviationCoef", 0.35).toDouble()); + ui.selectContentDeviationThresholdSB->setValue( + settings.value("settings/selectContentDeviationThreshold", 1.0).toDouble()); + ui.marginsDeviationCoefSB->setValue(settings.value("settings/marginsDeviationCoef", 0.35).toDouble()); + ui.marginsDeviationThresholdSB->setValue(settings.value("settings/marginsDeviationThreshold", 1.0).toDouble()); + + connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); + ui.autoSaveProjectCB->setChecked(settings.value("settings/auto_save_project").toBool()); + ui.highlightDeviationCB->setChecked(settings.value("settings/highlight_deviation", true).toBool()); + + connect(ui.colorSchemeBox, static_cast(&QComboBox::currentIndexChanged), [this](int) { + QMessageBox::information(this, tr("Information"), + tr("ScanTailor need to be restarted to apply the color scheme changes.")); + }); + + ui.thumbnailQualitySB->setValue(settings.value("settings/thumbnail_quality", QSize(200, 200)).toSize().width()); + ui.thumbnailSizeSB->setValue( + settings.value("settings/max_logical_thumb_size", QSizeF(250, 160)).toSizeF().toSize().width()); } SettingsDialog::~SettingsDialog() = default; void SettingsDialog::commitChanges() { - QSettings settings; - settings.setValue("settings/enable_opengl", ui.enableOpenglCb->isChecked()); - settings.setValue("settings/auto_save_project", ui.autoSaveProjectCB->isChecked()); - settings.setValue("settings/highlight_deviation", ui.highlightDeviationCB->isChecked()); - if (ui.colorSchemeBox->currentIndex() == 0) { - settings.setValue("settings/color_scheme", "dark"); - } else if (ui.colorSchemeBox->currentIndex() == 1) { - settings.setValue("settings/color_scheme", "light"); - } - - settings.setValue("settings/bw_compression", ui.tiffCompressionBWBox->currentData().toInt()); - settings.setValue("settings/color_compression", ui.tiffCompressionColorBox->currentData().toInt()); - settings.setValue("settings/language", ui.languageBox->currentData().toString()); - - settings.setValue("settings/deskewDeviationCoef", ui.deskewDeviationCoefSB->value()); - settings.setValue("settings/deskewDeviationThreshold", ui.deskewDeviationThresholdSB->value()); - settings.setValue("settings/selectContentDeviationCoef", ui.selectContentDeviationCoefSB->value()); - settings.setValue("settings/selectContentDeviationThreshold", ui.selectContentDeviationThresholdSB->value()); - settings.setValue("settings/marginsDeviationCoef", ui.marginsDeviationCoefSB->value()); - settings.setValue("settings/marginsDeviationThreshold", ui.marginsDeviationThresholdSB->value()); + QSettings settings; + settings.setValue("settings/enable_opengl", ui.enableOpenglCb->isChecked()); + settings.setValue("settings/auto_save_project", ui.autoSaveProjectCB->isChecked()); + settings.setValue("settings/highlight_deviation", ui.highlightDeviationCB->isChecked()); + if (ui.colorSchemeBox->currentIndex() == 0) { + settings.setValue("settings/color_scheme", "dark"); + } else if (ui.colorSchemeBox->currentIndex() == 1) { + settings.setValue("settings/color_scheme", "light"); + } else if (ui.colorSchemeBox->currentIndex() == 2) { + settings.setValue("settings/color_scheme", "native"); + } + + settings.setValue("settings/bw_compression", ui.tiffCompressionBWBox->currentData().toInt()); + settings.setValue("settings/color_compression", ui.tiffCompressionColorBox->currentData().toInt()); + settings.setValue("settings/language", ui.languageBox->currentData().toString()); + + settings.setValue("settings/deskewDeviationCoef", ui.deskewDeviationCoefSB->value()); + settings.setValue("settings/deskewDeviationThreshold", ui.deskewDeviationThresholdSB->value()); + settings.setValue("settings/selectContentDeviationCoef", ui.selectContentDeviationCoefSB->value()); + settings.setValue("settings/selectContentDeviationThreshold", ui.selectContentDeviationThresholdSB->value()); + settings.setValue("settings/marginsDeviationCoef", ui.marginsDeviationCoefSB->value()); + settings.setValue("settings/marginsDeviationThreshold", ui.marginsDeviationThresholdSB->value()); + + settings.setValue("settings/blackOnWhiteDetection", ui.blackOnWhiteDetectionCB->isChecked()); + settings.setValue("settings/blackOnWhiteDetectionAtOutput", ui.blackOnWhiteDetectionAtOutputCB->isChecked()); + + { + const int quality = ui.thumbnailQualitySB->value(); + settings.setValue("settings/thumbnail_quality", QSize(quality, quality)); + } + { + const double width = ui.thumbnailSizeSB->value(); + const double height = std::round((width * (16.0 / 25.0)) * 100) / 100; + settings.setValue("settings/max_logical_thumb_size", QSizeF(width, height)); + } + + emit settingsChanged(); +} - emit settingsChanged(); -} \ No newline at end of file +void SettingsDialog::blackOnWhiteDetectionToggled(bool checked) { + ui.blackOnWhiteDetectionAtOutputCB->setEnabled(checked); +} diff --git a/SettingsDialog.h b/SettingsDialog.h index 730a8e136..0fa8d8149 100644 --- a/SettingsDialog.h +++ b/SettingsDialog.h @@ -19,27 +19,26 @@ #ifndef SETTINGS_DIALOG_H_ #define SETTINGS_DIALOG_H_ -#include "ui_SettingsDialog.h" #include +#include "ui_SettingsDialog.h" class SettingsDialog : public QDialog { - Q_OBJECT -public: - explicit SettingsDialog(QWidget* parent = nullptr); - - ~SettingsDialog() override; - + Q_OBJECT + public: + explicit SettingsDialog(QWidget* parent = nullptr); -signals: + ~SettingsDialog() override; - void settingsChanged(); + signals: + void settingsChanged(); -private slots: + private slots: + void commitChanges(); - void commitChanges(); + void blackOnWhiteDetectionToggled(bool checked); -private: - Ui::SettingsDialog ui; + private: + Ui::SettingsDialog ui; }; diff --git a/SkinnedButton.cpp b/SkinnedButton.cpp index 3aee96967..920768769 100644 --- a/SkinnedButton.cpp +++ b/SkinnedButton.cpp @@ -20,65 +20,68 @@ #include SkinnedButton::SkinnedButton(const QString& file, QWidget* parent) - : QToolButton(parent), m_normalStatePixmap(file), m_normalStateFile(file) { - updateStyleSheet(); + : QToolButton(parent), m_normalStatePixmap(file), m_normalStateFile(file) { + updateStyleSheet(); } SkinnedButton::SkinnedButton(const QString& normal_state_file, const QString& hover_state_file, const QString& pressed_state_file, QWidget* parent) - : QToolButton(parent), - m_normalStatePixmap(normal_state_file), - m_normalStateFile(normal_state_file), - m_hoverStateFile(hover_state_file), - m_pressedStateFile(pressed_state_file) { - updateStyleSheet(); + : QToolButton(parent), + m_normalStatePixmap(normal_state_file), + m_normalStateFile(normal_state_file), + m_hoverStateFile(hover_state_file), + m_pressedStateFile(pressed_state_file) { + updateStyleSheet(); } void SkinnedButton::setHoverImage(const QString& file) { - m_hoverStateFile = file; - updateStyleSheet(); + m_hoverStateFile = file; + updateStyleSheet(); } void SkinnedButton::setPressedImage(const QString& file) { - m_pressedStateFile = file; - updateStyleSheet(); + m_pressedStateFile = file; + updateStyleSheet(); } void SkinnedButton::setMask() { - setMask(m_normalStatePixmap.mask()); + setMask(m_normalStatePixmap.mask()); } QSize SkinnedButton::sizeHint() const { - if (m_normalStatePixmap.isNull()) { - return QToolButton::sizeHint(); - } else { - return m_normalStatePixmap.size(); - } + if (m_normalStatePixmap.isNull()) { + return QToolButton::sizeHint(); + } else { + return m_normalStatePixmap.size(); + } } void SkinnedButton::updateStyleSheet() { - QString style = QString("QToolButton {" - "border: none;" - "background: transparent;" - "image: url(%1);" - "}") - .arg(m_normalStateFile); + QString style = QString( + "QToolButton {" + "border: none;" + "background: transparent;" + "image: url(%1);" + "}") + .arg(m_normalStateFile); - if (!m_hoverStateFile.isEmpty()) { - style += QString("QToolButton:hover {" - "image: url(%1);" - "}") - .arg(m_hoverStateFile); - } + if (!m_hoverStateFile.isEmpty()) { + style += QString( + "QToolButton:hover {" + "image: url(%1);" + "}") + .arg(m_hoverStateFile); + } - if (!m_pressedStateFile.isEmpty()) { - style += QString("QToolButton:hover:pressed {" - "image: url(%1);" - "}") - .arg(m_pressedStateFile); - } + if (!m_pressedStateFile.isEmpty()) { + style += QString( + "QToolButton:hover:pressed {" + "image: url(%1);" + "}") + .arg(m_pressedStateFile); + } - setStyleSheet(style); + setStyleSheet(style); } diff --git a/SkinnedButton.h b/SkinnedButton.h index 4201112d8..b3e30a286 100644 --- a/SkinnedButton.h +++ b/SkinnedButton.h @@ -19,9 +19,9 @@ #ifndef SKINNEDBUTTON_H_ #define SKINNEDBUTTON_H_ -#include #include #include +#include /** * \brief A button represented by a set of images. @@ -33,81 +33,81 @@ * \li The pressed state image. */ class SkinnedButton : public QToolButton { -public: - /** - * \brief Construct a skinned button from a single image. - * - * \param file The path to a file or a Qt resource to the - * image representing the normal state of the button. - * \param parent An optional parent widget. - */ - explicit SkinnedButton(const QString& file, QWidget* parent = nullptr); - - /** - * \brief Construct a skinned button from a set of 3 images. - * - * \param normal_state_file The path to a file or a Qt resource - * to the image representing the normal state of the button. - * \param hover_state_file The path to a file or a Qt resource - * to the image representing the hover state of the button. - * \param pressed_state_file The path to a file or a Qt resource - * to the image representing the pressed state of the button. - * \param parent An optional parent widget. - * - * Note that the sizes of all 3 images should be the same. - */ - SkinnedButton(const QString& normal_state_file, - const QString& hover_state_file, - const QString& pressed_state_file, - QWidget* parent = nullptr); - - /** - * \brief Set the hover state image. - * - * \param file The path to a file or a Qt resource to the - * image representing the hover state of the button. - * This image should have the same size as the normal - * state image. - */ - void setHoverImage(const QString& file); - - /** - * \brief Set the pressed state image. - * - * \param file The path to a file or a Qt resource to the - * image representing the pressed state of the button. - * This image should have the same size as the normal - * state image. - */ - void setPressedImage(const QString& file); - - /** - * \brief Set the mask of the widget based on the alpha channel - * of the normal state image. - * - * The mask affects things like the mouse-over handling. - */ - void setMask(); - - /** - * Bring in the other signatures of setMask(). - */ - using QToolButton::setMask; - - /** - * \brief Reimplemented sizeHint(). - * - * \return The size of the normal state image. - */ - QSize sizeHint() const override; - -private: - void updateStyleSheet(); - - QPixmap m_normalStatePixmap; - QString m_normalStateFile; - QString m_hoverStateFile; - QString m_pressedStateFile; + public: + /** + * \brief Construct a skinned button from a single image. + * + * \param file The path to a file or a Qt resource to the + * image representing the normal state of the button. + * \param parent An optional parent widget. + */ + explicit SkinnedButton(const QString& file, QWidget* parent = nullptr); + + /** + * \brief Construct a skinned button from a set of 3 images. + * + * \param normal_state_file The path to a file or a Qt resource + * to the image representing the normal state of the button. + * \param hover_state_file The path to a file or a Qt resource + * to the image representing the hover state of the button. + * \param pressed_state_file The path to a file or a Qt resource + * to the image representing the pressed state of the button. + * \param parent An optional parent widget. + * + * Note that the sizes of all 3 images should be the same. + */ + SkinnedButton(const QString& normal_state_file, + const QString& hover_state_file, + const QString& pressed_state_file, + QWidget* parent = nullptr); + + /** + * \brief Set the hover state image. + * + * \param file The path to a file or a Qt resource to the + * image representing the hover state of the button. + * This image should have the same size as the normal + * state image. + */ + void setHoverImage(const QString& file); + + /** + * \brief Set the pressed state image. + * + * \param file The path to a file or a Qt resource to the + * image representing the pressed state of the button. + * This image should have the same size as the normal + * state image. + */ + void setPressedImage(const QString& file); + + /** + * \brief Set the mask of the widget based on the alpha channel + * of the normal state image. + * + * The mask affects things like the mouse-over handling. + */ + void setMask(); + + /** + * Bring in the other signatures of setMask(). + */ + using QToolButton::setMask; + + /** + * \brief Reimplemented sizeHint(). + * + * \return The size of the normal state image. + */ + QSize sizeHint() const override; + + private: + void updateStyleSheet(); + + QPixmap m_normalStatePixmap; + QString m_normalStateFile; + QString m_hoverStateFile; + QString m_pressedStateFile; }; diff --git a/SmartFilenameOrdering.cpp b/SmartFilenameOrdering.cpp index e574d0e6d..19ac80d4b 100644 --- a/SmartFilenameOrdering.cpp +++ b/SmartFilenameOrdering.cpp @@ -20,59 +20,59 @@ #include bool SmartFilenameOrdering::operator()(const QFileInfo& lhs, const QFileInfo& rhs) const { - // First compare directories. - if (int comp = lhs.absolutePath().compare(rhs.absolutePath())) { - return comp < 0; - } - - const QString lhs_fname(lhs.fileName()); - const QString rhs_fname(rhs.fileName()); - const QChar* lhs_ptr = lhs_fname.constData(); - const QChar* rhs_ptr = rhs_fname.constData(); - while (!lhs_ptr->isNull() && !rhs_ptr->isNull()) { - const bool lhs_is_digit = lhs_ptr->isDigit(); - const bool rhs_is_digit = rhs_ptr->isDigit(); - if (lhs_is_digit != rhs_is_digit) { - // Digits have priority over non-digits. - return lhs_is_digit; - } - - if (lhs_is_digit && rhs_is_digit) { - unsigned long lhs_number = 0; - do { - lhs_number = lhs_number * 10 + lhs_ptr->digitValue(); - ++lhs_ptr; - // Note: isDigit() implies !isNull() - } while (lhs_ptr->isDigit()); - - unsigned long rhs_number = 0; - do { - rhs_number = rhs_number * 10 + rhs_ptr->digitValue(); - ++rhs_ptr; - // Note: isDigit() implies !isNull() - } while (rhs_ptr->isDigit()); - - if (lhs_number != rhs_number) { - return lhs_number < rhs_number; - } else { - continue; - } - } + // First compare directories. + if (int comp = lhs.absolutePath().compare(rhs.absolutePath())) { + return comp < 0; + } - if (lhs_ptr->isNull() != rhs_ptr->isNull()) { - return *lhs_ptr < *rhs_ptr; - } + const QString lhs_fname(lhs.fileName()); + const QString rhs_fname(rhs.fileName()); + const QChar* lhs_ptr = lhs_fname.constData(); + const QChar* rhs_ptr = rhs_fname.constData(); + while (!lhs_ptr->isNull() && !rhs_ptr->isNull()) { + const bool lhs_is_digit = lhs_ptr->isDigit(); + const bool rhs_is_digit = rhs_ptr->isDigit(); + if (lhs_is_digit != rhs_is_digit) { + // Digits have priority over non-digits. + return lhs_is_digit; + } + if (lhs_is_digit && rhs_is_digit) { + unsigned long lhs_number = 0; + do { + lhs_number = lhs_number * 10 + lhs_ptr->digitValue(); ++lhs_ptr; + // Note: isDigit() implies !isNull() + } while (lhs_ptr->isDigit()); + + unsigned long rhs_number = 0; + do { + rhs_number = rhs_number * 10 + rhs_ptr->digitValue(); ++rhs_ptr; + // Note: isDigit() implies !isNull() + } while (rhs_ptr->isDigit()); + + if (lhs_number != rhs_number) { + return lhs_number < rhs_number; + } else { + continue; + } } - if (!lhs_ptr->isNull() || !rhs_ptr->isNull()) { - return lhs_ptr->isNull(); + if (lhs_ptr->isNull() != rhs_ptr->isNull()) { + return *lhs_ptr < *rhs_ptr; } - // OK, the smart comparison indicates the file names are equal. - // However, if they aren't symbol-to-symbol equal, we can't treat - // them as equal, so let's do a usual comparision now. - return lhs_fname < rhs_fname; + ++lhs_ptr; + ++rhs_ptr; + } + + if (!lhs_ptr->isNull() || !rhs_ptr->isNull()) { + return lhs_ptr->isNull(); + } + + // OK, the smart comparison indicates the file names are equal. + // However, if they aren't symbol-to-symbol equal, we can't treat + // them as equal, so let's do a usual comparision now. + return lhs_fname < rhs_fname; } // () diff --git a/SmartFilenameOrdering.h b/SmartFilenameOrdering.h index 5e45981ef..2c5fd302b 100644 --- a/SmartFilenameOrdering.h +++ b/SmartFilenameOrdering.h @@ -22,20 +22,20 @@ class QFileInfo; class SmartFilenameOrdering { -public: - SmartFilenameOrdering() = default; - - /** - * \brief Compare filenames using a set of heuristic rules. - * - * This function tries to mimic the way humans would order filenames. - * For example, "2.png" will go before "12.png". While doing so, - * it still provides the usual guarantees of a comparison predicate, - * such as two different file paths will never be ruled equivalent. - * - * \return true if \p lhs should go before \p rhs. - */ - bool operator()(const QFileInfo& lhs, const QFileInfo& rhs) const; + public: + SmartFilenameOrdering() = default; + + /** + * \brief Compare filenames using a set of heuristic rules. + * + * This function tries to mimic the way humans would order filenames. + * For example, "2.png" will go before "12.png". While doing so, + * it still provides the usual guarantees of a comparison predicate, + * such as two different file paths will never be ruled equivalent. + * + * \return true if \p lhs should go before \p rhs. + */ + bool operator()(const QFileInfo& lhs, const QFileInfo& rhs) const; }; diff --git a/StageListView.cpp b/StageListView.cpp index 81f77a10b..f4d8e3cfb 100644 --- a/StageListView.cpp +++ b/StageListView.cpp @@ -17,378 +17,372 @@ */ #include "StageListView.h" -#include "StageSequence.h" -#include "ChangedStateItemDelegate.h" -#include "SkinnedButton.h" -#include "BubbleAnimation.h" -#include "ColorSchemeManager.h" #include #include -#include #include +#include #include #include #include +#include "BubbleAnimation.h" +#include "ChangedStateItemDelegate.h" +#include "ColorSchemeManager.h" +#include "SkinnedButton.h" +#include "StageSequence.h" class StageListView::Model : public QAbstractTableModel { -public: - Model(QObject* parent, intrusive_ptr stages); + public: + Model(QObject* parent, intrusive_ptr stages); - void updateBatchProcessingAnimation(int selected_row, const QPixmap& animation_frame); + void updateBatchProcessingAnimation(int selected_row, const QPixmap& animation_frame); - void disableBatchProcessingAnimation(); + void disableBatchProcessingAnimation(); - int columnCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; - int rowCount(const QModelIndex& parent) const override; + int rowCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; -private: - intrusive_ptr m_ptrStages; - QPixmap m_curAnimationFrame; - int m_curSelectedRow; + private: + intrusive_ptr m_stages; + QPixmap m_curAnimationFrame; + int m_curSelectedRow; }; class StageListView::LeftColDelegate : public ChangedStateItemDelegate { -public: - explicit LeftColDelegate(StageListView* view); + public: + explicit LeftColDelegate(StageListView* view); - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; -private: - typedef ChangedStateItemDelegate SuperClass; + private: + typedef ChangedStateItemDelegate SuperClass; - StageListView* m_pView; + StageListView* m_view; }; class StageListView::RightColDelegate : public ChangedStateItemDelegate { -public: - explicit RightColDelegate(QObject* parent = nullptr); + public: + explicit RightColDelegate(QObject* parent = nullptr); - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; -private: - typedef ChangedStateItemDelegate SuperClass; + private: + typedef ChangedStateItemDelegate SuperClass; }; StageListView::StageListView(QWidget* parent) - : QTableView(parent), - m_sizeHint(QTableView::sizeHint()), - m_pModel(nullptr), - m_pFirstColDelegate(new LeftColDelegate(this)), - m_pSecondColDelegate(new RightColDelegate(this)), - m_curBatchAnimationFrame(0), - m_timerId(0), - m_batchProcessingPossible(false), - m_batchProcessingInProgress(false) { - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - // Prevent current item visualization. Not to be confused - // with selected items. - m_pFirstColDelegate->flagsForceDisabled(QStyle::State_HasFocus); - m_pSecondColDelegate->flagsForceDisabled(QStyle::State_HasFocus); - setItemDelegateForColumn(0, m_pFirstColDelegate); - setItemDelegateForColumn(1, m_pSecondColDelegate); - - QHeaderView* h_header = horizontalHeader(); - h_header->setSectionResizeMode(QHeaderView::Stretch); - h_header->hide(); - - QHeaderView* v_header = verticalHeader(); - v_header->setSectionResizeMode(QHeaderView::ResizeToContents); - v_header->setSectionsMovable(false); - - m_pLaunchBtn = new SkinnedButton(":/icons/play-small.png", ":/icons/play-small-hovered.png", - ":/icons/play-small-pressed.png", viewport()); - m_pLaunchBtn->setStatusTip(tr("Launch batch processing")); - m_pLaunchBtn->hide(); - - connect(m_pLaunchBtn, SIGNAL(clicked()), this, SIGNAL(launchBatchProcessing())); - - connect(verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(ensureSelectedRowVisible()), - Qt::QueuedConnection); + : QTableView(parent), + m_sizeHint(QTableView::sizeHint()), + m_model(nullptr), + m_firstColDelegate(new LeftColDelegate(this)), + m_secondColDelegate(new RightColDelegate(this)), + m_curBatchAnimationFrame(0), + m_timerId(0), + m_batchProcessingPossible(false), + m_batchProcessingInProgress(false) { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // Prevent current item visualization. Not to be confused + // with selected items. + m_firstColDelegate->flagsForceDisabled(QStyle::State_HasFocus); + m_secondColDelegate->flagsForceDisabled(QStyle::State_HasFocus); + setItemDelegateForColumn(0, m_firstColDelegate); + setItemDelegateForColumn(1, m_secondColDelegate); + + QHeaderView* h_header = horizontalHeader(); + h_header->setSectionResizeMode(QHeaderView::Stretch); + h_header->hide(); + + QHeaderView* v_header = verticalHeader(); + v_header->setSectionResizeMode(QHeaderView::ResizeToContents); + v_header->setSectionsMovable(false); + + m_launchBtn = new SkinnedButton(":/icons/play-small.png", ":/icons/play-small-hovered.png", + ":/icons/play-small-pressed.png", viewport()); + m_launchBtn->setStatusTip(tr("Launch batch processing")); + m_launchBtn->hide(); + + connect(m_launchBtn, SIGNAL(clicked()), this, SIGNAL(launchBatchProcessing())); + + connect(verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(ensureSelectedRowVisible()), + Qt::QueuedConnection); } StageListView::~StageListView() = default; void StageListView::setStages(const intrusive_ptr& stages) { - if (QAbstractItemModel* m = model()) { - // Q*View classes don't own their models. - m->deleteLater(); - } - - m_pModel = new Model(this, stages); - setModel(m_pModel); - - QHeaderView* h_header = horizontalHeader(); - QHeaderView* v_header = verticalHeader(); - h_header->setSectionResizeMode(0, QHeaderView::Stretch); - h_header->setSectionResizeMode(1, QHeaderView::Fixed); - if (v_header->count() != 0) { - // Make the cells in the last column square. - const int square_side = v_header->sectionSize(0); - h_header->resizeSection(1, square_side); - const int reduced_square_side = std::max(1, square_side - 6); - createBatchAnimationSequence(reduced_square_side); - } else { - // Just to avoid special cases elsewhere. - createBatchAnimationSequence(1); - } - m_curBatchAnimationFrame = 0; - - updateRowSpans(); - // Limit the vertical size to make it just enough to get - // rid of the scrollbars, but not more. - int height = verticalHeader()->length(); - height += this->height() - viewport()->height(); - m_sizeHint.setHeight(height); - QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Maximum); - sp.setVerticalStretch(1); - setSizePolicy(sp); - updateGeometry(); + if (QAbstractItemModel* m = model()) { + // Q*View classes don't own their models. + m->deleteLater(); + } + + m_model = new Model(this, stages); + setModel(m_model); + + QHeaderView* h_header = horizontalHeader(); + QHeaderView* v_header = verticalHeader(); + h_header->setSectionResizeMode(0, QHeaderView::Stretch); + h_header->setSectionResizeMode(1, QHeaderView::Fixed); + if (v_header->count() != 0) { + // Make the cells in the last column square. + const int square_side = v_header->sectionSize(0); + h_header->resizeSection(1, square_side); + const int reduced_square_side = std::max(1, square_side - 6); + createBatchAnimationSequence(reduced_square_side); + } else { + // Just to avoid special cases elsewhere. + createBatchAnimationSequence(1); + } + m_curBatchAnimationFrame = 0; + + updateRowSpans(); + // Limit the vertical size to make it just enough to get + // rid of the scrollbars, but not more. + int height = verticalHeader()->length(); + height += this->height() - viewport()->height(); + m_sizeHint.setHeight(height); + QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Maximum); + sp.setVerticalStretch(1); + setSizePolicy(sp); + updateGeometry(); } // StageListView::setStages void StageListView::setBatchProcessingPossible(const bool possible) { - if (m_batchProcessingPossible == possible) { - return; - } - m_batchProcessingPossible = possible; - - if (possible) { - placeLaunchButton(selectedRow()); - } else { - removeLaunchButton(selectedRow()); - } + if (m_batchProcessingPossible == possible) { + return; + } + m_batchProcessingPossible = possible; + + if (possible) { + placeLaunchButton(selectedRow()); + } else { + removeLaunchButton(selectedRow()); + } } void StageListView::setBatchProcessingInProgress(const bool in_progress) { - if (m_batchProcessingInProgress == in_progress) { - return; - } - m_batchProcessingInProgress = in_progress; - - if (in_progress) { - removeLaunchButton(selectedRow()); - updateRowSpans(); // Join columns. - // Some styles (Oxygen) visually separate items in a selected row. - // We really don't want that, so we pretend the items are not selected. - // Same goes for hovered items. - m_pFirstColDelegate->flagsForceDisabled(QStyle::State_Selected | QStyle::State_MouseOver); - m_pSecondColDelegate->flagsForceDisabled(QStyle::State_Selected | QStyle::State_MouseOver); - - initiateBatchAnimationFrameRendering(); - m_timerId = startTimer(180); - } else { - updateRowSpans(); // Separate columns. - placeLaunchButton(selectedRow()); - - m_pFirstColDelegate->removeChanges(QStyle::State_Selected | QStyle::State_MouseOver); - m_pSecondColDelegate->removeChanges(QStyle::State_Selected | QStyle::State_MouseOver); - - if (m_pModel) { - m_pModel->disableBatchProcessingAnimation(); - } - killTimer(m_timerId); - m_timerId = 0; + if (m_batchProcessingInProgress == in_progress) { + return; + } + m_batchProcessingInProgress = in_progress; + + if (in_progress) { + removeLaunchButton(selectedRow()); + updateRowSpans(); // Join columns. + // Some styles (Oxygen) visually separate items in a selected row. + // We really don't want that, so we pretend the items are not selected. + // Same goes for hovered items. + m_firstColDelegate->flagsForceDisabled(QStyle::State_Selected | QStyle::State_MouseOver); + m_secondColDelegate->flagsForceDisabled(QStyle::State_Selected | QStyle::State_MouseOver); + + initiateBatchAnimationFrameRendering(); + m_timerId = startTimer(180); + } else { + updateRowSpans(); // Separate columns. + placeLaunchButton(selectedRow()); + + m_firstColDelegate->removeChanges(QStyle::State_Selected | QStyle::State_MouseOver); + m_secondColDelegate->removeChanges(QStyle::State_Selected | QStyle::State_MouseOver); + + if (m_model) { + m_model->disableBatchProcessingAnimation(); } + killTimer(m_timerId); + m_timerId = 0; + } } // StageListView::setBatchProcessingInProgress void StageListView::timerEvent(QTimerEvent* event) { - if (event->timerId() != m_timerId) { - QTableView::timerEvent(event); + if (event->timerId() != m_timerId) { + QTableView::timerEvent(event); - return; - } + return; + } - initiateBatchAnimationFrameRendering(); + initiateBatchAnimationFrameRendering(); } void StageListView::initiateBatchAnimationFrameRendering() { - if (!m_pModel || !m_batchProcessingInProgress) { - return; - } + if (!m_model || !m_batchProcessingInProgress) { + return; + } - const int selected_row = selectedRow(); - if (selected_row == -1) { - return; - } + const int selected_row = selectedRow(); + if (selected_row == -1) { + return; + } - m_pModel->updateBatchProcessingAnimation(selected_row, m_batchAnimationPixmaps[m_curBatchAnimationFrame]); - if (++m_curBatchAnimationFrame == (int) m_batchAnimationPixmaps.size()) { - m_curBatchAnimationFrame = 0; - } + m_model->updateBatchProcessingAnimation(selected_row, m_batchAnimationPixmaps[m_curBatchAnimationFrame]); + if (++m_curBatchAnimationFrame == (int) m_batchAnimationPixmaps.size()) { + m_curBatchAnimationFrame = 0; + } } void StageListView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - // Call the base version. - QTableView::selectionChanged(selected, deselected); + // Call the base version. + QTableView::selectionChanged(selected, deselected); - if (!deselected.isEmpty()) { - removeLaunchButton(deselected.front().topLeft().row()); - } + if (!deselected.isEmpty()) { + removeLaunchButton(deselected.front().topLeft().row()); + } - if (!selected.isEmpty()) { - placeLaunchButton(selected.front().topLeft().row()); - } + if (!selected.isEmpty()) { + placeLaunchButton(selected.front().topLeft().row()); + } } void StageListView::ensureSelectedRowVisible() { - // This loop won't run more than one iteration. - for (const QModelIndex& idx : selectionModel()->selectedRows(0)) { - scrollTo(idx, EnsureVisible); - } + // This loop won't run more than one iteration. + for (const QModelIndex& idx : selectionModel()->selectedRows(0)) { + scrollTo(idx, EnsureVisible); + } } void StageListView::removeLaunchButton(const int row) { - if (row == -1) { - return; - } + if (row == -1) { + return; + } - m_pLaunchBtn->hide(); + m_launchBtn->hide(); } void StageListView::placeLaunchButton(int row) { - if (row == -1) { - return; - } + if (row == -1) { + return; + } - const QModelIndex idx(m_pModel->index(row, 0)); - QRect button_geometry(visualRect(idx)); + const QModelIndex idx(m_model->index(row, 0)); + QRect button_geometry(visualRect(idx)); - // Place it to the right (assuming height is less than width). - button_geometry.setLeft(button_geometry.right() + 1 - button_geometry.height()); + // Place it to the right (assuming height is less than width). + button_geometry.setLeft(button_geometry.right() + 1 - button_geometry.height()); - m_pLaunchBtn->setGeometry(button_geometry); - m_pLaunchBtn->show(); + m_launchBtn->setGeometry(button_geometry); + m_launchBtn->show(); } void StageListView::createBatchAnimationSequence(const int square_side) { - const int num_frames = 8; - m_batchAnimationPixmaps.resize(num_frames); - - const QColor head_color( - ColorSchemeManager::instance() - ->getColorParam("stage_list_head_color", palette().color(QPalette::Window).lighter(200)) - .color()); - const QColor tail_color( - ColorSchemeManager::instance() - ->getColorParam("stage_list_tail_color", palette().color(QPalette::Window).lighter(130)) - .color()); - - BubbleAnimation animation(num_frames); - for (int i = 0; i < num_frames; ++i) { - QPixmap& pixmap = m_batchAnimationPixmaps[i]; - if ((pixmap.width() != square_side) || (pixmap.height() != square_side)) { - pixmap = QPixmap(square_side, square_side); - } - pixmap.fill(Qt::transparent); - animation.nextFrame(head_color, tail_color, &pixmap); + const int num_frames = 8; + m_batchAnimationPixmaps.resize(num_frames); + + const QColor head_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::StageListHead, palette().color(QPalette::Window).darker(200)); + const QColor tail_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::StageListTail, palette().color(QPalette::Window).darker(130)); + + BubbleAnimation animation(num_frames); + for (int i = 0; i < num_frames; ++i) { + QPixmap& pixmap = m_batchAnimationPixmaps[i]; + if ((pixmap.width() != square_side) || (pixmap.height() != square_side)) { + pixmap = QPixmap(square_side, square_side); } + pixmap.fill(Qt::transparent); + animation.nextFrame(head_color, tail_color, &pixmap); + } } void StageListView::updateRowSpans() { - if (!m_pModel) { - return; - } - - const int count = m_pModel->rowCount(QModelIndex()); - for (int i = 0; i < count; ++i) { - setSpan(i, 0, 1, m_batchProcessingInProgress ? 1 : 2); - } + if (!m_model) { + return; + } + + const int count = m_model->rowCount(QModelIndex()); + for (int i = 0; i < count; ++i) { + setSpan(i, 0, 1, m_batchProcessingInProgress ? 1 : 2); + } } int StageListView::selectedRow() const { - const QModelIndexList selection(selectionModel()->selectedRows(0)); - if (selection.empty()) { - return -1; - } + const QModelIndexList selection(selectionModel()->selectedRows(0)); + if (selection.empty()) { + return -1; + } - return selection.front().row(); + return selection.front().row(); } /*========================= StageListView::Model ======================*/ StageListView::Model::Model(QObject* parent, intrusive_ptr stages) - : QAbstractTableModel(parent), m_ptrStages(std::move(stages)), m_curSelectedRow(0) { - assert(m_ptrStages); + : QAbstractTableModel(parent), m_stages(std::move(stages)), m_curSelectedRow(0) { + assert(m_stages); } void StageListView::Model::updateBatchProcessingAnimation(const int selected_row, const QPixmap& animation_frame) { - const int max_row = std::max(selected_row, m_curSelectedRow); - m_curSelectedRow = selected_row; - m_curAnimationFrame = animation_frame; - emit dataChanged(index(0, 1), index(max_row, 1)); + const int max_row = std::max(selected_row, m_curSelectedRow); + m_curSelectedRow = selected_row; + m_curAnimationFrame = animation_frame; + emit dataChanged(index(0, 1), index(max_row, 1)); } void StageListView::Model::disableBatchProcessingAnimation() { - m_curAnimationFrame = QPixmap(); - emit dataChanged(index(0, 1), index(m_curSelectedRow, 1)); + m_curAnimationFrame = QPixmap(); + emit dataChanged(index(0, 1), index(m_curSelectedRow, 1)); } int StageListView::Model::columnCount(const QModelIndex& parent) const { - return 2; + return 2; } int StageListView::Model::rowCount(const QModelIndex& parent) const { - return m_ptrStages->count(); + return m_stages->count(); } QVariant StageListView::Model::data(const QModelIndex& index, const int role) const { - if (role == Qt::DisplayRole) { - if (index.column() == 0) { - return m_ptrStages->filterAt(index.row())->getName(); - } + if (role == Qt::DisplayRole) { + if (index.column() == 0) { + return m_stages->filterAt(index.row())->getName(); } - if (role == Qt::UserRole) { - if (index.column() == 1) { - if (index.row() <= m_curSelectedRow) { - return m_curAnimationFrame; - } - } + } + if (role == Qt::UserRole) { + if (index.column() == 1) { + if (index.row() <= m_curSelectedRow) { + return m_curAnimationFrame; + } } + } - return QVariant(); + return QVariant(); } /*================= StageListView::LeftColDelegate ===================*/ -StageListView::LeftColDelegate::LeftColDelegate(StageListView* view) : SuperClass(view), m_pView(view) { -} +StageListView::LeftColDelegate::LeftColDelegate(StageListView* view) : SuperClass(view), m_view(view) {} void StageListView::LeftColDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - SuperClass::paint(painter, option, index); + SuperClass::paint(painter, option, index); - if ((index.row() == m_pView->selectedRow()) && m_pView->m_pLaunchBtn->isVisible()) { - QRect button_geometry(option.rect); - // Place it to the right (assuming height is less than width). - button_geometry.setLeft(button_geometry.right() + 1 - button_geometry.height()); - m_pView->m_pLaunchBtn->setGeometry(button_geometry); - } + if ((index.row() == m_view->selectedRow()) && m_view->m_launchBtn->isVisible()) { + QRect button_geometry(option.rect); + // Place it to the right (assuming height is less than width). + button_geometry.setLeft(button_geometry.right() + 1 - button_geometry.height()); + m_view->m_launchBtn->setGeometry(button_geometry); + } } /*================= StageListView::RightColDelegate ===================*/ -StageListView::RightColDelegate::RightColDelegate(QObject* parent) : SuperClass(parent) { -} +StageListView::RightColDelegate::RightColDelegate(QObject* parent) : SuperClass(parent) {} void StageListView::RightColDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - SuperClass::paint(painter, option, index); - - const QVariant var(index.data(Qt::UserRole)); - if (!var.isNull()) { - const QPixmap pixmap(var.value()); - if (!pixmap.isNull()) { - QRect r(pixmap.rect()); - r.moveCenter(option.rect.center()); - painter->drawPixmap(r, pixmap); - } + SuperClass::paint(painter, option, index); + + const QVariant var(index.data(Qt::UserRole)); + if (!var.isNull()) { + const QPixmap pixmap(var.value()); + if (!pixmap.isNull()) { + QRect r(pixmap.rect()); + r.moveCenter(option.rect.center()); + painter->drawPixmap(r, pixmap); } + } } diff --git a/StageListView.h b/StageListView.h index 762f64a35..5bf94dced 100644 --- a/StageListView.h +++ b/StageListView.h @@ -19,75 +19,73 @@ #ifndef STAGELISTVIEW_H_ #define STAGELISTVIEW_H_ -#include "intrusive_ptr.h" -#include #include +#include #include +#include "intrusive_ptr.h" class StageSequence; class StageListView : public QTableView { - Q_OBJECT -public: - explicit StageListView(QWidget* parent); + Q_OBJECT + public: + explicit StageListView(QWidget* parent); - ~StageListView() override; + ~StageListView() override; - void setStages(const intrusive_ptr& stages); + void setStages(const intrusive_ptr& stages); - QSize sizeHint() const override { - return m_sizeHint; - } + QSize sizeHint() const override { return m_sizeHint; } -signals: + signals: - void launchBatchProcessing(); + void launchBatchProcessing(); -public slots: + public slots: - void setBatchProcessingPossible(bool possible); + void setBatchProcessingPossible(bool possible); - void setBatchProcessingInProgress(bool in_progress); + void setBatchProcessingInProgress(bool in_progress); -protected slots: + protected slots: - void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override; + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override; -private slots: + private slots: - void ensureSelectedRowVisible(); + void ensureSelectedRowVisible(); -protected: - void timerEvent(QTimerEvent* event) override; + protected: + void timerEvent(QTimerEvent* event) override; -private: - class Model; - class LeftColDelegate; + private: + class Model; + class LeftColDelegate; - class RightColDelegate; + class RightColDelegate; - void removeLaunchButton(int row); + void removeLaunchButton(int row); - void placeLaunchButton(int row); + void placeLaunchButton(int row); - void initiateBatchAnimationFrameRendering(); + void initiateBatchAnimationFrameRendering(); - void createBatchAnimationSequence(int square_side); + void createBatchAnimationSequence(int square_side); - void updateRowSpans(); + void updateRowSpans(); - int selectedRow() const; + int selectedRow() const; - QSize m_sizeHint; - Model* m_pModel; - LeftColDelegate* m_pFirstColDelegate; - RightColDelegate* m_pSecondColDelegate; - QWidget* m_pLaunchBtn; - std::vector m_batchAnimationPixmaps; - int m_curBatchAnimationFrame; - int m_timerId; - bool m_batchProcessingPossible; - bool m_batchProcessingInProgress; + QSize m_sizeHint; + Model* m_model; + LeftColDelegate* m_firstColDelegate; + RightColDelegate* m_secondColDelegate; + QWidget* m_launchBtn; + std::vector m_batchAnimationPixmaps; + int m_curBatchAnimationFrame; + int m_timerId; + bool m_batchProcessingPossible; + bool m_batchProcessingInProgress; }; diff --git a/StageSequence.cpp b/StageSequence.cpp index 37e945dce..40caf6efc 100644 --- a/StageSequence.cpp +++ b/StageSequence.cpp @@ -21,45 +21,45 @@ StageSequence::StageSequence(const intrusive_ptr& pages, const PageSelectionAccessor& page_selection_accessor) - : m_ptrFixOrientationFilter(new fix_orientation::Filter(page_selection_accessor)), - m_ptrPageSplitFilter(new page_split::Filter(pages, page_selection_accessor)), - m_ptrDeskewFilter(new deskew::Filter(page_selection_accessor)), - m_ptrSelectContentFilter(new select_content::Filter(page_selection_accessor)), - m_ptrPageLayoutFilter(new page_layout::Filter(pages, page_selection_accessor)), - m_ptrOutputFilter(new output::Filter(page_selection_accessor)) { - m_fixOrientationFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrFixOrientationFilter); + : m_fixOrientationFilter(new fix_orientation::Filter(page_selection_accessor)), + m_pageSplitFilter(new page_split::Filter(pages, page_selection_accessor)), + m_deskewFilter(new deskew::Filter(page_selection_accessor)), + m_selectContentFilter(new select_content::Filter(page_selection_accessor)), + m_pageLayoutFilter(new page_layout::Filter(pages, page_selection_accessor)), + m_outputFilter(new output::Filter(page_selection_accessor)) { + m_fixOrientationFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_fixOrientationFilter); - m_pageSplitFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrPageSplitFilter); + m_pageSplitFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_pageSplitFilter); - m_deskewFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrDeskewFilter); + m_deskewFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_deskewFilter); - m_selectContentFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrSelectContentFilter); + m_selectContentFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_selectContentFilter); - m_pageLayoutFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrPageLayoutFilter); + m_pageLayoutFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_pageLayoutFilter); - m_outputFilterIdx = static_cast(m_filters.size()); - m_filters.emplace_back(m_ptrOutputFilter); + m_outputFilterIdx = static_cast(m_filters.size()); + m_filters.emplace_back(m_outputFilter); } void StageSequence::performRelinking(const AbstractRelinker& relinker) { - for (FilterPtr& filter : m_filters) { - filter->performRelinking(relinker); - } + for (FilterPtr& filter : m_filters) { + filter->performRelinking(relinker); + } } int StageSequence::findFilter(const FilterPtr& filter) const { - int idx = 0; - for (const FilterPtr& f : m_filters) { - if (f == filter) { - return idx; - } - ++idx; + int idx = 0; + for (const FilterPtr& f : m_filters) { + if (f == filter) { + return idx; } + ++idx; + } - return -1; + return -1; } diff --git a/StageSequence.h b/StageSequence.h index 3679a8ccc..bdb5a5f01 100644 --- a/StageSequence.h +++ b/StageSequence.h @@ -19,17 +19,17 @@ #ifndef STAGESEQUENCE_H_ #define STAGESEQUENCE_H_ -#include "NonCopyable.h" -#include "ref_countable.h" -#include "intrusive_ptr.h" +#include #include "AbstractFilter.h" +#include "NonCopyable.h" +#include "filters/deskew/Filter.h" #include "filters/fix_orientation/Filter.h" +#include "filters/output/Filter.h" +#include "filters/page_layout/Filter.h" #include "filters/page_split/Filter.h" -#include "filters/deskew/Filter.h" #include "filters/select_content/Filter.h" -#include "filters/page_layout/Filter.h" -#include "filters/output/Filter.h" -#include +#include "intrusive_ptr.h" +#include "ref_countable.h" class PageId; class ProjectPages; @@ -37,91 +37,61 @@ class PageSelectionAccessor; class AbstractRelinker; class StageSequence : public ref_countable { - DECLARE_NON_COPYABLE(StageSequence) - -public: - typedef intrusive_ptr FilterPtr; - - StageSequence(const intrusive_ptr& pages, const PageSelectionAccessor& page_selection_accessor); - - void performRelinking(const AbstractRelinker& relinker); - - const std::vector& filters() const { - return m_filters; - } - - int count() const { - return static_cast(m_filters.size()); - } - - const FilterPtr& filterAt(int idx) const { - return m_filters[idx]; - } - - int findFilter(const FilterPtr& filter) const; - - const intrusive_ptr& fixOrientationFilter() const { - return m_ptrFixOrientationFilter; - } - - const intrusive_ptr& pageSplitFilter() const { - return m_ptrPageSplitFilter; - } - - const intrusive_ptr& deskewFilter() const { - return m_ptrDeskewFilter; - } - - const intrusive_ptr& selectContentFilter() const { - return m_ptrSelectContentFilter; - } - - const intrusive_ptr& pageLayoutFilter() const { - return m_ptrPageLayoutFilter; - } - - const intrusive_ptr& outputFilter() const { - return m_ptrOutputFilter; - } - - int fixOrientationFilterIdx() const { - return m_fixOrientationFilterIdx; - } - - int pageSplitFilterIdx() const { - return m_pageSplitFilterIdx; - } - - int deskewFilterIdx() const { - return m_deskewFilterIdx; - } - - int selectContentFilterIdx() const { - return m_selectContentFilterIdx; - } - - int pageLayoutFilterIdx() const { - return m_pageLayoutFilterIdx; - } - - int outputFilterIdx() const { - return m_outputFilterIdx; - } - -private: - intrusive_ptr m_ptrFixOrientationFilter; - intrusive_ptr m_ptrPageSplitFilter; - intrusive_ptr m_ptrDeskewFilter; - intrusive_ptr m_ptrSelectContentFilter; - intrusive_ptr m_ptrPageLayoutFilter; - intrusive_ptr m_ptrOutputFilter; - std::vector m_filters; - int m_fixOrientationFilterIdx; - int m_pageSplitFilterIdx; - int m_deskewFilterIdx; - int m_selectContentFilterIdx; - int m_pageLayoutFilterIdx; - int m_outputFilterIdx; + DECLARE_NON_COPYABLE(StageSequence) + + public: + typedef intrusive_ptr FilterPtr; + + StageSequence(const intrusive_ptr& pages, const PageSelectionAccessor& page_selection_accessor); + + void performRelinking(const AbstractRelinker& relinker); + + const std::vector& filters() const { return m_filters; } + + int count() const { return static_cast(m_filters.size()); } + + const FilterPtr& filterAt(int idx) const { return m_filters[idx]; } + + int findFilter(const FilterPtr& filter) const; + + const intrusive_ptr& fixOrientationFilter() const { return m_fixOrientationFilter; } + + const intrusive_ptr& pageSplitFilter() const { return m_pageSplitFilter; } + + const intrusive_ptr& deskewFilter() const { return m_deskewFilter; } + + const intrusive_ptr& selectContentFilter() const { return m_selectContentFilter; } + + const intrusive_ptr& pageLayoutFilter() const { return m_pageLayoutFilter; } + + const intrusive_ptr& outputFilter() const { return m_outputFilter; } + + int fixOrientationFilterIdx() const { return m_fixOrientationFilterIdx; } + + int pageSplitFilterIdx() const { return m_pageSplitFilterIdx; } + + int deskewFilterIdx() const { return m_deskewFilterIdx; } + + int selectContentFilterIdx() const { return m_selectContentFilterIdx; } + + int pageLayoutFilterIdx() const { return m_pageLayoutFilterIdx; } + + int outputFilterIdx() const { return m_outputFilterIdx; } + + private: + intrusive_ptr m_fixOrientationFilter; + intrusive_ptr m_pageSplitFilter; + intrusive_ptr m_deskewFilter; + intrusive_ptr m_selectContentFilter; + intrusive_ptr m_pageLayoutFilter; + intrusive_ptr m_outputFilter; + std::vector m_filters; + int m_fixOrientationFilterIdx; + int m_pageSplitFilterIdx; + int m_deskewFilterIdx; + int m_selectContentFilterIdx; + int m_pageLayoutFilterIdx; + int m_outputFilterIdx; }; diff --git a/StatusBarPanel.cpp b/StatusBarPanel.cpp index def6b152d..5d4dbce08 100644 --- a/StatusBarPanel.cpp +++ b/StatusBarPanel.cpp @@ -1,140 +1,140 @@ -#include -#include #include "StatusBarPanel.h" +#include +#include #include "ImageViewInfoProvider.h" -#include "UnitsProvider.h" #include "PageId.h" +#include "UnitsProvider.h" StatusBarPanel::StatusBarPanel() { - ui.setupUi(this); + ui.setupUi(this); } void StatusBarPanel::updateMousePos(const QPointF& mousePos) { - const QMutexLocker locker(&mutex); + const QMutexLocker locker(&m_mutex); - StatusBarPanel::mousePos = mousePos; - mousePosChanged(); + StatusBarPanel::m_mousePos = mousePos; + mousePosChanged(); } void StatusBarPanel::updatePhysSize(const QSizeF& physSize) { - const QMutexLocker locker(&mutex); + const QMutexLocker locker(&m_mutex); - StatusBarPanel::physSize = physSize; - physSizeChanged(); + StatusBarPanel::m_physSize = physSize; + physSizeChanged(); } void StatusBarPanel::updateDpi(const Dpi& dpi) { - StatusBarPanel::dpi = dpi; + StatusBarPanel::m_dpi = dpi; } void StatusBarPanel::clearImageViewInfo() { - infoProvider = nullptr; - updateMousePos(QPointF()); - updatePhysSize(QRectF().size()); - dpi = Dpi(); + m_infoProvider = nullptr; + updateMousePos(QPointF()); + updatePhysSize(QRectF().size()); + m_dpi = Dpi(); } void StatusBarPanel::updatePage(int pageNumber, size_t pageCount, const PageId& pageId) { - ui.pageNoLabel->setText(tr("p. %1 / %2").arg(pageNumber).arg(pageCount)); - - QString pageFileInfo = QFileInfo(pageId.imageId().filePath()).baseName(); - if (pageFileInfo.size() > 15) { - pageFileInfo = "..." + pageFileInfo.right(13); - } - if (pageId.subPage() != PageId::SINGLE_PAGE) { - pageFileInfo = pageFileInfo.right(11) + ((pageId.subPage() == PageId::LEFT_PAGE) ? tr(" [L]") : tr(" [R]")); - } - - ui.pageInfoLine->setVisible(true); - ui.pageInfo->setText(pageFileInfo); + ui.pageNoLabel->setText(tr("p. %1 / %2").arg(pageNumber).arg(pageCount)); + + QString pageFileInfo = QFileInfo(pageId.imageId().filePath()).baseName(); + if (pageFileInfo.size() > 15) { + pageFileInfo = "..." + pageFileInfo.right(13); + } + if (pageId.subPage() != PageId::SINGLE_PAGE) { + pageFileInfo = pageFileInfo.right(11) + ((pageId.subPage() == PageId::LEFT_PAGE) ? tr(" [L]") : tr(" [R]")); + } + + ui.pageInfoLine->setVisible(true); + ui.pageInfo->setText(pageFileInfo); } void StatusBarPanel::clear() { - ui.mousePosLabel->clear(); - ui.physSizeLabel->clear(); - ui.pageNoLabel->clear(); - ui.pageInfo->clear(); - - ui.mousePosLine->setVisible(false); - ui.physSizeLine->setVisible(false); - ui.pageInfoLine->setVisible(false); + ui.mousePosLabel->clear(); + ui.physSizeLabel->clear(); + ui.pageNoLabel->clear(); + ui.pageInfo->clear(); + + ui.mousePosLine->setVisible(false); + ui.physSizeLine->setVisible(false); + ui.pageInfoLine->setVisible(false); } void StatusBarPanel::updateUnits(Units) { - const QMutexLocker locker(&mutex); + const QMutexLocker locker(&m_mutex); - mousePosChanged(); - physSizeChanged(); + mousePosChanged(); + physSizeChanged(); } void StatusBarPanel::mousePosChanged() { - if (!mousePos.isNull() && !dpi.isNull()) { - double x = mousePos.x(); - double y = mousePos.y(); - UnitsProvider::getInstance()->convertFrom(x, y, PIXELS, dpi); - - switch (UnitsProvider::getInstance()->getUnits()) { - case PIXELS: - case MILLIMETRES: - x = std::ceil(x); - y = std::ceil(y); - break; - default: - x = std::ceil(x * 10) / 10; - y = std::ceil(y * 10) / 10; - break; - } - - ui.mousePosLine->setVisible(true); - ui.mousePosLabel->setText(QString("%1, %2").arg(x).arg(y)); - } else { - ui.mousePosLabel->clear(); - ui.mousePosLine->setVisible(false); + if (!m_mousePos.isNull() && !m_dpi.isNull()) { + double x = m_mousePos.x(); + double y = m_mousePos.y(); + UnitsProvider::getInstance()->convertFrom(x, y, PIXELS, m_dpi); + + switch (UnitsProvider::getInstance()->getUnits()) { + case PIXELS: + case MILLIMETRES: + x = std::ceil(x); + y = std::ceil(y); + break; + default: + x = std::ceil(x * 10) / 10; + y = std::ceil(y * 10) / 10; + break; } + + ui.mousePosLine->setVisible(true); + ui.mousePosLabel->setText(QString("%1, %2").arg(x).arg(y)); + } else { + ui.mousePosLabel->clear(); + ui.mousePosLine->setVisible(false); + } } void StatusBarPanel::physSizeChanged() { - if (!physSize.isNull() && !dpi.isNull()) { - double width = physSize.width(); - double height = physSize.height(); - UnitsProvider::getInstance()->convertFrom(width, height, PIXELS, dpi); - - const Units units = UnitsProvider::getInstance()->getUnits(); - switch (units) { - case PIXELS: - width = std::round(width); - height = std::round(height); - break; - case MILLIMETRES: - width = std::round(width); - height = std::round(height); - break; - case CENTIMETRES: - width = std::round(width * 10) / 10; - height = std::round(height * 10) / 10; - break; - case INCHES: - width = std::round(width * 10) / 10; - height = std::round(height * 10) / 10; - break; - } - - ui.physSizeLine->setVisible(true); - ui.physSizeLabel->setText(QString("%1 x %2 %3").arg(width).arg(height).arg(unitsToLocalizedString(units))); - } else { - ui.physSizeLabel->clear(); - ui.physSizeLine->setVisible(false); + if (!m_physSize.isNull() && !m_dpi.isNull()) { + double width = m_physSize.width(); + double height = m_physSize.height(); + UnitsProvider::getInstance()->convertFrom(width, height, PIXELS, m_dpi); + + const Units units = UnitsProvider::getInstance()->getUnits(); + switch (units) { + case PIXELS: + width = std::round(width); + height = std::round(height); + break; + case MILLIMETRES: + width = std::round(width); + height = std::round(height); + break; + case CENTIMETRES: + width = std::round(width * 10) / 10; + height = std::round(height * 10) / 10; + break; + case INCHES: + width = std::round(width * 10) / 10; + height = std::round(height * 10) / 10; + break; } + + ui.physSizeLine->setVisible(true); + ui.physSizeLabel->setText(QString("%1 x %2 %3").arg(width).arg(height).arg(unitsToLocalizedString(units))); + } else { + ui.physSizeLabel->clear(); + ui.physSizeLine->setVisible(false); + } } void StatusBarPanel::setInfoProvider(ImageViewInfoProvider* infoProvider) { - if (this->infoProvider) { - infoProvider->detachObserver(this); - } - if (infoProvider) { - infoProvider->attachObserver(this); - } - - this->infoProvider = infoProvider; + if (m_infoProvider) { + infoProvider->detachObserver(this); + } + if (infoProvider) { + infoProvider->attachObserver(this); + } + + m_infoProvider = infoProvider; } diff --git a/StatusBarPanel.h b/StatusBarPanel.h index 03c3b2aa0..639d4ad0c 100644 --- a/StatusBarPanel.h +++ b/StatusBarPanel.h @@ -2,52 +2,51 @@ #ifndef SCANTAILOR_STATUSBARPANEL_H #define SCANTAILOR_STATUSBARPANEL_H -#include #include -#include "ui_StatusBarPanel.h" -#include "UnitsObserver.h" -#include "ImageViewInfoObserver.h" +#include #include "Dpi.h" +#include "ImageViewInfoObserver.h" #include "ImageViewInfoProvider.h" +#include "UnitsObserver.h" +#include "ui_StatusBarPanel.h" class PageId; class StatusBarPanel : public QWidget, public UnitsObserver, public ImageViewInfoObserver { - Q_OBJECT -private: - mutable QMutex mutex; - Ui::StatusBarPanel ui; - QPointF mousePos; - QSizeF physSize; - Dpi dpi; - ImageViewInfoProvider* infoProvider; + Q_OBJECT + public: + StatusBarPanel(); -public: - StatusBarPanel(); + ~StatusBarPanel() override = default; - ~StatusBarPanel() override = default; + public: + void updateMousePos(const QPointF& mousePos) override; -public: - void updateMousePos(const QPointF& mousePos) override; + void updatePhysSize(const QSizeF& physSize) override; - void updatePhysSize(const QSizeF& physSize) override; + void updateDpi(const Dpi& dpi) override; - void updateDpi(const Dpi& dpi) override; + void clearImageViewInfo() override; - void clearImageViewInfo() override; + void updatePage(int pageNumber, size_t pageCount, const PageId& pageId); - void updatePage(int pageNumber, size_t pageCount, const PageId& pageId); + void clear(); - void clear(); + void updateUnits(Units) override; - void updateUnits(Units) override; + void setInfoProvider(ImageViewInfoProvider* infoProvider) override; - void setInfoProvider(ImageViewInfoProvider* infoProvider) override; + private: + void mousePosChanged(); -private: - void mousePosChanged(); + void physSizeChanged(); - void physSizeChanged(); + Ui::StatusBarPanel ui; + mutable QMutex m_mutex; + QPointF m_mousePos; + QSizeF m_physSize; + Dpi m_dpi; + ImageViewInfoProvider* m_infoProvider; }; diff --git a/SystemLoadWidget.cpp b/SystemLoadWidget.cpp index fd2015ab0..264428cc7 100644 --- a/SystemLoadWidget.cpp +++ b/SystemLoadWidget.cpp @@ -17,71 +17,71 @@ */ #include "SystemLoadWidget.h" -#include -#include #include +#include +#include static const char* const key = "settings/batch_processing_threads"; SystemLoadWidget::SystemLoadWidget(QWidget* parent) : QWidget(parent), m_maxThreads(QThread::idealThreadCount()) { - ui.setupUi(this); + ui.setupUi(this); - if (sizeof(void*) <= 4) { - // Restricting num of processors for 32-bit due to - // address space constraints. - if (m_maxThreads > 2) { - m_maxThreads = 2; - } + if (sizeof(void*) <= 4) { + // Restricting num of processors for 32-bit due to + // address space constraints. + if (m_maxThreads > 2) { + m_maxThreads = 2; } - int num_threads = std::min(m_maxThreads, QSettings().value(key, m_maxThreads).toInt()); + } + int num_threads = std::min(m_maxThreads, QSettings().value(key, m_maxThreads).toInt()); - ui.slider->setRange(1, m_maxThreads); - ui.slider->setValue(num_threads); + ui.slider->setRange(1, m_maxThreads); + ui.slider->setValue(num_threads); - connect(ui.slider, SIGNAL(sliderPressed()), SLOT(sliderPressed())); - connect(ui.slider, SIGNAL(sliderMoved(int)), SLOT(sliderMoved(int))); - connect(ui.slider, SIGNAL(valueChanged(int)), SLOT(valueChanged(int))); - connect(ui.minusBtn, SIGNAL(clicked()), SLOT(decreaseLoad())); - connect(ui.plusBtn, SIGNAL(clicked()), SLOT(increaseLoad())); + connect(ui.slider, SIGNAL(sliderPressed()), SLOT(sliderPressed())); + connect(ui.slider, SIGNAL(sliderMoved(int)), SLOT(sliderMoved(int))); + connect(ui.slider, SIGNAL(valueChanged(int)), SLOT(valueChanged(int))); + connect(ui.minusBtn, SIGNAL(clicked()), SLOT(decreaseLoad())); + connect(ui.plusBtn, SIGNAL(clicked()), SLOT(increaseLoad())); } void SystemLoadWidget::sliderPressed() { - showHideToolTip(ui.slider->value()); + showHideToolTip(ui.slider->value()); } void SystemLoadWidget::sliderMoved(int threads) { - showHideToolTip(threads); + showHideToolTip(threads); } void SystemLoadWidget::valueChanged(int threads) { - QSettings settings; - if (threads == m_maxThreads) { - settings.remove(key); - } else { - settings.setValue(key, threads); - } + QSettings settings; + if (threads == m_maxThreads) { + settings.remove(key); + } else { + settings.setValue(key, threads); + } } void SystemLoadWidget::decreaseLoad() { - ui.slider->setValue(ui.slider->value() - 1); - showHideToolTip(ui.slider->value()); + ui.slider->setValue(ui.slider->value() - 1); + showHideToolTip(ui.slider->value()); } void SystemLoadWidget::increaseLoad() { - ui.slider->setValue(ui.slider->value() + 1); - showHideToolTip(ui.slider->value()); + ui.slider->setValue(ui.slider->value() + 1); + showHideToolTip(ui.slider->value()); } void SystemLoadWidget::showHideToolTip(int threads) { - // Show the tooltip immediately. - const QPoint center(ui.slider->rect().center()); - QPoint tooltip_pos(ui.slider->mapFromGlobal(QCursor::pos())); - if ((tooltip_pos.x() < 0) || (tooltip_pos.x() >= ui.slider->width())) { - tooltip_pos.setX(center.x()); - } - if ((tooltip_pos.y() < 0) || (tooltip_pos.y() >= ui.slider->height())) { - tooltip_pos.setY(center.y()); - } - tooltip_pos = ui.slider->mapToGlobal(tooltip_pos); - QToolTip::showText(tooltip_pos, QString("%1/%2").arg(threads).arg(m_maxThreads), ui.slider); + // Show the tooltip immediately. + const QPoint center(ui.slider->rect().center()); + QPoint tooltip_pos(ui.slider->mapFromGlobal(QCursor::pos())); + if ((tooltip_pos.x() < 0) || (tooltip_pos.x() >= ui.slider->width())) { + tooltip_pos.setX(center.x()); + } + if ((tooltip_pos.y() < 0) || (tooltip_pos.y() >= ui.slider->height())) { + tooltip_pos.setY(center.y()); + } + tooltip_pos = ui.slider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, QString("%1/%2").arg(threads).arg(m_maxThreads), ui.slider); } diff --git a/SystemLoadWidget.h b/SystemLoadWidget.h index f188f8f60..868efa16a 100644 --- a/SystemLoadWidget.h +++ b/SystemLoadWidget.h @@ -19,31 +19,31 @@ #ifndef SYSTEM_LOAD_WIDGET_H_ #define SYSTEM_LOAD_WIDGET_H_ -#include "ui_SystemLoadWidget.h" #include +#include "ui_SystemLoadWidget.h" class SystemLoadWidget : public QWidget { - Q_OBJECT -public: - explicit SystemLoadWidget(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit SystemLoadWidget(QWidget* parent = nullptr); -private slots: + private slots: - void sliderPressed(); + void sliderPressed(); - void sliderMoved(int threads); + void sliderMoved(int threads); - void valueChanged(int threads); + void valueChanged(int threads); - void decreaseLoad(); + void decreaseLoad(); - void increaseLoad(); + void increaseLoad(); -private: - void showHideToolTip(int threads); + private: + void showHideToolTip(int threads); - Ui::SystemLoadWidget ui; - int m_maxThreads; + Ui::SystemLoadWidget ui; + int m_maxThreads; }; diff --git a/TabbedDebugImages.cpp b/TabbedDebugImages.cpp index dba89d919..0b486b593 100644 --- a/TabbedDebugImages.cpp +++ b/TabbedDebugImages.cpp @@ -16,27 +16,27 @@ along with this program. If not, see . */ -#include "DebugImageView.h" #include "TabbedDebugImages.h" +#include "DebugImageView.h" TabbedDebugImages::TabbedDebugImages(QWidget* parent) : QTabWidget(parent) { - setDocumentMode(true); - connect(this, SIGNAL(currentChanged(int)), SLOT(currentTabChanged(int))); + setDocumentMode(true); + connect(this, SIGNAL(currentChanged(int)), SLOT(currentTabChanged(int))); } void TabbedDebugImages::currentTabChanged(const int idx) { - if (auto* div = dynamic_cast(widget(idx))) { - div->unlink(); - m_liveViews.push_back(*div); - removeExcessLiveViews(); - div->setLive(true); - } + if (auto* div = dynamic_cast(widget(idx))) { + div->unlink(); + m_liveViews.push_back(*div); + removeExcessLiveViews(); + div->setLive(true); + } } void TabbedDebugImages::removeExcessLiveViews() { - auto remaining = static_cast(m_liveViews.size()); - for (; remaining > MAX_LIVE_VIEWS; --remaining) { - m_liveViews.front().setLive(false); - m_liveViews.erase(m_liveViews.begin()); - } + auto remaining = static_cast(m_liveViews.size()); + for (; remaining > MAX_LIVE_VIEWS; --remaining) { + m_liveViews.front().setLive(false); + m_liveViews.erase(m_liveViews.begin()); + } } diff --git a/TabbedDebugImages.h b/TabbedDebugImages.h index fa80b56c8..5596f4279 100644 --- a/TabbedDebugImages.h +++ b/TabbedDebugImages.h @@ -19,32 +19,32 @@ #ifndef TABBED_DEBUG_IMAGES_H_ #define TABBED_DEBUG_IMAGES_H_ -#include "DebugImageView.h" #include #include +#include "DebugImageView.h" class TabbedDebugImages : public QTabWidget { - Q_OBJECT -public: - explicit TabbedDebugImages(QWidget* parent = nullptr); + Q_OBJECT + public: + explicit TabbedDebugImages(QWidget* parent = nullptr); -private slots: + private slots: - void currentTabChanged(int idx); + void currentTabChanged(int idx); -private: - typedef boost::intrusive::list> DebugViewList; + private: + typedef boost::intrusive::list> DebugViewList; - enum { MAX_LIVE_VIEWS = 3 }; + enum { MAX_LIVE_VIEWS = 3 }; - void removeExcessLiveViews(); + void removeExcessLiveViews(); - /** - * We don't want to keep all the debug images in memory. We normally keep - * only a few of them. This list holds references to them in the order - * they become live. - */ - DebugViewList m_liveViews; + /** + * We don't want to keep all the debug images in memory. We normally keep + * only a few of them. This list holds references to them in the order + * they become live. + */ + DebugViewList m_liveViews; }; diff --git a/TaskStatus.h b/TaskStatus.h index 733b35f7b..a29c64b05 100644 --- a/TaskStatus.h +++ b/TaskStatus.h @@ -20,14 +20,14 @@ #define TASKSTATUS_H_ class TaskStatus { -public: - virtual ~TaskStatus() = default; + public: + virtual ~TaskStatus() = default; - virtual void cancel() = 0; + virtual void cancel() = 0; - virtual bool isCancelled() const = 0; + virtual bool isCancelled() const = 0; - virtual void throwIfCancelled() const = 0; + virtual void throwIfCancelled() const = 0; }; diff --git a/ThreadPriority.cpp b/ThreadPriority.cpp index 59123d998..ff7224729 100644 --- a/ThreadPriority.cpp +++ b/ThreadPriority.cpp @@ -21,81 +21,81 @@ #include QThread::Priority ThreadPriority::toQThreadPriority() const { - switch (m_prio) { - case Normal: - return QThread::NormalPriority; - case Low: - return QThread::LowPriority; - case Lowest: - return QThread::LowestPriority; - case Idle: - return QThread::IdlePriority; - } - - assert(!"Unreachable"); - - return QThread::NormalPriority; + switch (m_prio) { + case Normal: + return QThread::NormalPriority; + case Low: + return QThread::LowPriority; + case Lowest: + return QThread::LowestPriority; + case Idle: + return QThread::IdlePriority; + } + + assert(!"Unreachable"); + + return QThread::NormalPriority; } int ThreadPriority::toPosixNiceLevel() const { - switch (m_prio) { - case Normal: - return 0; - case Low: - return 6; - case Lowest: - return 12; - case Idle: - return 19; - } - - assert(!"Unreachable"); - - return 0; + switch (m_prio) { + case Normal: + return 0; + case Low: + return 6; + case Lowest: + return 12; + case Idle: + return 19; + } + + assert(!"Unreachable"); + + return 0; } ThreadPriority ThreadPriority::load(const QSettings& settings, const QString& key, Priority dflt) { - const QString str(settings.value(key).toString()); - if (str == "normal") { - return Normal; - } else if (str == "low") { - return Low; - } else if (str == "lowest") { - return Lowest; - } else if (str == "idle") { - return Idle; - } else { - return dflt; - } + const QString str(settings.value(key).toString()); + if (str == "normal") { + return Normal; + } else if (str == "low") { + return Low; + } else if (str == "lowest") { + return Lowest; + } else if (str == "idle") { + return Idle; + } else { + return dflt; + } } ThreadPriority ThreadPriority::load(const QString& key, Priority dflt) { - QSettings settings; + QSettings settings; - return load(settings, key, dflt); + return load(settings, key, dflt); } void ThreadPriority::save(QSettings& settings, const QString& key) { - const char* str = ""; - switch (m_prio) { - case Normal: - str = "normal"; - break; - case Low: - str = "low"; - break; - case Lowest: - str = "lowest"; - break; - case Idle: - str = "idle"; - break; - } - - settings.setValue(key, QString::fromLatin1(str)); + const char* str = ""; + switch (m_prio) { + case Normal: + str = "normal"; + break; + case Low: + str = "low"; + break; + case Lowest: + str = "lowest"; + break; + case Idle: + str = "idle"; + break; + } + + settings.setValue(key, QString::fromLatin1(str)); } void ThreadPriority::save(const QString& key) { - QSettings settings; - save(settings, key); + QSettings settings; + save(settings, key); } diff --git a/ThreadPriority.h b/ThreadPriority.h index 22f258ed7..94d294ff7 100644 --- a/ThreadPriority.h +++ b/ThreadPriority.h @@ -19,41 +19,36 @@ #ifndef THREAD_PRIORITY_H_ #define THREAD_PRIORITY_H_ -#include #include +#include class QSettings; class ThreadPriority { - // Member-wise copying is OK. -public: - enum Priority { Minimum, Idle = Minimum, Lowest, Low, Normal, Maximum = Normal }; + // Member-wise copying is OK. + public: + enum Priority { Minimum, Idle = Minimum, Lowest, Low, Normal, Maximum = Normal }; - ThreadPriority(Priority prio) : m_prio(prio) { - } + ThreadPriority(Priority prio) : m_prio(prio) {} - void setValue(Priority prio) { - m_prio = prio; - } + void setValue(Priority prio) { m_prio = prio; } - Priority value() const { - return m_prio; - } + Priority value() const { return m_prio; } - QThread::Priority toQThreadPriority() const; + QThread::Priority toQThreadPriority() const; - int toPosixNiceLevel() const; + int toPosixNiceLevel() const; - static ThreadPriority load(const QSettings& settings, const QString& key, Priority dflt = Normal); + static ThreadPriority load(const QSettings& settings, const QString& key, Priority dflt = Normal); - static ThreadPriority load(const QString& key, Priority dflt = Normal); + static ThreadPriority load(const QString& key, Priority dflt = Normal); - void save(QSettings& settings, const QString& key); + void save(QSettings& settings, const QString& key); - void save(const QString& key); + void save(const QString& key); -private: - Priority m_prio; + private: + Priority m_prio; }; diff --git a/ThumbnailBase.cpp b/ThumbnailBase.cpp index ad5e81159..cf8eda197 100644 --- a/ThumbnailBase.cpp +++ b/ThumbnailBase.cpp @@ -17,31 +17,28 @@ */ #include "ThumbnailBase.h" -#include "PixmapRenderer.h" -#include "imageproc/PolygonUtils.h" -#include +#include #include +#include #include -#include -#include #include +#include #include +#include "PixmapRenderer.h" +#include "imageproc/PolygonUtils.h" using namespace imageproc; class ThumbnailBase::LoadCompletionHandler : public AbstractCommand { - DECLARE_NON_COPYABLE(LoadCompletionHandler) + DECLARE_NON_COPYABLE(LoadCompletionHandler) -public: - explicit LoadCompletionHandler(ThumbnailBase* thumb) : m_pThumb(thumb) { - } + public: + explicit LoadCompletionHandler(ThumbnailBase* thumb) : m_thumb(thumb) {} - void operator()(const ThumbnailLoadResult& result) override { - m_pThumb->handleLoadResult(result); - } + void operator()(const ThumbnailLoadResult& result) override { m_thumb->handleLoadResult(result); } -private: - ThumbnailBase* m_pThumb; + private: + ThumbnailBase* m_thumb; }; @@ -49,202 +46,201 @@ ThumbnailBase::ThumbnailBase(intrusive_ptr thumbnail_cache const QSizeF& max_size, const ImageId& image_id, const ImageTransformation& image_xform) - : ThumbnailBase(std::move(thumbnail_cache), - max_size, - image_id, - image_xform, - image_xform.resultingPostCropArea().boundingRect()) { -} + : ThumbnailBase(std::move(thumbnail_cache), + max_size, + image_id, + image_xform, + image_xform.resultingPostCropArea().boundingRect()) {} ThumbnailBase::ThumbnailBase(intrusive_ptr thumbnail_cache, const QSizeF& max_size, const ImageId& image_id, const ImageTransformation& image_xform, QRectF displayArea) - : m_ptrThumbnailCache(std::move(thumbnail_cache)), - m_maxSize(max_size), - m_imageId(image_id), - m_imageXform(image_xform), - m_extendedClipArea(false), - m_displayArea(displayArea) { - setImageXform(m_imageXform); + : m_thumbnailCache(std::move(thumbnail_cache)), + m_maxSize(max_size), + m_imageId(image_id), + m_imageXform(image_xform), + m_extendedClipArea(false), + m_displayArea(displayArea) { + setImageXform(m_imageXform); } ThumbnailBase::~ThumbnailBase() = default; QRectF ThumbnailBase::boundingRect() const { - return m_boundingRect; + return m_boundingRect; } void ThumbnailBase::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - QPixmap pixmap; - - if (!m_ptrCompletionHandler) { - std::shared_ptr handler(new LoadCompletionHandler(this)); - const ThumbnailPixmapCache::Status status = m_ptrThumbnailCache->loadRequest(m_imageId, pixmap, handler); - if (status == ThumbnailPixmapCache::QUEUED) { - m_ptrCompletionHandler.swap(handler); - } - } + QPixmap pixmap; - const QTransform image_to_display(m_postScaleXform * painter->worldTransform()); - const QTransform thumb_to_display(painter->worldTransform()); + if (!m_completionHandler) { + std::shared_ptr handler(new LoadCompletionHandler(this)); + const ThumbnailPixmapCache::Status status = m_thumbnailCache->loadRequest(m_imageId, pixmap, handler); + if (status == ThumbnailPixmapCache::QUEUED) { + m_completionHandler.swap(handler); + } + } - if (pixmap.isNull()) { - const double border = 1.0; - const double shadow = 2.0; - QRectF rect(m_boundingRect); - rect.adjust(border, border, -(border + shadow), -(border + shadow)); + const QTransform image_to_display(m_postScaleXform * painter->worldTransform()); + const QTransform thumb_to_display(painter->worldTransform()); - painter->fillRect(m_boundingRect, QColor(0x00, 0x00, 0x00)); - painter->fillRect(rect, QColor(0xff, 0xff, 0xff)); + if (pixmap.isNull()) { + const double border = 1.0; + const double shadow = 2.0; + QRectF rect(m_boundingRect); + rect.adjust(border, border, -(border + shadow), -(border + shadow)); - paintOverImage(*painter, image_to_display, thumb_to_display); + painter->fillRect(m_boundingRect, QColor(0x00, 0x00, 0x00)); + painter->fillRect(rect, QColor(0xff, 0xff, 0xff)); - return; - } + paintOverImage(*painter, image_to_display, thumb_to_display); + return; + } - const QSizeF orig_image_size(m_imageXform.origRect().size()); - const double x_pre_scale = orig_image_size.width() / pixmap.width(); - const double y_pre_scale = orig_image_size.height() / pixmap.height(); - QTransform pre_scale_xform; - pre_scale_xform.scale(x_pre_scale, y_pre_scale); - const QTransform pixmap_to_thumb(pre_scale_xform * m_imageXform.transform() * m_postScaleXform); + const QSizeF orig_image_size(m_imageXform.origRect().size()); + const double x_pre_scale = orig_image_size.width() / pixmap.width(); + const double y_pre_scale = orig_image_size.height() / pixmap.height(); + QTransform pre_scale_xform; + pre_scale_xform.scale(x_pre_scale, y_pre_scale); - // The polygon to draw into in original image coordinates. - QPolygonF image_poly(PolygonUtils::round(m_imageXform.resultingPreCropArea())); - if (!m_extendedClipArea) { - image_poly = image_poly.intersected(PolygonUtils::round(m_imageXform.resultingRect())); - } + const QTransform pixmap_to_thumb(pre_scale_xform * m_imageXform.transform() * m_postScaleXform); - // The polygon to draw into in display coordinates. - QPolygonF display_poly(image_to_display.map(image_poly)); + // The polygon to draw into in original image coordinates. + QPolygonF image_poly(PolygonUtils::round(m_imageXform.resultingPreCropArea())); + if (!m_extendedClipArea) { + image_poly = image_poly.intersected(PolygonUtils::round(m_imageXform.resultingRect())); + } - QRectF display_rect(image_to_display.map(PolygonUtils::round(m_displayArea)).boundingRect().toAlignedRect()); + // The polygon to draw into in display coordinates. + QPolygonF display_poly(image_to_display.map(image_poly)); - QPixmap temp_pixmap; - const QString cache_key(QString::fromLatin1("ThumbnailBase::temp_pixmap")); - if (!QPixmapCache::find(cache_key, temp_pixmap) || (temp_pixmap.width() < display_rect.width()) - || (temp_pixmap.height() < display_rect.height())) { - auto w = (int) display_rect.width(); - auto h = (int) display_rect.height(); - // Add some extra, to avoid rectreating the pixmap too often. - w += w / 10; - h += h / 10; + QRectF display_rect(image_to_display.map(PolygonUtils::round(m_displayArea)).boundingRect().toAlignedRect()); - temp_pixmap = QPixmap(w, h); + QPixmap temp_pixmap; + const QString cache_key(QString::fromLatin1("ThumbnailBase::temp_pixmap")); + if (!QPixmapCache::find(cache_key, temp_pixmap) || (temp_pixmap.width() < display_rect.width()) + || (temp_pixmap.height() < display_rect.height())) { + auto w = (int) display_rect.width(); + auto h = (int) display_rect.height(); + // Add some extra, to avoid rectreating the pixmap too often. + w += w / 10; + h += h / 10; - if (!temp_pixmap.hasAlphaChannel()) { - // This actually forces the alpha channel to be created. - temp_pixmap.fill(Qt::transparent); - } + temp_pixmap = QPixmap(w, h); - QPixmapCache::insert(cache_key, temp_pixmap); + if (!temp_pixmap.hasAlphaChannel()) { + // This actually forces the alpha channel to be created. + temp_pixmap.fill(Qt::transparent); } - QPainter temp_painter; - temp_painter.begin(&temp_pixmap); + QPixmapCache::insert(cache_key, temp_pixmap); + } - QTransform temp_adjustment; - temp_adjustment.translate(-display_rect.left(), -display_rect.top()); + QPainter temp_painter; + temp_painter.begin(&temp_pixmap); - temp_painter.setWorldTransform(pixmap_to_thumb * thumb_to_display * temp_adjustment); + QTransform temp_adjustment; + temp_adjustment.translate(-display_rect.left(), -display_rect.top()); - // Turn off alpha compositing. - temp_painter.setCompositionMode(QPainter::CompositionMode_Source); + temp_painter.setWorldTransform(pixmap_to_thumb * thumb_to_display * temp_adjustment); - temp_painter.setRenderHint(QPainter::SmoothPixmapTransform); - temp_painter.setRenderHint(QPainter::Antialiasing); + // Turn off alpha compositing. + temp_painter.setCompositionMode(QPainter::CompositionMode_Source); - PixmapRenderer::drawPixmap(temp_painter, pixmap); + temp_painter.setRenderHint(QPainter::SmoothPixmapTransform); + temp_painter.setRenderHint(QPainter::Antialiasing); - // Turn alpha compositing on again. - temp_painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - // Setup the painter for drawing in thumbnail coordinates, - // as required for paintOverImage(). - temp_painter.setWorldTransform(thumb_to_display * temp_adjustment); + PixmapRenderer::drawPixmap(temp_painter, pixmap); - temp_painter.save(); - prePaintOverImage(temp_painter, image_to_display * temp_adjustment, thumb_to_display * temp_adjustment); - temp_painter.restore(); + // Turn alpha compositing on again. + temp_painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + // Setup the painter for drawing in thumbnail coordinates, + // as required for paintOverImage(). + temp_painter.setWorldTransform(thumb_to_display * temp_adjustment); - temp_painter.setPen(Qt::NoPen); - temp_painter.setBrush(Qt::transparent); - temp_painter.setWorldTransform(temp_adjustment); + temp_painter.save(); + prePaintOverImage(temp_painter, image_to_display * temp_adjustment, thumb_to_display * temp_adjustment); + temp_painter.restore(); - temp_painter.setCompositionMode(QPainter::CompositionMode_Clear); + temp_painter.setPen(Qt::NoPen); + temp_painter.setBrush(Qt::transparent); + temp_painter.setWorldTransform(temp_adjustment); - { - QPainterPath outer_path; - outer_path.addRect(display_rect); - QPainterPath inner_path; - inner_path.addPolygon(display_poly); + temp_painter.setCompositionMode(QPainter::CompositionMode_Clear); - temp_painter.drawPath(outer_path.subtracted(inner_path)); - } + { + QPainterPath outer_path; + outer_path.addRect(display_rect); + QPainterPath inner_path; + inner_path.addPolygon(display_poly); + + temp_painter.drawPath(outer_path.subtracted(inner_path)); + } - // Turn alpha compositing on again. - temp_painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - // Setup the painter for drawing in thumbnail coordinates, - // as required for paintOverImage(). - temp_painter.setWorldTransform(thumb_to_display * temp_adjustment); + // Turn alpha compositing on again. + temp_painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + // Setup the painter for drawing in thumbnail coordinates, + // as required for paintOverImage(). + temp_painter.setWorldTransform(thumb_to_display * temp_adjustment); - temp_painter.save(); - paintOverImage(temp_painter, image_to_display * temp_adjustment, thumb_to_display * temp_adjustment); - temp_painter.restore(); + temp_painter.save(); + paintOverImage(temp_painter, image_to_display * temp_adjustment, thumb_to_display * temp_adjustment); + temp_painter.restore(); - temp_painter.end(); + temp_painter.end(); - painter->setClipRect(QRectF(QPointF(0, 0), display_rect.size())); - painter->setRenderHint(QPainter::SmoothPixmapTransform, false); - painter->setCompositionMode(QPainter::CompositionMode_SourceOver); - painter->drawPixmap(QPointF(0, 0), temp_pixmap); + painter->setClipRect(QRectF(QPointF(0, 0), display_rect.size())); + painter->setRenderHint(QPainter::SmoothPixmapTransform, false); + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + painter->drawPixmap(QPointF(0, 0), temp_pixmap); } // ThumbnailBase::paint void ThumbnailBase::paintDeviant(QPainter& painter) { - QSettings settings; - if (!settings.value("settings/highlight_deviation", true).toBool()) { - return; - } + QSettings settings; + if (!settings.value("settings/highlight_deviation", true).toBool()) { + return; + } - QPen pen(QColor(0xdd, 0x00, 0x00, 0xee)); - pen.setWidth(5); - pen.setCosmetic(true); - painter.setPen(pen); + QPen pen(QColor(0xdd, 0x00, 0x00, 0xee)); + pen.setWidth(5); + pen.setCosmetic(true); + painter.setPen(pen); - painter.setBrush(QColor(0xdd, 0x00, 0x00, 0xee)); + painter.setBrush(QColor(0xdd, 0x00, 0x00, 0xee)); - QFont font("Serif"); - font.setWeight(QFont::Bold); - font.setPixelSize(static_cast(boundingRect().width() / 2)); - painter.setFont(font); + QFont font("Serif"); + font.setWeight(QFont::Bold); + font.setPixelSize(static_cast(boundingRect().width() / 2)); + painter.setFont(font); - painter.drawText(boundingRect(), Qt::AlignCenter, "*"); + painter.drawText(boundingRect(), Qt::AlignCenter, "*"); } void ThumbnailBase::setImageXform(const ImageTransformation& image_xform) { - m_imageXform = image_xform; - const QSizeF unscaled_size(m_displayArea.size().expandedTo(QSizeF(1, 1))); - QSizeF scaled_size(unscaled_size); - scaled_size.scale(m_maxSize, Qt::KeepAspectRatio); + m_imageXform = image_xform; + const QSizeF unscaled_size(m_displayArea.size().expandedTo(QSizeF(1, 1))); + QSizeF scaled_size(unscaled_size); + scaled_size.scale(m_maxSize, Qt::KeepAspectRatio); - m_boundingRect = QRectF(QPointF(0.0, 0.0), scaled_size); + m_boundingRect = QRectF(QPointF(0.0, 0.0), scaled_size); - const double x_post_scale = scaled_size.width() / unscaled_size.width(); - const double y_post_scale = scaled_size.height() / unscaled_size.height(); - m_postScaleXform.reset(); - m_postScaleXform.scale(x_post_scale, y_post_scale); + const double x_post_scale = scaled_size.width() / unscaled_size.width(); + const double y_post_scale = scaled_size.height() / unscaled_size.height(); + m_postScaleXform.reset(); + m_postScaleXform.scale(x_post_scale, y_post_scale); } void ThumbnailBase::handleLoadResult(const ThumbnailLoadResult& result) { - m_ptrCompletionHandler.reset(); - - if (result.status() != ThumbnailLoadResult::LOAD_FAILED) { - // Note that we don't store result.pixmap() in - // this object, because we may have already went - // out of view, so we may never receive a paint event. - update(); - } + m_completionHandler.reset(); + + if (result.status() != ThumbnailLoadResult::LOAD_FAILED) { + // Note that we don't store result.pixmap() in + // this object, because we may have already went + // out of view, so we may never receive a paint event. + update(); + } } diff --git a/ThumbnailBase.h b/ThumbnailBase.h index 41bdaa2e6..7ffec052f 100644 --- a/ThumbnailBase.h +++ b/ThumbnailBase.h @@ -19,121 +19,113 @@ #ifndef THUMBNAILBASE_H_ #define THUMBNAILBASE_H_ -#include "NonCopyable.h" +#include +#include +#include +#include #include "ImageId.h" #include "ImageTransformation.h" -#include "intrusive_ptr.h" +#include "NonCopyable.h" #include "ThumbnailPixmapCache.h" -#include -#include -#include -#include +#include "intrusive_ptr.h" class ThumbnailLoadResult; class ThumbnailBase : public QGraphicsItem { - DECLARE_NON_COPYABLE(ThumbnailBase) - -public: - ThumbnailBase(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& image_xform); - - ThumbnailBase(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& image_xform, - QRectF displayArea); - - ~ThumbnailBase() override; - - QRectF boundingRect() const override; - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - -protected: - /** - * \brief A hook to allow subclasses to draw over the thumbnail. - * - * \param painter The painter to be used for drawing. - * \param image_to_display Can be supplied to \p painter as a world - * transformation in order to draw in virtual image coordinates, - * that is in coordinates we get after applying the - * ImageTransformation to the physical image coordinates. - * We are talking about full-sized images here. - * \param thumb_to_display Can be supplied to \p painter as a world - * transformation in order to draw in thumbnail coordinates. - * Valid thumbnail coordinates lie within this->boundingRect(). - * - * The painter is configured for drawing in thumbnail coordinates by - * default. No clipping is configured, but drawing should be - * restricted to this->boundingRect(). Note that it's not necessary - * for subclasses to restore the painter state. - */ - virtual void paintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) { - } - - /** - * \brief This is the same as paintOverImage(). - * The only difference is that the painted content will be cropped with the image. - */ - virtual void prePaintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) { - } - - virtual void paintDeviant(QPainter& painter); - - /** - * By default, the image is clipped by both the crop area (as defined - * by imageXform().resultingPostCropArea()), and the physical boundaries of - * the image itself. Basically a point won't be clipped only if it's both - * inside of the crop area and inside the image. - * Extended clipping area only includes the cropping area, so it's possible - * to draw outside of the image but inside the crop area. - */ - void setExtendedClipArea(bool enabled) { - m_extendedClipArea = enabled; - } - - void setImageXform(const ImageTransformation& image_xform); - - const ImageTransformation& imageXform() const { - return m_imageXform; - } - - /** - * \brief Converts from the virtual image coordinates to thumbnail image coordinates. - * - * Virtual image coordinates is what you get after ImageTransformation. - */ - const QTransform& virtToThumb() const { - return m_postScaleXform; - } - -private: - class LoadCompletionHandler; - - void handleLoadResult(const ThumbnailLoadResult& result); - - intrusive_ptr m_ptrThumbnailCache; - QSizeF m_maxSize; - ImageId m_imageId; - ImageTransformation m_imageXform; - QRectF m_boundingRect; - QRectF m_displayArea; - - /** - * Transforms virtual image coordinates into thumbnail coordinates. - * Valid thumbnail coordinates lie within this->boundingRect(). - */ - QTransform m_postScaleXform; - - std::shared_ptr m_ptrCompletionHandler; - bool m_extendedClipArea; + DECLARE_NON_COPYABLE(ThumbnailBase) + + public: + ThumbnailBase(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& image_xform); + + ThumbnailBase(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& image_xform, + QRectF displayArea); + + ~ThumbnailBase() override; + + QRectF boundingRect() const override; + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + protected: + /** + * \brief A hook to allow subclasses to draw over the thumbnail. + * + * \param painter The painter to be used for drawing. + * \param image_to_display Can be supplied to \p painter as a world + * transformation in order to draw in virtual image coordinates, + * that is in coordinates we get after applying the + * ImageTransformation to the physical image coordinates. + * We are talking about full-sized images here. + * \param thumb_to_display Can be supplied to \p painter as a world + * transformation in order to draw in thumbnail coordinates. + * Valid thumbnail coordinates lie within this->boundingRect(). + * + * The painter is configured for drawing in thumbnail coordinates by + * default. No clipping is configured, but drawing should be + * restricted to this->boundingRect(). Note that it's not necessary + * for subclasses to restore the painter state. + */ + virtual void paintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) {} + + /** + * \brief This is the same as paintOverImage(). + * The only difference is that the painted content will be cropped with the image. + */ + virtual void prePaintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) {} + + virtual void paintDeviant(QPainter& painter); + + /** + * By default, the image is clipped by both the crop area (as defined + * by imageXform().resultingPostCropArea()), and the physical boundaries of + * the image itself. Basically a point won't be clipped only if it's both + * inside of the crop area and inside the image. + * Extended clipping area only includes the cropping area, so it's possible + * to draw outside of the image but inside the crop area. + */ + void setExtendedClipArea(bool enabled) { m_extendedClipArea = enabled; } + + void setImageXform(const ImageTransformation& image_xform); + + const ImageTransformation& imageXform() const { return m_imageXform; } + + /** + * \brief Converts from the virtual image coordinates to thumbnail image coordinates. + * + * Virtual image coordinates is what you get after ImageTransformation. + */ + const QTransform& virtToThumb() const { return m_postScaleXform; } + + private: + class LoadCompletionHandler; + + void handleLoadResult(const ThumbnailLoadResult& result); + + intrusive_ptr m_thumbnailCache; + QSizeF m_maxSize; + ImageId m_imageId; + ImageTransformation m_imageXform; + QRectF m_boundingRect; + QRectF m_displayArea; + + /** + * Transforms virtual image coordinates into thumbnail coordinates. + * Valid thumbnail coordinates lie within this->boundingRect(). + */ + QTransform m_postScaleXform; + + std::shared_ptr m_completionHandler; + bool m_extendedClipArea; }; diff --git a/ThumbnailFactory.cpp b/ThumbnailFactory.cpp index fa3a786cc..19e7e30a0 100644 --- a/ThumbnailFactory.cpp +++ b/ThumbnailFactory.cpp @@ -17,61 +17,57 @@ */ #include "ThumbnailFactory.h" -#include "CompositeCacheDrivenTask.h" -#include "filter_dc/ThumbnailCollector.h" #include #include +#include "CompositeCacheDrivenTask.h" +#include "filter_dc/ThumbnailCollector.h" class ThumbnailFactory::Collector : public ThumbnailCollector { -public: - Collector(intrusive_ptr cache, const QSizeF& max_size); + public: + Collector(intrusive_ptr cache, const QSizeF& max_size); - void processThumbnail(std::unique_ptr thumbnail) override; + void processThumbnail(std::unique_ptr thumbnail) override; - intrusive_ptr thumbnailCache() override; + intrusive_ptr thumbnailCache() override; - QSizeF maxLogicalThumbSize() const override; + QSizeF maxLogicalThumbSize() const override; - std::unique_ptr retrieveThumbnail() { - return std::move(m_ptrThumbnail); - } + std::unique_ptr retrieveThumbnail() { return std::move(m_thumbnail); } -private: - intrusive_ptr m_ptrCache; - QSizeF m_maxSize; - std::unique_ptr m_ptrThumbnail; + private: + intrusive_ptr m_cache; + QSizeF m_maxSize; + std::unique_ptr m_thumbnail; }; ThumbnailFactory::ThumbnailFactory(intrusive_ptr pixmap_cache, const QSizeF& max_size, intrusive_ptr task) - : m_ptrPixmapCache(std::move(pixmap_cache)), m_maxSize(max_size), m_ptrTask(std::move(task)) { -} + : m_pixmapCache(std::move(pixmap_cache)), m_maxSize(max_size), m_task(std::move(task)) {} ThumbnailFactory::~ThumbnailFactory() = default; std::unique_ptr ThumbnailFactory::get(const PageInfo& page_info) { - Collector collector(m_ptrPixmapCache, m_maxSize); - m_ptrTask->process(page_info, &collector); + Collector collector(m_pixmapCache, m_maxSize); + m_task->process(page_info, &collector); - return collector.retrieveThumbnail(); + return collector.retrieveThumbnail(); } /*======================= ThumbnailFactory::Collector ======================*/ ThumbnailFactory::Collector::Collector(intrusive_ptr cache, const QSizeF& max_size) - : m_ptrCache(std::move(cache)), m_maxSize(max_size) { -} + : m_cache(std::move(cache)), m_maxSize(max_size) {} void ThumbnailFactory::Collector::processThumbnail(std::unique_ptr thumbnail) { - m_ptrThumbnail = std::move(thumbnail); + m_thumbnail = std::move(thumbnail); } intrusive_ptr ThumbnailFactory::Collector::thumbnailCache() { - return m_ptrCache; + return m_cache; } QSizeF ThumbnailFactory::Collector::maxLogicalThumbSize() const { - return m_maxSize; + return m_maxSize; } diff --git a/ThumbnailFactory.h b/ThumbnailFactory.h index bd733a682..e0918d08a 100644 --- a/ThumbnailFactory.h +++ b/ThumbnailFactory.h @@ -19,35 +19,35 @@ #ifndef THUMBNAILFACTORY_H_ #define THUMBNAILFACTORY_H_ -#include "NonCopyable.h" -#include "ref_countable.h" -#include "intrusive_ptr.h" -#include "ThumbnailPixmapCache.h" #include #include +#include "NonCopyable.h" +#include "ThumbnailPixmapCache.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class PageInfo; class CompositeCacheDrivenTask; class QGraphicsItem; class ThumbnailFactory : public ref_countable { - DECLARE_NON_COPYABLE(ThumbnailFactory) + DECLARE_NON_COPYABLE(ThumbnailFactory) -public: - ThumbnailFactory(intrusive_ptr pixmap_cache, - const QSizeF& max_size, - intrusive_ptr task); + public: + ThumbnailFactory(intrusive_ptr pixmap_cache, + const QSizeF& max_size, + intrusive_ptr task); - ~ThumbnailFactory() override; + ~ThumbnailFactory() override; - std::unique_ptr get(const PageInfo& page_info); + std::unique_ptr get(const PageInfo& page_info); -private: - class Collector; + private: + class Collector; - intrusive_ptr m_ptrPixmapCache; - QSizeF m_maxSize; - intrusive_ptr m_ptrTask; + intrusive_ptr m_pixmapCache; + QSizeF m_maxSize; + intrusive_ptr m_task; }; diff --git a/ThumbnailLoadResult.h b/ThumbnailLoadResult.h index 4023a06d3..026a67826 100644 --- a/ThumbnailLoadResult.h +++ b/ThumbnailLoadResult.h @@ -22,50 +22,45 @@ #include class ThumbnailLoadResult { -public: - enum Status { - /** - * \brief Thumbnail loaded successfully. Pixmap is not null. - */ - LOADED, + public: + enum Status { + /** + * \brief Thumbnail loaded successfully. Pixmap is not null. + */ + LOADED, - /** - * \brief Thumbnail failed to load. Pixmap is null. - */ - LOAD_FAILED, + /** + * \brief Thumbnail failed to load. Pixmap is null. + */ + LOAD_FAILED, - /** - * \brief Request has expired. Pixmap is null. - * - * Consider the following situation: we scroll our thumbnail - * list from the beginning all the way to the end. This will - * result in every thumbnail being requested. If we just - * load them in request order, that would be quite slow and - * inefficient. It would be nice if we could cancel the load - * requests for items that went out of view. Unfortunately, - * QGraphicsView doesn't provide "went out of view" - * notifications. Instead, we load thumbnails starting from - * most recently requested, and expire requests after a certain - * number of newer requests are processed. If the client is - * still interested in the thumbnail, it may request it again. - */ - REQUEST_EXPIRED - }; + /** + * \brief Request has expired. Pixmap is null. + * + * Consider the following situation: we scroll our thumbnail + * list from the beginning all the way to the end. This will + * result in every thumbnail being requested. If we just + * load them in request order, that would be quite slow and + * inefficient. It would be nice if we could cancel the load + * requests for items that went out of view. Unfortunately, + * QGraphicsView doesn't provide "went out of view" + * notifications. Instead, we load thumbnails starting from + * most recently requested, and expire requests after a certain + * number of newer requests are processed. If the client is + * still interested in the thumbnail, it may request it again. + */ + REQUEST_EXPIRED + }; - ThumbnailLoadResult(Status status, const QPixmap& pixmap) : m_pixmap(pixmap), m_status(status) { - } + ThumbnailLoadResult(Status status, const QPixmap& pixmap) : m_pixmap(pixmap), m_status(status) {} - Status status() const { - return m_status; - } + Status status() const { return m_status; } - const QPixmap& pixmap() const { - return m_pixmap; - } + const QPixmap& pixmap() const { return m_pixmap; } -private: - QPixmap m_pixmap; - Status m_status; + private: + QPixmap m_pixmap; + Status m_status; }; diff --git a/ThumbnailPixmapCache.cpp b/ThumbnailPixmapCache.cpp index 5952338db..0be6ec292 100644 --- a/ThumbnailPixmapCache.cpp +++ b/ThumbnailPixmapCache.cpp @@ -17,234 +17,226 @@ */ #include "ThumbnailPixmapCache.h" -#include "ImageId.h" -#include "ImageLoader.h" -#include "AtomicFileOverwriter.h" -#include "RelinkablePath.h" -#include "OutOfMemoryHandler.h" -#include "imageproc/Scale.h" -#include "imageproc/GrayImage.h" #include #include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include "AtomicFileOverwriter.h" +#include "ImageId.h" +#include "ImageLoader.h" +#include "OutOfMemoryHandler.h" +#include "RelinkablePath.h" +#include "imageproc/GrayImage.h" +#include "imageproc/Scale.h" using namespace ::boost; using namespace ::boost::multi_index; using namespace imageproc; class ThumbnailPixmapCache::Item { -public: - enum Status { - /** - * The background threaed hasn't touched it yet. - */ - QUEUED, + public: + enum Status { + /** + * The background threaed hasn't touched it yet. + */ + QUEUED, - /** - * The image is currently being loaded by a background - * thread, or it has been loaded, but the main thread - * hasn't yet received the loaded image, or it's currently - * converting it to a pixmap. - */ - IN_PROGRESS, + /** + * The image is currently being loaded by a background + * thread, or it has been loaded, but the main thread + * hasn't yet received the loaded image, or it's currently + * converting it to a pixmap. + */ + IN_PROGRESS, - /** - * The image was loaded and then converted to a pixmap - * by the main thread. - */ - LOADED, + /** + * The image was loaded and then converted to a pixmap + * by the main thread. + */ + LOADED, - /** - * The image could not be loaded. - */ - LOAD_FAILED - }; + /** + * The image could not be loaded. + */ + LOAD_FAILED + }; - ImageId imageId; + ImageId imageId; - mutable QPixmap pixmap; + mutable QPixmap pixmap; - /**< Guaranteed to be set if status is LOADED */ + /**< Guaranteed to be set if status is LOADED */ - mutable std::vector> completionHandlers; + mutable std::vector> completionHandlers; - /** - * The total image loading attempts (of any images) by - * ThumbnailPixmapCache at the time of the creation of this item. - * This information is used for request expiration. - * \see ThumbnailLoadResult::REQUEST_EXPIRED - */ - int precedingLoadAttempts; + /** + * The total image loading attempts (of any images) by + * ThumbnailPixmapCache at the time of the creation of this item. + * This information is used for request expiration. + * \see ThumbnailLoadResult::REQUEST_EXPIRED + */ + int precedingLoadAttempts; - mutable Status status; + mutable Status status; - Item(const ImageId& image_id, int preceding_load_attempts, Status st); + Item(const ImageId& image_id, int preceding_load_attempts, Status st); - Item(const Item& other); + Item(const Item& other); -private: - Item& operator=(const Item& other) = delete; // Assignment is forbidden. + private: + Item& operator=(const Item& other) = delete; // Assignment is forbidden. }; class ThumbnailPixmapCache::Impl : public QThread { -public: - Impl(const QString& thumb_dir, const QSize& max_thumb_size, int max_cached_pixmaps, int expiration_threshold); + public: + Impl(const QString& thumb_dir, const QSize& max_thumb_size, int max_cached_pixmaps, int expiration_threshold); - ~Impl() override; + ~Impl() override; - void setThumbDir(const QString& thumb_dir); + void setThumbDir(const QString& thumb_dir); - Status request(const ImageId& image_id, - QPixmap& pixmap, - bool load_now = false, - const std::weak_ptr* completion_handler = nullptr); + Status request(const ImageId& image_id, + QPixmap& pixmap, + bool load_now = false, + const std::weak_ptr* completion_handler = nullptr); - void ensureThumbnailExists(const ImageId& image_id, const QImage& image); + void ensureThumbnailExists(const ImageId& image_id, const QImage& image); - void recreateThumbnail(const ImageId& image_id, const QImage& image); + void recreateThumbnail(const ImageId& image_id, const QImage& image); -protected: - void run() override; + protected: + void run() override; - void customEvent(QEvent* e) override; + void customEvent(QEvent* e) override; -private: - class LoadResultEvent; - class ItemsByKeyTag; - class LoadQueueTag; - class RemoveQueueTag; + private: + class LoadResultEvent; + class ItemsByKeyTag; + class LoadQueueTag; + class RemoveQueueTag; - typedef multi_index_container, member>, - sequenced>, - sequenced>>> - Container; + typedef multi_index_container, member>, + sequenced>, + sequenced>>> + Container; - typedef Container::index::type ItemsByKey; - typedef Container::index::type LoadQueue; - typedef Container::index::type RemoveQueue; + typedef Container::index::type ItemsByKey; + typedef Container::index::type LoadQueue; + typedef Container::index::type RemoveQueue; - class BackgroundLoader : public QObject { - public: - explicit BackgroundLoader(Impl& owner); + class BackgroundLoader : public QObject { + public: + explicit BackgroundLoader(Impl& owner); - protected: - void customEvent(QEvent* e) override; + protected: + void customEvent(QEvent* e) override; - private: - Impl& m_rOwner; - }; + private: + Impl& m_owner; + }; - void backgroundProcessing(); + void backgroundProcessing(); - static QImage loadSaveThumbnail(const ImageId& image_id, const QString& thumb_dir, const QSize& max_thumb_size); + static QImage loadSaveThumbnail(const ImageId& image_id, const QString& thumb_dir, const QSize& max_thumb_size); - static QString getThumbFilePath(const ImageId& image_id, const QString& thumb_dir); + static QString getThumbFilePath(const ImageId& image_id, const QString& thumb_dir); - static QImage makeThumbnail(const QImage& image, const QSize& max_thumb_size); + static QImage makeThumbnail(const QImage& image, const QSize& max_thumb_size); - void queuedToInProgress(const LoadQueue::iterator& lq_it); + void queuedToInProgress(const LoadQueue::iterator& lq_it); - void postLoadResult(const LoadQueue::iterator& lq_it, const QImage& image, ThumbnailLoadResult::Status status); + void postLoadResult(const LoadQueue::iterator& lq_it, const QImage& image, ThumbnailLoadResult::Status status); - void processLoadResult(LoadResultEvent* result); + void processLoadResult(LoadResultEvent* result); - void removeExcessLocked(); + void removeExcessLocked(); - void removeItemLocked(const RemoveQueue::iterator& it); + void removeItemLocked(const RemoveQueue::iterator& it); - void cachePixmapUnlocked(const ImageId& image_id, const QPixmap& pixmap); + void cachePixmapUnlocked(const ImageId& image_id, const QPixmap& pixmap); - void cachePixmapLocked(const ImageId& image_id, const QPixmap& pixmap); + void cachePixmapLocked(const ImageId& image_id, const QPixmap& pixmap); - mutable QMutex m_mutex; - BackgroundLoader m_backgroundLoader; - Container m_items; - ItemsByKey& m_itemsByKey; /**< ImageId => Item mapping */ + mutable QMutex m_mutex; + BackgroundLoader m_backgroundLoader; + Container m_items; + ItemsByKey& m_itemsByKey; /**< ImageId => Item mapping */ - /** - * An "std::list"-like view of QUEUED items in the order they are - * going to be loaded. Actually the list contains all kinds of items, - * but all QUEUED ones precede any others. New QUEUED items are added - * to the front of this list for purposes of request expiration. - * \see ThumbnailLoadResult::REQUEST_EXPIRED - */ - LoadQueue& m_loadQueue; + /** + * An "std::list"-like view of QUEUED items in the order they are + * going to be loaded. Actually the list contains all kinds of items, + * but all QUEUED ones precede any others. New QUEUED items are added + * to the front of this list for purposes of request expiration. + * \see ThumbnailLoadResult::REQUEST_EXPIRED + */ + LoadQueue& m_loadQueue; - /** - * An "std::list"-like view of LOADED items in the order they are - * going to be removed. Actually the list contains all kinds of items, - * but all LOADED ones precede any others. Note that we don't bother - * removing items without a pixmap, which would be all except LOADED - * items. New LOADED items are added after the last LOADED item - * already present in the list. - */ - RemoveQueue& m_removeQueue; + /** + * An "std::list"-like view of LOADED items in the order they are + * going to be removed. Actually the list contains all kinds of items, + * but all LOADED ones precede any others. Note that we don't bother + * removing items without a pixmap, which would be all except LOADED + * items. New LOADED items are added after the last LOADED item + * already present in the list. + */ + RemoveQueue& m_removeQueue; - /** - * An iterator of m_removeQueue that marks the end of LOADED items. - */ - RemoveQueue::iterator m_endOfLoadedItems; + /** + * An iterator of m_removeQueue that marks the end of LOADED items. + */ + RemoveQueue::iterator m_endOfLoadedItems; - QString m_thumbDir; - QSize m_maxThumbSize; - int m_maxCachedPixmaps; + QString m_thumbDir; + QSize m_maxThumbSize; + int m_maxCachedPixmaps; - /** - * \see ThumbnailPixmapCache::ThumbnailPixmapCache() - */ - int m_expirationThreshold; + /** + * \see ThumbnailPixmapCache::ThumbnailPixmapCache() + */ + int m_expirationThreshold; - int m_numQueuedItems; - int m_numLoadedItems; + int m_numQueuedItems; + int m_numLoadedItems; - /** - * Total image loading attempts so far. Used for request expiration. - * \see ThumbnailLoadResult::REQUEST_EXPIRED - */ - int m_totalLoadAttempts; + /** + * Total image loading attempts so far. Used for request expiration. + * \see ThumbnailLoadResult::REQUEST_EXPIRED + */ + int m_totalLoadAttempts; - bool m_threadStarted; - bool m_shuttingDown; + bool m_threadStarted; + bool m_shuttingDown; }; class ThumbnailPixmapCache::Impl::LoadResultEvent : public QEvent { -public: - LoadResultEvent(const Impl::LoadQueue::iterator& lq_it, const QImage& image, ThumbnailLoadResult::Status status); + public: + LoadResultEvent(const Impl::LoadQueue::iterator& lq_it, const QImage& image, ThumbnailLoadResult::Status status); - ~LoadResultEvent() override; + ~LoadResultEvent() override; - Impl::LoadQueue::iterator lqIter() const { - return m_lqIter; - } + Impl::LoadQueue::iterator lqIter() const { return m_lqIter; } - const QImage& image() const { - return m_image; - } + const QImage& image() const { return m_image; } - void releaseImage() { - m_image = QImage(); - } + void releaseImage() { m_image = QImage(); } - ThumbnailLoadResult::Status status() const { - return m_status; - } + ThumbnailLoadResult::Status status() const { return m_status; } -private: - Impl::LoadQueue::iterator m_lqIter; - QImage m_image; - ThumbnailLoadResult::Status m_status; + private: + Impl::LoadQueue::iterator m_lqIter; + QImage m_image; + ThumbnailLoadResult::Status m_status; }; @@ -254,39 +246,36 @@ ThumbnailPixmapCache::ThumbnailPixmapCache(const QString& thumb_dir, const QSize& max_thumb_size, const int max_cached_pixmaps, const int expiration_threshold) - : m_ptrImpl(new Impl(RelinkablePath::normalize(thumb_dir), - max_thumb_size, - max_cached_pixmaps, - expiration_threshold)) { + : m_impl(new Impl(RelinkablePath::normalize(thumb_dir), max_thumb_size, max_cached_pixmaps, expiration_threshold)) { } ThumbnailPixmapCache::~ThumbnailPixmapCache() = default; void ThumbnailPixmapCache::setThumbDir(const QString& thumb_dir) { - m_ptrImpl->setThumbDir(RelinkablePath::normalize(thumb_dir)); + m_impl->setThumbDir(RelinkablePath::normalize(thumb_dir)); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadFromCache(const ImageId& image_id, QPixmap& pixmap) { - return m_ptrImpl->request(image_id, pixmap); + return m_impl->request(image_id, pixmap); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadNow(const ImageId& image_id, QPixmap& pixmap) { - return m_ptrImpl->request(image_id, pixmap, true); + return m_impl->request(image_id, pixmap, true); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadRequest( - const ImageId& image_id, - QPixmap& pixmap, - const std::weak_ptr& completion_handler) { - return m_ptrImpl->request(image_id, pixmap, false, &completion_handler); + const ImageId& image_id, + QPixmap& pixmap, + const std::weak_ptr& completion_handler) { + return m_impl->request(image_id, pixmap, false, &completion_handler); } void ThumbnailPixmapCache::ensureThumbnailExists(const ImageId& image_id, const QImage& image) { - m_ptrImpl->ensureThumbnailExists(image_id, image); + m_impl->ensureThumbnailExists(image_id, image); } void ThumbnailPixmapCache::recreateThumbnail(const ImageId& image_id, const QImage& image) { - m_ptrImpl->recreateThumbnail(image_id, image); + m_impl->recreateThumbnail(image_id, image); } /*======================= ThumbnailPixmapCache::Impl ========================*/ @@ -295,570 +284,569 @@ ThumbnailPixmapCache::Impl::Impl(const QString& thumb_dir, const QSize& max_thumb_size, const int max_cached_pixmaps, const int expiration_threshold) - : m_backgroundLoader(*this), - m_items(), - m_itemsByKey(m_items.get()), - m_loadQueue(m_items.get()), - m_removeQueue(m_items.get()), - m_endOfLoadedItems(m_removeQueue.end()), - m_thumbDir(thumb_dir), - m_maxThumbSize(max_thumb_size), - m_maxCachedPixmaps(max_cached_pixmaps), - m_expirationThreshold(expiration_threshold), - m_numQueuedItems(0), - m_numLoadedItems(0), - m_totalLoadAttempts(0), - m_threadStarted(false), - m_shuttingDown(false) { - // Note that QDir::mkdir() will fail if the parent directory, - // that is $OUT/cache doesn't exist. We want that behaviour, - // as otherwise when loading a project from a different machine, - // a whole bunch of bogus directories would be created. - QDir().mkdir(m_thumbDir); - - m_backgroundLoader.moveToThread(this); + : m_backgroundLoader(*this), + m_items(), + m_itemsByKey(m_items.get()), + m_loadQueue(m_items.get()), + m_removeQueue(m_items.get()), + m_endOfLoadedItems(m_removeQueue.end()), + m_thumbDir(thumb_dir), + m_maxThumbSize(max_thumb_size), + m_maxCachedPixmaps(max_cached_pixmaps), + m_expirationThreshold(expiration_threshold), + m_numQueuedItems(0), + m_numLoadedItems(0), + m_totalLoadAttempts(0), + m_threadStarted(false), + m_shuttingDown(false) { + // Note that QDir::mkdir() will fail if the parent directory, + // that is $OUT/cache doesn't exist. We want that behaviour, + // as otherwise when loading a project from a different machine, + // a whole bunch of bogus directories would be created. + QDir().mkdir(m_thumbDir); + + m_backgroundLoader.moveToThread(this); } ThumbnailPixmapCache::Impl::~Impl() { - { - const QMutexLocker locker(&m_mutex); - - if (!m_threadStarted) { - return; - } + { + const QMutexLocker locker(&m_mutex); - m_shuttingDown = true; + if (!m_threadStarted) { + return; } - quit(); - wait(); + m_shuttingDown = true; + } + + quit(); + wait(); } void ThumbnailPixmapCache::Impl::setThumbDir(const QString& thumb_dir) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - if (thumb_dir == m_thumbDir) { - return; - } + if (thumb_dir == m_thumbDir) { + return; + } - m_thumbDir = thumb_dir; + m_thumbDir = thumb_dir; - for (const Item& item : m_loadQueue) { - // This trick will make all queued tasks to expire. - m_totalLoadAttempts = std::max(m_totalLoadAttempts, item.precedingLoadAttempts + m_expirationThreshold + 1); - } + for (const Item& item : m_loadQueue) { + // This trick will make all queued tasks to expire. + m_totalLoadAttempts = std::max(m_totalLoadAttempts, item.precedingLoadAttempts + m_expirationThreshold + 1); + } } ThumbnailPixmapCache::Status ThumbnailPixmapCache::Impl::request( - const ImageId& image_id, - QPixmap& pixmap, - const bool load_now, - const std::weak_ptr* completion_handler) { - assert(QCoreApplication::instance()->thread() == QThread::currentThread()); + const ImageId& image_id, + QPixmap& pixmap, + const bool load_now, + const std::weak_ptr* completion_handler) { + assert(QCoreApplication::instance()->thread() == QThread::currentThread()); - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - if (m_shuttingDown) { - return LOAD_FAILED; - } + if (m_shuttingDown) { + return LOAD_FAILED; + } - const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); - if (k_it != m_itemsByKey.end()) { - if (k_it->status == Item::LOADED) { - pixmap = k_it->pixmap; + const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); + if (k_it != m_itemsByKey.end()) { + if (k_it->status == Item::LOADED) { + pixmap = k_it->pixmap; - // Move it after all other candidates for removal. - const RemoveQueue::iterator rq_it(m_items.project(k_it)); - m_removeQueue.relocate(m_endOfLoadedItems, rq_it); + // Move it after all other candidates for removal. + const RemoveQueue::iterator rq_it(m_items.project(k_it)); + m_removeQueue.relocate(m_endOfLoadedItems, rq_it); - return LOADED; - } else if (k_it->status == Item::LOAD_FAILED) { - pixmap = k_it->pixmap; + return LOADED; + } else if (k_it->status == Item::LOAD_FAILED) { + pixmap = k_it->pixmap; - return LOAD_FAILED; - } + return LOAD_FAILED; } + } - if (load_now) { - const QString thumb_dir(m_thumbDir); - const QSize max_thumb_size(m_maxThumbSize); - - locker.unlock(); - - pixmap = QPixmap::fromImage(loadSaveThumbnail(image_id, thumb_dir, max_thumb_size)); - if (pixmap.isNull()) { - return LOAD_FAILED; - } - - cachePixmapUnlocked(image_id, pixmap); + if (load_now) { + const QString thumb_dir(m_thumbDir); + const QSize max_thumb_size(m_maxThumbSize); - return LOADED; - } + locker.unlock(); - if (!completion_handler) { - return LOAD_FAILED; + pixmap = QPixmap::fromImage(loadSaveThumbnail(image_id, thumb_dir, max_thumb_size)); + if (pixmap.isNull()) { + return LOAD_FAILED; } - if (k_it != m_itemsByKey.end()) { - assert(k_it->status == Item::QUEUED || k_it->status == Item::IN_PROGRESS); - k_it->completionHandlers.push_back(*completion_handler); - - if (k_it->status == Item::QUEUED) { - // Because we've got a new request for this item, - // we move it to the beginning of the load queue. - // Note that we don't do it for IN_PROGRESS items, - // because all QUEUED items must precede any other - // items in the load queue. - const LoadQueue::iterator lq_it(m_items.project(k_it)); - m_loadQueue.relocate(m_loadQueue.begin(), lq_it); - } + cachePixmapUnlocked(image_id, pixmap); - return QUEUED; - } + return LOADED; + } - // Create a new item. - const LoadQueue::iterator lq_it(m_loadQueue.push_front(Item(image_id, m_totalLoadAttempts, Item::QUEUED)).first); - // Now our new item is at the beginning of the load queue and at the - // end of the remove queue. + if (!completion_handler) { + return LOAD_FAILED; + } - assert(lq_it->status == Item::QUEUED); - assert(lq_it->completionHandlers.empty()); + if (k_it != m_itemsByKey.end()) { + assert(k_it->status == Item::QUEUED || k_it->status == Item::IN_PROGRESS); + k_it->completionHandlers.push_back(*completion_handler); - if (m_endOfLoadedItems == m_removeQueue.end()) { - m_endOfLoadedItems = m_items.project(lq_it); - } - lq_it->completionHandlers.push_back(*completion_handler); - - if (m_numQueuedItems++ == 0) { - if (m_threadStarted) { - // Wake the background thread up. - QCoreApplication::postEvent(&m_backgroundLoader, new QEvent(QEvent::User)); - } else { - // Start the background thread. - start(); - m_threadStarted = true; - } + if (k_it->status == Item::QUEUED) { + // Because we've got a new request for this item, + // we move it to the beginning of the load queue. + // Note that we don't do it for IN_PROGRESS items, + // because all QUEUED items must precede any other + // items in the load queue. + const LoadQueue::iterator lq_it(m_items.project(k_it)); + m_loadQueue.relocate(m_loadQueue.begin(), lq_it); } return QUEUED; -} // ThumbnailPixmapCache::Impl::request + } -void ThumbnailPixmapCache::Impl::ensureThumbnailExists(const ImageId& image_id, const QImage& image) { - if (m_shuttingDown) { - return; - } + // Create a new item. + const LoadQueue::iterator lq_it(m_loadQueue.push_front(Item(image_id, m_totalLoadAttempts, Item::QUEUED)).first); + // Now our new item is at the beginning of the load queue and at the + // end of the remove queue. - if (image.isNull()) { - return; - } + assert(lq_it->status == Item::QUEUED); + assert(lq_it->completionHandlers.empty()); - QMutexLocker locker(&m_mutex); - const QString thumb_dir(m_thumbDir); - const QSize max_thumb_size(m_maxThumbSize); - locker.unlock(); + if (m_endOfLoadedItems == m_removeQueue.end()) { + m_endOfLoadedItems = m_items.project(lq_it); + } + lq_it->completionHandlers.push_back(*completion_handler); - const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); - if (QFile::exists(thumb_file_path)) { - return; + if (m_numQueuedItems++ == 0) { + if (m_threadStarted) { + // Wake the background thread up. + QCoreApplication::postEvent(&m_backgroundLoader, new QEvent(QEvent::User)); + } else { + // Start the background thread. + start(); + m_threadStarted = true; } + } - const QImage thumbnail(makeThumbnail(image, max_thumb_size)); + return QUEUED; +} // ThumbnailPixmapCache::Impl::request - AtomicFileOverwriter overwriter; - QIODevice* iodev = overwriter.startWriting(thumb_file_path); - if (iodev && thumbnail.save(iodev, "PNG")) { - overwriter.commit(); - } +void ThumbnailPixmapCache::Impl::ensureThumbnailExists(const ImageId& image_id, const QImage& image) { + if (m_shuttingDown) { + return; + } + + if (image.isNull()) { + return; + } + + QMutexLocker locker(&m_mutex); + const QString thumb_dir(m_thumbDir); + const QSize max_thumb_size(m_maxThumbSize); + locker.unlock(); + + const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); + if (QFile::exists(thumb_file_path)) { + return; + } + + const QImage thumbnail(makeThumbnail(image, max_thumb_size)); + + AtomicFileOverwriter overwriter; + QIODevice* iodev = overwriter.startWriting(thumb_file_path); + if (iodev && thumbnail.save(iodev, "PNG")) { + overwriter.commit(); + } } void ThumbnailPixmapCache::Impl::recreateThumbnail(const ImageId& image_id, const QImage& image) { - if (m_shuttingDown) { - return; - } + if (m_shuttingDown) { + return; + } + + if (image.isNull()) { + return; + } + + QMutexLocker locker(&m_mutex); + const QString thumb_dir(m_thumbDir); + const QSize max_thumb_size(m_maxThumbSize); + locker.unlock(); + + const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); + const QImage thumbnail(makeThumbnail(image, max_thumb_size)); + bool thumb_written = false; + + // Note that we may be called from multiple threads at the same time. + AtomicFileOverwriter overwriter; + QIODevice* iodev = overwriter.startWriting(thumb_file_path); + if (iodev && thumbnail.save(iodev, "PNG")) { + thumb_written = overwriter.commit(); + } else { + overwriter.abort(); + } + + if (!thumb_written) { + return; + } + + const QMutexLocker locker2(&m_mutex); + + const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); + if (k_it == m_itemsByKey.end()) { + return; + } + + switch (k_it->status) { + case Item::LOADED: + case Item::LOAD_FAILED: + removeItemLocked(m_items.project(k_it)); + break; + case Item::QUEUED: + break; + case Item::IN_PROGRESS: + // We have a small race condition in this case. + // We don't know if the other thread has already loaded + // the thumbnail or not. In case it did, again we + // don't know if it loaded the old or new version. + // Well, let's just pretend the thumnail was loaded + // (or failed to load) before we wrote the new version. + break; + } +} // ThumbnailPixmapCache::Impl::recreateThumbnail - if (image.isNull()) { - return; - } +void ThumbnailPixmapCache::Impl::run() { + backgroundProcessing(); + exec(); // Wait for further processing requests (via custom events). +} - QMutexLocker locker(&m_mutex); - const QString thumb_dir(m_thumbDir); - const QSize max_thumb_size(m_maxThumbSize); - locker.unlock(); +void ThumbnailPixmapCache::Impl::customEvent(QEvent* e) { + processLoadResult(dynamic_cast(e)); +} - const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); - const QImage thumbnail(makeThumbnail(image, max_thumb_size)); - bool thumb_written = false; +void ThumbnailPixmapCache::Impl::backgroundProcessing() { + // This method is called from a background thread. + assert(QCoreApplication::instance()->thread() != QThread::currentThread()); + + while (true) { + try { + // We are going to initialize these while holding the mutex. + LoadQueue::iterator lq_it; + ImageId image_id; + QString thumb_dir; + QSize max_thumb_size; + + { + const QMutexLocker locker(&m_mutex); - // Note that we may be called from multiple threads at the same time. - AtomicFileOverwriter overwriter; - QIODevice* iodev = overwriter.startWriting(thumb_file_path); - if (iodev && thumbnail.save(iodev, "PNG")) { - thumb_written = overwriter.commit(); - } else { - overwriter.abort(); - } + if (m_shuttingDown || m_items.empty()) { + break; + } - if (!thumb_written) { - return; - } + lq_it = m_loadQueue.begin(); + image_id = lq_it->imageId; - const QMutexLocker locker2(&m_mutex); + if (lq_it->status != Item::QUEUED) { + // All QUEUED items precede any other items + // in the load queue, so it means there are no + // QUEUED items at all. + assert(m_numQueuedItems == 0); + break; + } - const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); - if (k_it == m_itemsByKey.end()) { - return; - } + // By marking the item as IN_PROGRESS, we prevent it + // from being processed again before the GUI thread + // receives our LoadResultEvent. + queuedToInProgress(lq_it); - switch (k_it->status) { - case Item::LOADED: - case Item::LOAD_FAILED: - removeItemLocked(m_items.project(k_it)); - break; - case Item::QUEUED: - break; - case Item::IN_PROGRESS: - // We have a small race condition in this case. - // We don't know if the other thread has already loaded - // the thumbnail or not. In case it did, again we - // don't know if it loaded the old or new version. - // Well, let's just pretend the thumnail was loaded - // (or failed to load) before we wrote the new version. - break; - } -} // ThumbnailPixmapCache::Impl::recreateThumbnail + if (m_totalLoadAttempts - lq_it->precedingLoadAttempts > m_expirationThreshold) { + // Expire this request. The reasoning behind + // request expiration is described in + // ThumbnailLoadResult::REQUEST_EXPIRED + // documentation. -void ThumbnailPixmapCache::Impl::run() { - backgroundProcessing(); - exec(); // Wait for further processing requests (via custom events). -} + postLoadResult(lq_it, QImage(), ThumbnailLoadResult::REQUEST_EXPIRED); + continue; + } -void ThumbnailPixmapCache::Impl::customEvent(QEvent* e) { - processLoadResult(dynamic_cast(e)); -} + // Expired requests don't count as load attempts. + ++m_totalLoadAttempts; -void ThumbnailPixmapCache::Impl::backgroundProcessing() { - // This method is called from a background thread. - assert(QCoreApplication::instance()->thread() != QThread::currentThread()); - - for (;;) { - try { - // We are going to initialize these while holding the mutex. - LoadQueue::iterator lq_it; - ImageId image_id; - QString thumb_dir; - QSize max_thumb_size; - - { - const QMutexLocker locker(&m_mutex); - - if (m_shuttingDown || m_items.empty()) { - break; - } - - lq_it = m_loadQueue.begin(); - image_id = lq_it->imageId; - - if (lq_it->status != Item::QUEUED) { - // All QUEUED items precede any other items - // in the load queue, so it means there are no - // QUEUED items at all. - assert(m_numQueuedItems == 0); - break; - } - - // By marking the item as IN_PROGRESS, we prevent it - // from being processed again before the GUI thread - // receives our LoadResultEvent. - queuedToInProgress(lq_it); - - if (m_totalLoadAttempts - lq_it->precedingLoadAttempts > m_expirationThreshold) { - // Expire this request. The reasoning behind - // request expiration is described in - // ThumbnailLoadResult::REQUEST_EXPIRED - // documentation. - - postLoadResult(lq_it, QImage(), ThumbnailLoadResult::REQUEST_EXPIRED); - continue; - } - - // Expired requests don't count as load attempts. - ++m_totalLoadAttempts; - - // Copy those while holding the mutex. - thumb_dir = m_thumbDir; - max_thumb_size = m_maxThumbSize; - } // mutex scope - const QImage image(loadSaveThumbnail(image_id, thumb_dir, max_thumb_size)); - - const ThumbnailLoadResult::Status status - = image.isNull() ? ThumbnailLoadResult::LOAD_FAILED : ThumbnailLoadResult::LOADED; - postLoadResult(lq_it, image, status); - } catch (const std::bad_alloc&) { - OutOfMemoryHandler::instance().handleOutOfMemorySituation(); - } + // Copy those while holding the mutex. + thumb_dir = m_thumbDir; + max_thumb_size = m_maxThumbSize; + } // mutex scope + const QImage image(loadSaveThumbnail(image_id, thumb_dir, max_thumb_size)); + + const ThumbnailLoadResult::Status status + = image.isNull() ? ThumbnailLoadResult::LOAD_FAILED : ThumbnailLoadResult::LOADED; + postLoadResult(lq_it, image, status); + } catch (const std::bad_alloc&) { + OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } + } } // ThumbnailPixmapCache::Impl::backgroundProcessing QImage ThumbnailPixmapCache::Impl::loadSaveThumbnail(const ImageId& image_id, const QString& thumb_dir, const QSize& max_thumb_size) { - const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); + const QString thumb_file_path(getThumbFilePath(image_id, thumb_dir)); - QImage image(ImageLoader::load(thumb_file_path, 0)); - if (!image.isNull()) { - return image; - } + QImage image(ImageLoader::load(thumb_file_path, 0)); + if (!image.isNull()) { + return image; + } - image = ImageLoader::load(image_id); - if (image.isNull()) { - return QImage(); - } + image = ImageLoader::load(image_id); + if (image.isNull()) { + return QImage(); + } - const QImage thumbnail(makeThumbnail(image, max_thumb_size)); - thumbnail.save(thumb_file_path, "PNG"); + const QImage thumbnail(makeThumbnail(image, max_thumb_size)); + thumbnail.save(thumb_file_path, "PNG"); - return thumbnail; + return thumbnail; } QString ThumbnailPixmapCache::Impl::getThumbFilePath(const ImageId& image_id, const QString& thumb_dir) { - // Because a project may have several files with the same name (from - // different directories), we add a hash of the original image path - // to the thumbnail file name. - - const QByteArray orig_path_hash( - QCryptographicHash::hash(image_id.filePath().toUtf8(), QCryptographicHash::Md5).toHex()); - const QString orig_path_hash_str(QString::fromLatin1(orig_path_hash.data(), orig_path_hash.size())); - - const QFileInfo orig_img_path(image_id.filePath()); - QString thumb_file_path(thumb_dir); - thumb_file_path += QChar('/'); - thumb_file_path += orig_img_path.baseName(); - thumb_file_path += QChar('_'); - thumb_file_path += QString::number(image_id.zeroBasedPage()); - thumb_file_path += QChar('_'); - thumb_file_path += orig_path_hash_str; - thumb_file_path += QString::fromLatin1(".png"); - - return thumb_file_path; + // Because a project may have several files with the same name (from + // different directories), we add a hash of the original image path + // to the thumbnail file name. + + const QByteArray orig_path_hash( + QCryptographicHash::hash(image_id.filePath().toUtf8(), QCryptographicHash::Md5).toHex()); + const QString orig_path_hash_str(QString::fromLatin1(orig_path_hash.data(), orig_path_hash.size())); + + const QFileInfo orig_img_path(image_id.filePath()); + QString thumb_file_path(thumb_dir); + thumb_file_path += QChar('/'); + thumb_file_path += orig_img_path.baseName(); + thumb_file_path += QChar('_'); + thumb_file_path += QString::number(image_id.zeroBasedPage()); + thumb_file_path += QChar('_'); + thumb_file_path += orig_path_hash_str; + thumb_file_path += QString::fromLatin1(".png"); + + return thumb_file_path; } QImage ThumbnailPixmapCache::Impl::makeThumbnail(const QImage& image, const QSize& max_thumb_size) { - if ((image.width() < max_thumb_size.width()) && (image.height() < max_thumb_size.height())) { - return image; - } + if ((image.width() < max_thumb_size.width()) && (image.height() < max_thumb_size.height())) { + return image; + } - QSize to_size(image.size()); - to_size.scale(max_thumb_size, Qt::KeepAspectRatio); + QSize to_size(image.size()); + to_size.scale(max_thumb_size, Qt::KeepAspectRatio); - if ((image.format() == QImage::Format_Indexed8) && image.isGrayscale()) { - // This will be faster than QImage::scale(). - return scaleToGray(GrayImage(image), to_size); - } + if ((image.format() == QImage::Format_Indexed8) && image.isGrayscale()) { + // This will be faster than QImage::scale(). + return scaleToGray(GrayImage(image), to_size); + } - return image.scaled(to_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + return image.scaled(to_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } void ThumbnailPixmapCache::Impl::queuedToInProgress(const LoadQueue::iterator& lq_it) { - assert(lq_it->status == Item::QUEUED); - lq_it->status = Item::IN_PROGRESS; + assert(lq_it->status == Item::QUEUED); + lq_it->status = Item::IN_PROGRESS; - assert(m_numQueuedItems > 0); - --m_numQueuedItems; + assert(m_numQueuedItems > 0); + --m_numQueuedItems; - // Move it item to the end of load queue. - // The point is to keep QUEUED items before any others. - m_loadQueue.relocate(m_loadQueue.end(), lq_it); + // Move it item to the end of load queue. + // The point is to keep QUEUED items before any others. + m_loadQueue.relocate(m_loadQueue.end(), lq_it); - // Going from QUEUED to IN_PROGRESS doesn't require - // moving it in the remove queue, as we only remove - // LOADED items. + // Going from QUEUED to IN_PROGRESS doesn't require + // moving it in the remove queue, as we only remove + // LOADED items. } void ThumbnailPixmapCache::Impl::postLoadResult(const LoadQueue::iterator& lq_it, const QImage& image, const ThumbnailLoadResult::Status status) { - auto* e = new LoadResultEvent(lq_it, image, status); - QCoreApplication::postEvent(this, e); + auto* e = new LoadResultEvent(lq_it, image, status); + QCoreApplication::postEvent(this, e); } void ThumbnailPixmapCache::Impl::processLoadResult(LoadResultEvent* result) { - assert(QCoreApplication::instance()->thread() == QThread::currentThread()); + assert(QCoreApplication::instance()->thread() == QThread::currentThread()); - QPixmap pixmap(QPixmap::fromImage(result->image())); - result->releaseImage(); + QPixmap pixmap(QPixmap::fromImage(result->image())); + result->releaseImage(); - std::vector> completion_handlers; + std::vector> completion_handlers; - { - const QMutexLocker locker(&m_mutex); + { + const QMutexLocker locker(&m_mutex); - if (m_shuttingDown) { - return; - } + if (m_shuttingDown) { + return; + } - const LoadQueue::iterator lq_it(result->lqIter()); - const RemoveQueue::iterator rq_it(m_items.project(lq_it)); + const LoadQueue::iterator lq_it(result->lqIter()); + const RemoveQueue::iterator rq_it(m_items.project(lq_it)); - const Item& item = *lq_it; + const Item& item = *lq_it; - if ((result->status() == ThumbnailLoadResult::LOADED) && pixmap.isNull()) { - // That's a special case caused by cachePixmapLocked(). - assert(!item.pixmap.isNull()); - } else { - item.pixmap = pixmap; - } - item.completionHandlers.swap(completion_handlers); + if ((result->status() == ThumbnailLoadResult::LOADED) && pixmap.isNull()) { + // That's a special case caused by cachePixmapLocked(). + assert(!item.pixmap.isNull()); + } else { + item.pixmap = pixmap; + } + item.completionHandlers.swap(completion_handlers); - if (result->status() == ThumbnailLoadResult::LOADED) { - // Maybe remove an older item. - removeExcessLocked(); + if (result->status() == ThumbnailLoadResult::LOADED) { + // Maybe remove an older item. + removeExcessLocked(); - item.status = Item::LOADED; - ++m_numLoadedItems; + item.status = Item::LOADED; + ++m_numLoadedItems; - // Move this item after all other LOADED items in - // the remove queue. - m_removeQueue.relocate(m_endOfLoadedItems, rq_it); + // Move this item after all other LOADED items in + // the remove queue. + m_removeQueue.relocate(m_endOfLoadedItems, rq_it); - // Move to the end of load queue. - m_loadQueue.relocate(m_loadQueue.end(), lq_it); - } else if (result->status() == ThumbnailLoadResult::LOAD_FAILED) { - // We keep items that failed to load, as they are cheap - // to keep and helps us avoid trying to load them - // again and again. + // Move to the end of load queue. + m_loadQueue.relocate(m_loadQueue.end(), lq_it); + } else if (result->status() == ThumbnailLoadResult::LOAD_FAILED) { + // We keep items that failed to load, as they are cheap + // to keep and helps us avoid trying to load them + // again and again. - item.status = Item::LOAD_FAILED; + item.status = Item::LOAD_FAILED; - // Move to the end of load queue. - m_loadQueue.relocate(m_loadQueue.end(), lq_it); - } else { - assert(result->status() == ThumbnailLoadResult::REQUEST_EXPIRED); + // Move to the end of load queue. + m_loadQueue.relocate(m_loadQueue.end(), lq_it); + } else { + assert(result->status() == ThumbnailLoadResult::REQUEST_EXPIRED); - // Just remove it. - removeItemLocked(rq_it); - } - } // mutex scope - // Notify listeners. - const ThumbnailLoadResult load_result(result->status(), pixmap); - typedef std::weak_ptr WeakHandler; - for (const WeakHandler& wh : completion_handlers) { - const std::shared_ptr sh(wh.lock()); - if (sh) { - (*sh)(load_result); - } + // Just remove it. + removeItemLocked(rq_it); + } + } // mutex scope + // Notify listeners. + const ThumbnailLoadResult load_result(result->status(), pixmap); + typedef std::weak_ptr WeakHandler; + for (const WeakHandler& wh : completion_handlers) { + const std::shared_ptr sh(wh.lock()); + if (sh) { + (*sh)(load_result); } + } } // ThumbnailPixmapCache::Impl::processLoadResult void ThumbnailPixmapCache::Impl::removeExcessLocked() { - if (m_numLoadedItems >= m_maxCachedPixmaps) { - assert(m_numLoadedItems > 0); - assert(!m_removeQueue.empty()); - assert(m_removeQueue.front().status == Item::LOADED); - removeItemLocked(m_removeQueue.begin()); - } + if (m_numLoadedItems >= m_maxCachedPixmaps) { + assert(m_numLoadedItems > 0); + assert(!m_removeQueue.empty()); + assert(m_removeQueue.front().status == Item::LOADED); + removeItemLocked(m_removeQueue.begin()); + } } void ThumbnailPixmapCache::Impl::removeItemLocked(const RemoveQueue::iterator& it) { - switch (it->status) { - case Item::QUEUED: - assert(m_numQueuedItems > 0); - --m_numQueuedItems; - break; - case Item::LOADED: - assert(m_numLoadedItems > 0); - --m_numLoadedItems; - break; - default:; - } - - if (m_endOfLoadedItems == it) { - ++m_endOfLoadedItems; - } - - m_removeQueue.erase(it); + switch (it->status) { + case Item::QUEUED: + assert(m_numQueuedItems > 0); + --m_numQueuedItems; + break; + case Item::LOADED: + assert(m_numLoadedItems > 0); + --m_numLoadedItems; + break; + default:; + } + + if (m_endOfLoadedItems == it) { + ++m_endOfLoadedItems; + } + + m_removeQueue.erase(it); } void ThumbnailPixmapCache::Impl::cachePixmapUnlocked(const ImageId& image_id, const QPixmap& pixmap) { - const QMutexLocker locker(&m_mutex); - cachePixmapLocked(image_id, pixmap); + const QMutexLocker locker(&m_mutex); + cachePixmapLocked(image_id, pixmap); } void ThumbnailPixmapCache::Impl::cachePixmapLocked(const ImageId& image_id, const QPixmap& pixmap) { - if (m_shuttingDown) { - return; - } + if (m_shuttingDown) { + return; + } - const Item::Status new_status = pixmap.isNull() ? Item::LOAD_FAILED : Item::LOADED; + const Item::Status new_status = pixmap.isNull() ? Item::LOAD_FAILED : Item::LOADED; - // Check if such item already exists. - const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); - if (k_it == m_itemsByKey.end()) { - // Existing item not found. + // Check if such item already exists. + const ItemsByKey::iterator k_it(m_itemsByKey.find(image_id)); + if (k_it == m_itemsByKey.end()) { + // Existing item not found. - // Maybe remove an older item. - removeExcessLocked(); + // Maybe remove an older item. + removeExcessLocked(); - // Insert our new item. - const RemoveQueue::iterator rq_it( - m_removeQueue.insert(m_endOfLoadedItems, Item(image_id, m_totalLoadAttempts, new_status)).first); - // Our new item is now after all LOADED items in the - // remove queue and at the end of the load queue. - if (new_status == Item::LOAD_FAILED) { - --m_endOfLoadedItems; - } + // Insert our new item. + const RemoveQueue::iterator rq_it( + m_removeQueue.insert(m_endOfLoadedItems, Item(image_id, m_totalLoadAttempts, new_status)).first); + // Our new item is now after all LOADED items in the + // remove queue and at the end of the load queue. + if (new_status == Item::LOAD_FAILED) { + --m_endOfLoadedItems; + } - rq_it->pixmap = pixmap; + rq_it->pixmap = pixmap; - assert(rq_it->completionHandlers.empty()); + assert(rq_it->completionHandlers.empty()); - return; - } + return; + } - switch (k_it->status) { - case Item::LOADED: - // There is no point in replacing LOADED items. - case Item::IN_PROGRESS: - // It's unsafe to touch IN_PROGRESS items. - return; - default: - break; - } + switch (k_it->status) { + case Item::LOADED: + // There is no point in replacing LOADED items. + case Item::IN_PROGRESS: + // It's unsafe to touch IN_PROGRESS items. + return; + default: + break; + } - if ((new_status == Item::LOADED) && (k_it->status == Item::QUEUED)) { - // Not so fast. We can't go from QUEUED to LOADED directly. - // Well, maybe we can, but we'd have to invoke the completion - // handlers right now. We'd rather do it asynchronously, - // so let's transition it to IN_PROGRESS and send - // a LoadResultEvent asynchronously. + if ((new_status == Item::LOADED) && (k_it->status == Item::QUEUED)) { + // Not so fast. We can't go from QUEUED to LOADED directly. + // Well, maybe we can, but we'd have to invoke the completion + // handlers right now. We'd rather do it asynchronously, + // so let's transition it to IN_PROGRESS and send + // a LoadResultEvent asynchronously. - assert(!k_it->completionHandlers.empty()); + assert(!k_it->completionHandlers.empty()); - const LoadQueue::iterator lq_it(m_items.project(k_it)); + const LoadQueue::iterator lq_it(m_items.project(k_it)); - lq_it->pixmap = pixmap; - queuedToInProgress(lq_it); - postLoadResult(lq_it, QImage(), ThumbnailLoadResult::LOADED); + lq_it->pixmap = pixmap; + queuedToInProgress(lq_it); + postLoadResult(lq_it, QImage(), ThumbnailLoadResult::LOADED); - return; - } + return; + } - assert(k_it->status == Item::LOAD_FAILED); + assert(k_it->status == Item::LOAD_FAILED); - k_it->status = new_status; - k_it->pixmap = pixmap; + k_it->status = new_status; + k_it->pixmap = pixmap; - if (new_status == Item::LOADED) { - const RemoveQueue::iterator rq_it(m_items.project(k_it)); - m_removeQueue.relocate(m_endOfLoadedItems, rq_it); - ++m_numLoadedItems; - } + if (new_status == Item::LOADED) { + const RemoveQueue::iterator rq_it(m_items.project(k_it)); + m_removeQueue.relocate(m_endOfLoadedItems, rq_it); + ++m_numLoadedItems; + } } // ThumbnailPixmapCache::Impl::cachePixmapLocked /*====================== ThumbnailPixmapCache::Item =========================*/ ThumbnailPixmapCache::Item::Item(const ImageId& image_id, const int preceding_load_attempts, const Status st) - : imageId(image_id), precedingLoadAttempts(preceding_load_attempts), status(st) { -} + : imageId(image_id), precedingLoadAttempts(preceding_load_attempts), status(st) {} ThumbnailPixmapCache::Item::Item(const Item& other) = default; @@ -867,16 +855,14 @@ ThumbnailPixmapCache::Item::Item(const Item& other) = default; ThumbnailPixmapCache::Impl::LoadResultEvent::LoadResultEvent(const Impl::LoadQueue::iterator& lq_it, const QImage& image, const ThumbnailLoadResult::Status status) - : QEvent(QEvent::User), m_lqIter(lq_it), m_image(image), m_status(status) { -} + : QEvent(QEvent::User), m_lqIter(lq_it), m_image(image), m_status(status) {} ThumbnailPixmapCache::Impl::LoadResultEvent::~LoadResultEvent() = default; /*================== ThumbnailPixmapCache::BackgroundLoader =================*/ -ThumbnailPixmapCache::Impl::BackgroundLoader::BackgroundLoader(Impl& owner) : m_rOwner(owner) { -} +ThumbnailPixmapCache::Impl::BackgroundLoader::BackgroundLoader(Impl& owner) : m_owner(owner) {} void ThumbnailPixmapCache::Impl::BackgroundLoader::customEvent(QEvent*) { - m_rOwner.backgroundProcessing(); + m_owner.backgroundProcessing(); } diff --git a/ThumbnailPixmapCache.h b/ThumbnailPixmapCache.h index 5cb3a56d5..fd7447486 100644 --- a/ThumbnailPixmapCache.h +++ b/ThumbnailPixmapCache.h @@ -19,12 +19,12 @@ #ifndef THUMBNAILPIXMAPCACHE_H_ #define THUMBNAILPIXMAPCACHE_H_ -#include "NonCopyable.h" -#include "ref_countable.h" -#include "ThumbnailLoadResult.h" -#include "AbstractCommand.h" #include #include +#include "AbstractCommand.h" +#include "NonCopyable.h" +#include "ThumbnailLoadResult.h" +#include "ref_countable.h" class ImageId; class QImage; @@ -33,122 +33,122 @@ class QString; class QSize; class ThumbnailPixmapCache : public ref_countable { - DECLARE_NON_COPYABLE(ThumbnailPixmapCache) - -public: - enum Status { LOADED, LOAD_FAILED, QUEUED }; - - typedef AbstractCommand CompletionHandler; - - /** - * \brief Constructor. To be called from the GUI thread only. - * - * \param thumb_dir The directory to store thumbnails in. If the - * provided directory doesn't exist, it will be created. - * \param max_size The maximum width and height for thumbnails. - * The actual thumbnail size is going to depend on its aspect - * ratio, but it won't exceed the provided maximum. - * \param max_cached_pixmaps The maximum number of pixmaps to store - * in memory. - * \param expiration_threshold Requests are served from newest to - * oldest ones. If a request is still not served after a certain - * number of newer requests have been served, that request is - * expired. \p expiration_threshold specifies the exact number - * of requests that cause older requests to expire. - * - * \see ThumbnailLoadResult::REQUEST_EXPIRED - */ - ThumbnailPixmapCache(const QString& thumb_dir, - const QSize& max_size, - int max_cached_pixmaps, - int expiration_threshold); - - /** - * \brief Destructor. To be called from the GUI thread only. - */ - ~ThumbnailPixmapCache() override; - - void setThumbDir(const QString& thumb_dir); - - /** - * \brief Take the pixmap from cache, if it's there. - * - * If it's not, LOAD_FAILED will be returned. - * - * \note This function is to be called from the GUI thread only. - */ - Status loadFromCache(const ImageId& image_id, QPixmap& pixmap); - - /** - * \brief Take the pixmap from cache or from disk, blocking if necessary. - * - * \note This function is to be called from the GUI thread only. - */ - Status loadNow(const ImageId& image_id, QPixmap& pixmap); - - /** - * \brief Take the pixmap from cache or schedule a load request. - * - * If the pixmap is in cache, return it immediately. Otherwise, - * schedule its loading in background. Once the load request - * has been processed, the provided \p call_handler will be called. - * - * \note This function is to be called from the GUI thread only. - * - * \param image_id The identifier of the full size image and its thumbnail. - * \param[out] pixmap If the pixmap is cached, store it here. - * \param completion_handler A functor that will be called on request - * completion. The best way to construct such a functor would be: - * \code - * class X : public boost::signals::trackable - * { - * public: - * void handleCompletion(const ThumbnailLoadResult& result); - * }; - * - * X x; - * cache->loadRequest(image_id, pixmap, boost::bind(&X::handleCompletion, x, _1)); - * \endcode - * Note that deriving X from boost::signals::trackable (with public inheritance) - * allows to safely delete the x object without worrying about callbacks - * it may receive in the future. Keep in mind however, that deleting - * x is only safe when done from the GUI thread. Another thing to - * keep in mind is that only boost::bind() can handle trackable binds. - * Other methods, for example boost::lambda::bind() can't do that. - */ - Status loadRequest(const ImageId& image_id, - QPixmap& pixmap, - const std::weak_ptr& completion_handler); - - /** - * \brief If no thumbnail exists for this image, create it. - * - * Using this function is optional. It just presents an optimization - * opportunity. Suppose you have the full size image already loaded, - * and want to avoid a second load when its thumbnail is requested. - * - * \param image_id The identifier of the full size image and its thumbnail. - * \param image The full-size image. - * - * \note This function may be called from any thread, even concurrently. - */ - void ensureThumbnailExists(const ImageId& image_id, const QImage& image); - - /** - * \brief Re-create and replace the existing thumnail. - * - * \param image_id The identifier of the full size image and its thumbnail. - * \param image The full-size image or a thumbnail. - * - * \note This function may be called from any thread, even concurrently. - */ - void recreateThumbnail(const ImageId& image_id, const QImage& image); - -private: - class Item; - class Impl; - - std::unique_ptr m_ptrImpl; + DECLARE_NON_COPYABLE(ThumbnailPixmapCache) + + public: + enum Status { LOADED, LOAD_FAILED, QUEUED }; + + typedef AbstractCommand CompletionHandler; + + /** + * \brief Constructor. To be called from the GUI thread only. + * + * \param thumb_dir The directory to store thumbnails in. If the + * provided directory doesn't exist, it will be created. + * \param max_size The maximum width and height for thumbnails. + * The actual thumbnail size is going to depend on its aspect + * ratio, but it won't exceed the provided maximum. + * \param max_cached_pixmaps The maximum number of pixmaps to store + * in memory. + * \param expiration_threshold Requests are served from newest to + * oldest ones. If a request is still not served after a certain + * number of newer requests have been served, that request is + * expired. \p expiration_threshold specifies the exact number + * of requests that cause older requests to expire. + * + * \see ThumbnailLoadResult::REQUEST_EXPIRED + */ + ThumbnailPixmapCache(const QString& thumb_dir, + const QSize& max_size, + int max_cached_pixmaps, + int expiration_threshold); + + /** + * \brief Destructor. To be called from the GUI thread only. + */ + ~ThumbnailPixmapCache() override; + + void setThumbDir(const QString& thumb_dir); + + /** + * \brief Take the pixmap from cache, if it's there. + * + * If it's not, LOAD_FAILED will be returned. + * + * \note This function is to be called from the GUI thread only. + */ + Status loadFromCache(const ImageId& image_id, QPixmap& pixmap); + + /** + * \brief Take the pixmap from cache or from disk, blocking if necessary. + * + * \note This function is to be called from the GUI thread only. + */ + Status loadNow(const ImageId& image_id, QPixmap& pixmap); + + /** + * \brief Take the pixmap from cache or schedule a load request. + * + * If the pixmap is in cache, return it immediately. Otherwise, + * schedule its loading in background. Once the load request + * has been processed, the provided \p call_handler will be called. + * + * \note This function is to be called from the GUI thread only. + * + * \param image_id The identifier of the full size image and its thumbnail. + * \param[out] pixmap If the pixmap is cached, store it here. + * \param completion_handler A functor that will be called on request + * completion. The best way to construct such a functor would be: + * \code + * class X : public boost::signals::trackable + * { + * public: + * void handleCompletion(const ThumbnailLoadResult& result); + * }; + * + * X x; + * cache->loadRequest(image_id, pixmap, boost::bind(&X::handleCompletion, x, _1)); + * \endcode + * Note that deriving X from boost::signals::trackable (with public inheritance) + * allows to safely delete the x object without worrying about callbacks + * it may receive in the future. Keep in mind however, that deleting + * x is only safe when done from the GUI thread. Another thing to + * keep in mind is that only boost::bind() can handle trackable binds. + * Other methods, for example boost::lambda::bind() can't do that. + */ + Status loadRequest(const ImageId& image_id, + QPixmap& pixmap, + const std::weak_ptr& completion_handler); + + /** + * \brief If no thumbnail exists for this image, create it. + * + * Using this function is optional. It just presents an optimization + * opportunity. Suppose you have the full size image already loaded, + * and want to avoid a second load when its thumbnail is requested. + * + * \param image_id The identifier of the full size image and its thumbnail. + * \param image The full-size image. + * + * \note This function may be called from any thread, even concurrently. + */ + void ensureThumbnailExists(const ImageId& image_id, const QImage& image); + + /** + * \brief Re-create and replace the existing thumnail. + * + * \param image_id The identifier of the full size image and its thumbnail. + * \param image The full-size image or a thumbnail. + * + * \note This function may be called from any thread, even concurrently. + */ + void recreateThumbnail(const ImageId& image_id, const QImage& image); + + private: + class Item; + class Impl; + + std::unique_ptr m_impl; }; diff --git a/ThumbnailSequence.cpp b/ThumbnailSequence.cpp index 7b3e79464..4f741b431 100644 --- a/ThumbnailSequence.cpp +++ b/ThumbnailSequence.cpp @@ -17,1372 +17,1356 @@ */ #include "ThumbnailSequence.h" -#include "ThumbnailFactory.h" -#include "IncompleteThumbnail.h" -#include "PageSequence.h" -#include "ColorSchemeManager.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ColorSchemeManager.h" +#include "IncompleteThumbnail.h" +#include "PageSequence.h" +#include "ThumbnailFactory.h" using namespace ::boost::multi_index; using namespace ::boost::lambda; class ThumbnailSequence::Item { -public: - Item(const PageInfo& page_info, CompositeItem* comp_item); + public: + Item(const PageInfo& page_info, CompositeItem* comp_item); - const PageId& pageId() const { - return pageInfo.id(); - } + const PageId& pageId() const { return pageInfo.id(); } - bool isSelected() const { - return m_isSelected; - } + bool isSelected() const { return m_isSelected; } - bool isSelectionLeader() const { - return m_isSelectionLeader; - } + bool isSelectionLeader() const { return m_isSelectionLeader; } - void setSelected(bool selected) const; + void setSelected(bool selected) const; - void setSelectionLeader(bool selection_leader) const; + void setSelectionLeader(bool selection_leader) const; - PageInfo pageInfo; - mutable CompositeItem* composite; - mutable bool incompleteThumbnail; + PageInfo pageInfo; + mutable CompositeItem* composite; + mutable bool incompleteThumbnail; -private: - mutable bool m_isSelected; - mutable bool m_isSelectionLeader; + private: + mutable bool m_isSelected; + mutable bool m_isSelectionLeader; }; class ThumbnailSequence::GraphicsScene : public QGraphicsScene { -public: - typedef boost::function ContextMenuEventCallback; + public: + typedef boost::function ContextMenuEventCallback; - void setContextMenuEventCallback(ContextMenuEventCallback callback) { - m_contextMenuEventCallback = callback; - } + void setContextMenuEventCallback(ContextMenuEventCallback callback) { m_contextMenuEventCallback = callback; } -protected: - virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { - QGraphicsScene::contextMenuEvent(event); + protected: + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { + QGraphicsScene::contextMenuEvent(event); - if (!event->isAccepted() && m_contextMenuEventCallback) { - m_contextMenuEventCallback(event); - } + if (!event->isAccepted() && m_contextMenuEventCallback) { + m_contextMenuEventCallback(event); } + } -private: - ContextMenuEventCallback m_contextMenuEventCallback; + private: + ContextMenuEventCallback m_contextMenuEventCallback; }; class ThumbnailSequence::Impl { -public: - Impl(ThumbnailSequence& owner, const QSizeF& max_logical_thumb_size); + public: + Impl(ThumbnailSequence& owner, const QSizeF& max_logical_thumb_size); + + ~Impl(); + + void setThumbnailFactory(intrusive_ptr factory); + + void attachView(QGraphicsView* view); + + void reset(const PageSequence& pages, + const SelectionAction selection_action, + intrusive_ptr provider); + + intrusive_ptr pageOrderProvider() const; - ~Impl(); + PageSequence toPageSequence() const; - void setThumbnailFactory(intrusive_ptr factory); + void invalidateThumbnail(const PageId& page_id); - void attachView(QGraphicsView* view); + void invalidateThumbnail(const PageInfo& page_info); - void reset(const PageSequence& pages, - const SelectionAction selection_action, - intrusive_ptr provider); + void invalidateAllThumbnails(); - intrusive_ptr pageOrderProvider() const; + bool setSelection(const PageId& page_id, SelectionAction selection_action); - PageSequence toPageSequence() const; + PageInfo selectionLeader() const; - void invalidateThumbnail(const PageId& page_id); + PageInfo prevPage(const PageId& page_id) const; - void invalidateThumbnail(const PageInfo& page_info); + PageInfo nextPage(const PageId& page_id) const; - void invalidateAllThumbnails(); + PageInfo prevSelectedPage(const PageId& reference_page) const; - bool setSelection(const PageId& page_id); + PageInfo nextSelectedPage(const PageId& reference_page) const; - PageInfo selectionLeader() const; + PageInfo firstPage() const; - PageInfo prevPage(const PageId& page_id) const; + PageInfo lastPage() const; - PageInfo nextPage(const PageId& page_id) const; + void insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image); - PageInfo firstPage() const; + void removePages(const std::set& pages); - PageInfo lastPage() const; + QRectF selectionLeaderSceneRect() const; - void insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image); + std::set selectedItems() const; - void removePages(const std::set& pages); + std::vector selectedRanges() const; - QRectF selectionLeaderSceneRect() const; + void contextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); - std::set selectedItems() const; + void itemSelectedByUser(CompositeItem* item, Qt::KeyboardModifiers modifiers); - std::vector selectedRanges() const; + const QSizeF& getMaxLogicalThumbSize() const; - void contextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); + void setMaxLogicalThumbSize(const QSizeF& size); - void itemSelectedByUser(CompositeItem* item, Qt::KeyboardModifiers modifiers); + private: + class ItemsByIdTag; + class ItemsInOrderTag; + class SelectedThenUnselectedTag; -private: - class ItemsByIdTag; - class ItemsInOrderTag; + typedef multi_index_container< + Item, + indexed_by, const_mem_fun>, + sequenced>, + sequenced>>> + Container; - class SelectedThenUnselectedTag; + typedef Container::index::type ItemsById; + typedef Container::index::type ItemsInOrder; + typedef Container::index::type SelectedThenUnselected; - typedef multi_index_container< - Item, - indexed_by, const_mem_fun>, - sequenced>, - sequenced>>> - Container; + void invalidateThumbnailImpl(ItemsById::iterator id_it); - typedef Container::index::type ItemsById; - typedef Container::index::type ItemsInOrder; - typedef Container::index::type SelectedThenUnselected; + void sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt); - void invalidateThumbnailImpl(ItemsById::iterator id_it); + void selectItemNoModifiers(const ItemsById::iterator& it); - void sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt); + void selectItemWithControl(const ItemsById::iterator& it); - void selectItemNoModifiers(const ItemsById::iterator& it); + void selectItemWithShift(const ItemsById::iterator& it); - void selectItemWithControl(const ItemsById::iterator& it); + bool multipleItemsSelected() const; - void selectItemWithShift(const ItemsById::iterator& it); + void moveToSelected(const Item* item); - bool multipleItemsSelected() const; + void moveToUnselected(const Item* item); - void moveToSelected(const Item* item); + void clear(); - void moveToUnselected(const Item* item); + void clearSelection(); - void clear(); + /** + * Calculates the insertion position for an item with the given PageId + * based on m_orderProvider. + * + * \param begin Beginning of the interval to consider. + * \param end End of the interval to consider. + * \param page_id The item to find insertion position for. + * \param page_incomplete Whether the page is represented by IncompleteThumbnail. + * \param hint The place to start the search. Must be within [begin, end]. + * \param dist_from_hint If provided, the distance from \p hint + * to the calculated insertion position will be written there. + * For example, \p dist_from_hint == -2 would indicate that the + * insertion position is two elements to the left of \p hint. + */ + ItemsInOrder::iterator itemInsertPosition(ItemsInOrder::iterator begin, + ItemsInOrder::iterator end, + const PageId& page_id, + bool page_incomplete, + ItemsInOrder::iterator hint, + int* dist_from_hint = nullptr); - void clearSelection(); + std::unique_ptr getThumbnail(const PageInfo& page_info); - /** - * Calculates the insertion position for an item with the given PageId - * based on m_ptrOrderProvider. - * - * \param begin Beginning of the interval to consider. - * \param end End of the interval to consider. - * \param page_id The item to find insertion position for. - * \param page_incomplete Whether the page is represented by IncompleteThumbnail. - * \param hint The place to start the search. Must be within [begin, end]. - * \param dist_from_hint If provided, the distance from \p hint - * to the calculated insertion position will be written there. - * For example, \p dist_from_hint == -2 would indicate that the - * insertion position is two elements to the left of \p hint. - */ - ItemsInOrder::iterator itemInsertPosition(ItemsInOrder::iterator begin, - ItemsInOrder::iterator end, - const PageId& page_id, - bool page_incomplete, - ItemsInOrder::iterator hint, - int* dist_from_hint = 0); + std::unique_ptr getLabelGroup(const PageInfo& page_info); - std::unique_ptr getThumbnail(const PageInfo& page_info); + std::unique_ptr getCompositeItem(const Item* item, const PageInfo& info); - std::unique_ptr getLabelGroup(const PageInfo& page_info); + void commitSceneRect(); - std::unique_ptr getCompositeItem(const Item* item, const PageInfo& info); + int getGraphicsViewWidth() const; - void commitSceneRect(); + void updateSceneItemsPos(); - static const int SPACING = 0; - ThumbnailSequence& m_rOwner; - QSizeF m_maxLogicalThumbSize; - Container m_items; - ItemsById& m_itemsById; - ItemsInOrder& m_itemsInOrder; + static const int SPACING = 3; + ThumbnailSequence& m_owner; + QSizeF m_maxLogicalThumbSize; + Container m_items; + ItemsById& m_itemsById; + ItemsInOrder& m_itemsInOrder; - /** - * As the name implies, selected items go first here (in no particular order), - * then go unselected items (also in no particular order). - */ - SelectedThenUnselected& m_selectedThenUnselected; + /** + * As the name implies, selected items go first here (in no particular order), + * then go unselected items (also in no particular order). + */ + SelectedThenUnselected& m_selectedThenUnselected; - const Item* m_pSelectionLeader; - intrusive_ptr m_ptrFactory; - intrusive_ptr m_ptrOrderProvider; - GraphicsScene m_graphicsScene; - QRectF m_sceneRect; + const Item* m_selectionLeader; + intrusive_ptr m_factory; + intrusive_ptr m_orderProvider; + GraphicsScene m_graphicsScene; + QRectF m_sceneRect; }; class ThumbnailSequence::PlaceholderThumb : public QGraphicsItem { -public: - PlaceholderThumb(const QSizeF& max_size); + public: + PlaceholderThumb(const QSizeF& max_size); - virtual QRectF boundingRect() const; + virtual QRectF boundingRect() const; - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); -private: - static QPainterPath m_sCachedPath; - QSizeF m_maxSize; + private: + static QPainterPath m_sCachedPath; + QSizeF m_maxSize; }; class ThumbnailSequence::LabelGroup : public QGraphicsItemGroup { -public: - LabelGroup(std::unique_ptr normal_label, - std::unique_ptr bold_label, - std::unique_ptr pixmap = nullptr); + public: + LabelGroup(std::unique_ptr normal_label, + std::unique_ptr bold_label, + std::unique_ptr pixmap = nullptr); - void updateAppearence(bool selected, bool selection_leader); + void updateAppearence(bool selected, bool selection_leader); -private: - QGraphicsSimpleTextItem* m_pNormalLabel; - QGraphicsSimpleTextItem* m_pBoldLabel; + private: + QGraphicsSimpleTextItem* m_normalLabel; + QGraphicsSimpleTextItem* m_boldLabel; }; class ThumbnailSequence::CompositeItem : public QGraphicsItemGroup { -public: - CompositeItem(ThumbnailSequence::Impl& owner, - std::unique_ptr thumbnail, - std::unique_ptr label_group); + public: + CompositeItem(ThumbnailSequence::Impl& owner, + std::unique_ptr thumbnail, + std::unique_ptr label_group); - void setItem(const Item* item) { - m_pItem = item; - } + void setItem(const Item* item) { m_item = item; } - const Item* item() { - return m_pItem; - } + const Item* item() { return m_item; } - bool incompleteThumbnail() const; + bool incompleteThumbnail() const; - void updateSceneRect(QRectF& scene_rect); + void updateSceneRect(QRectF& scene_rect); - void updateAppearence(bool selected, bool selection_leader); + void updateAppearence(bool selected, bool selection_leader); - virtual QRectF boundingRect() const; + virtual QRectF boundingRect() const; - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); -protected: - virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); + protected: + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); - virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); -private: - // We no longer use QGraphicsView's selection mechanism, so we - // shadow isSelected() and setSelected() with unimplemented private - // functions. Just to be safe. - bool isSelected() const; + private: + // We no longer use QGraphicsView's selection mechanism, so we + // shadow isSelected() and setSelected() with unimplemented private + // functions. Just to be safe. + bool isSelected() const; - void setSelected(bool selected); + void setSelected(bool selected); - ThumbnailSequence::Impl& m_rOwner; - const ThumbnailSequence::Item* m_pItem; - QGraphicsItem* m_pThumb; - LabelGroup* m_pLabelGroup; + ThumbnailSequence::Impl& m_owner; + const ThumbnailSequence::Item* m_item; + QGraphicsItem* m_thumb; + LabelGroup* m_labelGroup; }; /*============================= ThumbnailSequence ===========================*/ ThumbnailSequence::ThumbnailSequence(const QSizeF& max_logical_thumb_size) - : m_ptrImpl(new Impl(*this, max_logical_thumb_size)) { -} + : m_impl(new Impl(*this, max_logical_thumb_size)) {} -ThumbnailSequence::~ThumbnailSequence() { -} +ThumbnailSequence::~ThumbnailSequence() {} void ThumbnailSequence::setThumbnailFactory(intrusive_ptr factory) { - m_ptrImpl->setThumbnailFactory(std::move(factory)); + m_impl->setThumbnailFactory(std::move(factory)); } void ThumbnailSequence::attachView(QGraphicsView* const view) { - m_ptrImpl->attachView(view); + m_impl->attachView(view); } void ThumbnailSequence::reset(const PageSequence& pages, const SelectionAction selection_action, intrusive_ptr order_provider) { - m_ptrImpl->reset(pages, selection_action, std::move(order_provider)); + m_impl->reset(pages, selection_action, std::move(order_provider)); } intrusive_ptr ThumbnailSequence::pageOrderProvider() const { - return m_ptrImpl->pageOrderProvider(); + return m_impl->pageOrderProvider(); } PageSequence ThumbnailSequence::toPageSequence() const { - return m_ptrImpl->toPageSequence(); + return m_impl->toPageSequence(); } void ThumbnailSequence::invalidateThumbnail(const PageId& page_id) { - m_ptrImpl->invalidateThumbnail(page_id); + m_impl->invalidateThumbnail(page_id); } void ThumbnailSequence::invalidateThumbnail(const PageInfo& page_info) { - m_ptrImpl->invalidateThumbnail(page_info); + m_impl->invalidateThumbnail(page_info); } void ThumbnailSequence::invalidateAllThumbnails() { - m_ptrImpl->invalidateAllThumbnails(); + m_impl->invalidateAllThumbnails(); } -bool ThumbnailSequence::setSelection(const PageId& page_id) { - return m_ptrImpl->setSelection(page_id); +bool ThumbnailSequence::setSelection(const PageId& page_id, const SelectionAction selection_action) { + return m_impl->setSelection(page_id, selection_action); } PageInfo ThumbnailSequence::selectionLeader() const { - return m_ptrImpl->selectionLeader(); + return m_impl->selectionLeader(); } PageInfo ThumbnailSequence::prevPage(const PageId& reference_page) const { - return m_ptrImpl->prevPage(reference_page); + return m_impl->prevPage(reference_page); } PageInfo ThumbnailSequence::nextPage(const PageId& reference_page) const { - return m_ptrImpl->nextPage(reference_page); + return m_impl->nextPage(reference_page); +} + +PageInfo ThumbnailSequence::prevSelectedPage(const PageId& reference_page) const { + return m_impl->prevSelectedPage(reference_page); +} + +PageInfo ThumbnailSequence::nextSelectedPage(const PageId& reference_page) const { + return m_impl->nextSelectedPage(reference_page); } PageInfo ThumbnailSequence::firstPage() const { - return m_ptrImpl->firstPage(); + return m_impl->firstPage(); } PageInfo ThumbnailSequence::lastPage() const { - return m_ptrImpl->lastPage(); + return m_impl->lastPage(); } void ThumbnailSequence::insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image) { - m_ptrImpl->insert(new_page, before_or_after, image); + m_impl->insert(new_page, before_or_after, image); } void ThumbnailSequence::removePages(const std::set& pages) { - m_ptrImpl->removePages(pages); + m_impl->removePages(pages); } QRectF ThumbnailSequence::selectionLeaderSceneRect() const { - return m_ptrImpl->selectionLeaderSceneRect(); + return m_impl->selectionLeaderSceneRect(); } std::set ThumbnailSequence::selectedItems() const { - return m_ptrImpl->selectedItems(); + return m_impl->selectedItems(); } std::vector ThumbnailSequence::selectedRanges() const { - return m_ptrImpl->selectedRanges(); + return m_impl->selectedRanges(); } void ThumbnailSequence::emitNewSelectionLeader(const PageInfo& page_info, const CompositeItem* composite, const SelectionFlags flags) { - const QRectF thumb_rect(composite->mapToScene(composite->boundingRect()).boundingRect()); - emit newSelectionLeader(page_info, thumb_rect, flags); + const QRectF thumb_rect(composite->mapToScene(composite->boundingRect()).boundingRect()); + emit newSelectionLeader(page_info, thumb_rect, flags); +} + +const QSizeF& ThumbnailSequence::getMaxLogicalThumbSize() const { + return m_impl->getMaxLogicalThumbSize(); +} + +void ThumbnailSequence::setMaxLogicalThumbSize(const QSizeF& size) { + m_impl->setMaxLogicalThumbSize(size); } /*======================== ThumbnailSequence::Impl ==========================*/ ThumbnailSequence::Impl::Impl(ThumbnailSequence& owner, const QSizeF& max_logical_thumb_size) - : m_rOwner(owner), - m_maxLogicalThumbSize(max_logical_thumb_size), - m_items(), - m_itemsById(m_items.get()), - m_itemsInOrder(m_items.get()), - m_selectedThenUnselected(m_items.get()), - m_pSelectionLeader(0) { - m_graphicsScene.setContextMenuEventCallback( - [&](QGraphicsSceneContextMenuEvent* evt) { this->sceneContextMenuEvent(evt); }); + : m_owner(owner), + m_maxLogicalThumbSize(max_logical_thumb_size), + m_items(), + m_itemsById(m_items.get()), + m_itemsInOrder(m_items.get()), + m_selectedThenUnselected(m_items.get()), + m_selectionLeader(nullptr) { + m_graphicsScene.setContextMenuEventCallback( + [&](QGraphicsSceneContextMenuEvent* evt) { this->sceneContextMenuEvent(evt); }); } -ThumbnailSequence::Impl::~Impl() { -} +ThumbnailSequence::Impl::~Impl() {} void ThumbnailSequence::Impl::setThumbnailFactory(intrusive_ptr factory) { - m_ptrFactory = std::move(factory); + m_factory = std::move(factory); } void ThumbnailSequence::Impl::attachView(QGraphicsView* const view) { - view->setScene(&m_graphicsScene); + view->setScene(&m_graphicsScene); } void ThumbnailSequence::Impl::reset(const PageSequence& pages, const SelectionAction selection_action, intrusive_ptr order_provider) { - m_ptrOrderProvider = std::move(order_provider); - - std::set selected; - PageInfo selection_leader; - - if (selection_action == KEEP_SELECTION) { - selectedItems().swap(selected); - if (m_pSelectionLeader) { - selection_leader = m_pSelectionLeader->pageInfo; - } - } + m_orderProvider = std::move(order_provider); - clear(); // Also clears the selection. + std::set selected; + PageInfo selection_leader; - if (pages.numPages() == 0) { - return; + if (selection_action == KEEP_SELECTION) { + selectedItems().swap(selected); + if (m_selectionLeader) { + selection_leader = m_selectionLeader->pageInfo; } + } - const Item* some_selected_item = 0; - - for (const PageInfo& page_info : pages) { - std::unique_ptr composite(getCompositeItem(0, page_info)); - m_itemsInOrder.push_back(Item(page_info, composite.release())); - const Item* item = &m_itemsInOrder.back(); - item->composite->setItem(item); + clear(); // Also clears the selection. - if (selected.find(page_info.id()) != selected.end()) { - item->setSelected(true); - moveToSelected(item); - some_selected_item = item; - } - if (page_info.id() == selection_leader.id()) { - m_pSelectionLeader = item; - } - } + if (pages.numPages() == 0) { + return; + } - invalidateAllThumbnails(); + const Item* some_selected_item = nullptr; + for (const PageInfo& page_info : pages) { + std::unique_ptr composite(getCompositeItem(0, page_info)); + m_itemsInOrder.push_back(Item(page_info, composite.release())); + const Item* item = &m_itemsInOrder.back(); + item->composite->setItem(item); - if (!m_pSelectionLeader) { - if (some_selected_item) { - m_pSelectionLeader = some_selected_item; - } - } + const ImageId& image_id = page_info.id().imageId(); - if (m_pSelectionLeader) { - m_pSelectionLeader->setSelectionLeader(true); - m_rOwner.emitNewSelectionLeader(selection_leader, m_pSelectionLeader->composite, DEFAULT_SELECTION_FLAGS); - } + bool item_found = (selected.find(page_info.id()) != selected.end()); + if (!item_found) { + switch (page_info.id().subPage()) { + case PageId::LEFT_PAGE: + case PageId::RIGHT_PAGE: + item_found = (selected.find(PageId(image_id, PageId::SINGLE_PAGE)) != selected.end()); + break; + case PageId::SINGLE_PAGE: + item_found = (selected.find(PageId(image_id, PageId::LEFT_PAGE)) != selected.end()) + || (selected.find(PageId(image_id, PageId::RIGHT_PAGE)) != selected.end()); + break; + } + } + if (item_found) { + item->setSelected(true); + moveToSelected(item); + some_selected_item = item; + + if ((page_info.id() == selection_leader.id()) + || (!m_selectionLeader && (image_id == selection_leader.id().imageId()))) { + m_selectionLeader = item; + } + } + } + + invalidateAllThumbnails(); + + if (!m_selectionLeader) { + if (some_selected_item) { + m_selectionLeader = some_selected_item; + } + } + if (m_selectionLeader) { + m_selectionLeader->setSelectionLeader(true); + m_owner.emitNewSelectionLeader(selection_leader, m_selectionLeader->composite, DEFAULT_SELECTION_FLAGS); + } } // ThumbnailSequence::Impl::reset intrusive_ptr ThumbnailSequence::Impl::pageOrderProvider() const { - return m_ptrOrderProvider; + return m_orderProvider; } PageSequence ThumbnailSequence::Impl::toPageSequence() const { - PageSequence pages; + PageSequence pages; - for (const Item& item : m_itemsInOrder) { - pages.append(item.pageInfo); - } + for (const Item& item : m_itemsInOrder) { + pages.append(item.pageInfo); + } - return pages; + return pages; } void ThumbnailSequence::Impl::invalidateThumbnail(const PageId& page_id) { - const ItemsById::iterator id_it(m_itemsById.find(page_id)); - if (id_it != m_itemsById.end()) { - invalidateThumbnailImpl(id_it); - } + const ItemsById::iterator id_it(m_itemsById.find(page_id)); + if (id_it != m_itemsById.end()) { + invalidateThumbnailImpl(id_it); + } } void ThumbnailSequence::Impl::invalidateThumbnail(const PageInfo& page_info) { - const ItemsById::iterator id_it(m_itemsById.find(page_info.id())); - if (id_it != m_itemsById.end()) { - m_itemsById.modify(id_it, [&page_info](Item& item) { item.pageInfo = page_info; }); - invalidateThumbnailImpl(id_it); - } -} - -void ThumbnailSequence::Impl::invalidateThumbnailImpl(const ItemsById::iterator id_it) { - std::unique_ptr composite(getCompositeItem(&*id_it, id_it->pageInfo)); - - CompositeItem* const new_composite = composite.get(); - CompositeItem* const old_composite = id_it->composite; - const QSizeF old_size(old_composite->boundingRect().size()); - const QSizeF new_size(new_composite->boundingRect().size()); - const QPointF old_pos(new_composite->pos()); - - new_composite->updateAppearence(id_it->isSelected(), id_it->isSelectionLeader()); - - m_graphicsScene.addItem(composite.release()); - id_it->composite = new_composite; - id_it->incompleteThumbnail = new_composite->incompleteThumbnail(); - delete old_composite; - - ItemsInOrder::iterator after_old(m_items.project(id_it)); - // Notice after_old++ below. - - // Move our item to the beginning of m_itemsInOrder, to make it out of range - // we are going to pass to itemInsertPosition(). - m_itemsInOrder.relocate(m_itemsInOrder.begin(), after_old++); - - int dist = 0; - const ItemsInOrder::iterator after_new(itemInsertPosition(++m_itemsInOrder.begin(), m_itemsInOrder.end(), - id_it->pageInfo.id(), id_it->incompleteThumbnail, - after_old, &dist)); - - // Move our item to its intended position. - m_itemsInOrder.relocate(after_new, m_itemsInOrder.begin()); - - - // Now let's reposition the items on the scene. - - ItemsInOrder::iterator ord_it, ord_end; - - // The range of [ord_it, ord_end) is supposed to contain all items - // between the old and new positions of our item, with the new - // position in range. - - if (dist <= 0) { // New position is before or equals to the old one. - ord_it = after_new; - --ord_it; // Include new item position in the range. - ord_end = after_old; - } else { // New position is after the old one. - ord_it = after_old; - ord_end = after_new; - } - - int view_width = 0; - if (!m_graphicsScene.views().isEmpty()) { - QGraphicsView* gv = m_graphicsScene.views().first(); - view_width = gv->width(); - view_width -= gv->style()->pixelMetric(QStyle::PM_ScrollBarExtent); - if (gv->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, gv)) { - view_width -= gv->frameWidth() * 2; + const ItemsById::iterator id_it(m_itemsById.find(page_info.id())); + if (id_it != m_itemsById.end()) { + m_itemsById.modify(id_it, [&page_info](Item& item) { item.pageInfo = page_info; }); + invalidateThumbnailImpl(id_it); + } +} + +int ThumbnailSequence::Impl::getGraphicsViewWidth() const { + int view_width = 0; + if (!m_graphicsScene.views().isEmpty()) { + QGraphicsView* gv = m_graphicsScene.views().first(); + view_width = gv->width(); + view_width -= gv->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + if (gv->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, gv)) { + view_width -= gv->frameWidth() * 2; + } + } + + return view_width; +} + +void ThumbnailSequence::Impl::updateSceneItemsPos() { + m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); + + const int view_width = getGraphicsViewWidth(); + assert(view_width > 0); + double yoffset = SPACING; + + ItemsInOrder::iterator ord_it = m_itemsInOrder.begin(); + const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); + + while (ord_it != ord_end) { + int items_in_row = 0; + double sum_item_widths = 0; + double xoffset = SPACING; + + // Determine how many items can fit into the current row. + for (ItemsInOrder::iterator row_it = ord_it; row_it != ord_end; ++row_it) { + const double item_width = row_it->composite->boundingRect().width(); + xoffset += item_width + SPACING; + if (xoffset > view_width) { + if (items_in_row == 0) { + // At least one page must be in a row. + items_in_row = 1; + sum_item_widths = item_width; } + break; + } + items_in_row++; + sum_item_widths += item_width; } - // look for a beginning of a row - double yoffset = -1; // undefined value - if (ord_it != m_itemsInOrder.begin()) { - // can't use ord_it->composite->pos() here - ItemsInOrder::iterator it(ord_it); - --it; - if (it->composite->pos().x() + it->composite->boundingRect().width() + SPACING - + ord_it->composite->boundingRect().width() - <= view_width) { - // not first in a row - yoffset = it->composite->pos().y(); // take ordinate of any prev page - ord_it = it; - if (it != m_itemsInOrder.begin()) { - while ((--it)->composite->pos().y() == yoffset) { - ord_it = it; - if (it == m_itemsInOrder.begin()) { - break; - } - } - } - } - } - // now ord_it is at beginning ot a row - if (yoffset < 0) { - // but it's ordinate is unknown as it's singe page or was first page in row - if (ord_it == m_itemsInOrder.begin()) { - // it's a first row - yoffset = SPACING; - } else { - // there are rows before and we'll find max height of prev one - ItemsInOrder::iterator it(ord_it); - --it; // we at end of the prev row - double next_yoffset = 0; - double row_y = it->composite->pos().y(); - do { - next_yoffset = std::max(it->composite->pos().y() + it->composite->boundingRect().height() + SPACING, - next_yoffset); - } while (it != m_itemsInOrder.begin() && (--it)->composite->pos().y() == row_y); - yoffset = next_yoffset; - } + // Split width exceeding spacing between margins of the pages in the current row. + const double adj_spacing = std::ceil((view_width - sum_item_widths) / (items_in_row + 1)); + xoffset = adj_spacing; + double next_yoffset = 0; + for (; items_in_row > 0; --items_in_row, ++ord_it) { + CompositeItem* composite = ord_it->composite; + composite->setPos(xoffset, yoffset); + composite->updateSceneRect(m_sceneRect); + xoffset += std::ceil(composite->boundingRect().width() + adj_spacing); + next_yoffset = std::max(composite->boundingRect().height() + SPACING, next_yoffset); } - ord_end = m_itemsInOrder.end(); - while (ord_it != ord_end) { - int items_in_row = 0; - double sum_item_widths = 0; - double xoffset = SPACING; - for (ItemsInOrder::iterator row_it = ord_it; row_it != ord_end; ++row_it) { - const double item_width = row_it->composite->boundingRect().width(); - xoffset += item_width; - if (xoffset > view_width) { - if (items_in_row == 0) { - items_in_row = 1; // at least one page must be in a row - sum_item_widths = item_width; - } - break; - } - items_in_row++; - sum_item_widths += item_width; - xoffset += SPACING; - } + if (ord_it != ord_end) { + yoffset += next_yoffset; + } + } - // split exceding width between margins of pages in a row - xoffset = SPACING; - double next_yoffset = 0; - for (; items_in_row > 0; --items_in_row, ++ord_it) { - CompositeItem* composite = ord_it->composite; - composite->setPos(xoffset, yoffset); - xoffset += composite->boundingRect().width() + SPACING; - next_yoffset = std::max(ord_it->composite->boundingRect().height() + SPACING, next_yoffset); - } + commitSceneRect(); +} - if (ord_it != ord_end) { - yoffset += next_yoffset; - } - } - // Update scene rect. - m_sceneRect.setTop(m_sceneRect.bottom()); - m_itemsInOrder.front().composite->updateSceneRect(m_sceneRect); - m_sceneRect.setBottom(m_sceneRect.top()); - m_itemsInOrder.back().composite->updateSceneRect(m_sceneRect); - id_it->composite->updateSceneRect(m_sceneRect); - commitSceneRect(); - // Possibly emit the newSelectionLeader() signal. - if (m_pSelectionLeader == &*id_it) { - if ((old_size != new_size) || (old_pos != id_it->composite->pos())) { - m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, REDUNDANT_SELECTION); - } - } +void ThumbnailSequence::Impl::invalidateThumbnailImpl(const ItemsById::iterator id_it) { + CompositeItem* const new_composite = getCompositeItem(&*id_it, id_it->pageInfo).release(); + CompositeItem* const old_composite = id_it->composite; + const QSizeF old_size(old_composite->boundingRect().size()); + const QSizeF new_size(new_composite->boundingRect().size()); + const QPointF old_pos(new_composite->pos()); + + id_it->composite = new_composite; + id_it->incompleteThumbnail = new_composite->incompleteThumbnail(); + delete old_composite; + + new_composite->updateAppearence(id_it->isSelected(), id_it->isSelectionLeader()); + m_graphicsScene.addItem(new_composite); + + ItemsInOrder::iterator after_old(m_items.project(id_it)); + // Notice after_old++ below. + // Move our item to the beginning of m_itemsInOrder, to make it out of range + // we are going to pass to itemInsertPosition(). + m_itemsInOrder.relocate(m_itemsInOrder.begin(), after_old++); + const ItemsInOrder::iterator after_new(itemInsertPosition( + ++m_itemsInOrder.begin(), m_itemsInOrder.end(), id_it->pageInfo.id(), id_it->incompleteThumbnail, after_old)); + // Move our item to its intended position. + m_itemsInOrder.relocate(after_new, m_itemsInOrder.begin()); + + updateSceneItemsPos(); + + // Possibly emit the newSelectionLeader() signal. + if (m_selectionLeader == &*id_it) { + if ((old_size != new_size) || (old_pos != id_it->composite->pos())) { + m_owner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, REDUNDANT_SELECTION); + } + } } // ThumbnailSequence::Impl::invalidateThumbnailImpl void ThumbnailSequence::Impl::invalidateAllThumbnails() { - // Recreate thumbnails now, whether a thumbnail is incomplete - // is taken into account when sorting. - ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); - const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); - for (; ord_it != ord_end; ++ord_it) { - CompositeItem* const old_composite = ord_it->composite; - ord_it->composite = getCompositeItem(&*ord_it, ord_it->pageInfo).release(); - ord_it->incompleteThumbnail = ord_it->composite->incompleteThumbnail(); - delete old_composite; - } - - // Sort pages in m_itemsInOrder using m_ptrOrderProvider. - if (m_ptrOrderProvider) { - m_itemsInOrder.sort([this](const Item& lhs, const Item& rhs) { - return m_ptrOrderProvider->precedes(lhs.pageId(), lhs.incompleteThumbnail, rhs.pageId(), - rhs.incompleteThumbnail); - }); - } - - m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); - - int view_width = 0; - if (!m_graphicsScene.views().isEmpty()) { - QGraphicsView* gv = m_graphicsScene.views().first(); - view_width = gv->width(); - view_width -= gv->style()->pixelMetric(QStyle::PM_ScrollBarExtent); - if (gv->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, gv)) { - view_width -= gv->frameWidth() * 2; - } - } - - double yoffset = SPACING; - ord_it = m_itemsInOrder.begin(); - while (ord_it != ord_end) { - int items_in_row = 0; - double sum_item_widths = 0; - double xoffset = SPACING; - for (ItemsInOrder::iterator row_it = ord_it; row_it != ord_end; ++row_it) { - const double item_width = row_it->composite->boundingRect().width(); - xoffset += item_width; - if (xoffset > view_width) { - if (items_in_row == 0) { - items_in_row = 1; // at least one page must be in a row - sum_item_widths = item_width; - } - break; - } - items_in_row++; - sum_item_widths += item_width; - xoffset += SPACING; - } + // Recreate thumbnails now, whether a thumbnail is incomplete + // is taken into account when sorting. + ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); + const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); + for (; ord_it != ord_end; ++ord_it) { + CompositeItem* const old_composite = ord_it->composite; + CompositeItem* const new_composite = getCompositeItem(&*ord_it, ord_it->pageInfo).release(); + + ord_it->composite = new_composite; + ord_it->incompleteThumbnail = new_composite->incompleteThumbnail(); + delete old_composite; - // split exceding width between margins of pages in a row - xoffset = SPACING; - double next_yoffset = 0; - for (; items_in_row > 0; --items_in_row, ++ord_it) { - CompositeItem* composite = ord_it->composite; - composite->setPos(xoffset, yoffset); - composite->updateSceneRect(m_sceneRect); - composite->updateAppearence(ord_it->isSelected(), ord_it->isSelectionLeader()); - m_graphicsScene.addItem(composite); - xoffset += composite->boundingRect().width() + SPACING; - next_yoffset = std::max(composite->boundingRect().height() + SPACING, next_yoffset); - } + new_composite->updateAppearence(ord_it->isSelected(), ord_it->isSelectionLeader()); + m_graphicsScene.addItem(new_composite); + } - if (ord_it != ord_end) { - yoffset += next_yoffset; - } - } + // Sort pages in m_itemsInOrder using m_orderProvider. + if (m_orderProvider) { + m_itemsInOrder.sort([this](const Item& lhs, const Item& rhs) { + return m_orderProvider->precedes(lhs.pageId(), lhs.incompleteThumbnail, rhs.pageId(), rhs.incompleteThumbnail); + }); + } - commitSceneRect(); + updateSceneItemsPos(); } // ThumbnailSequence::Impl::invalidateAllThumbnails -bool ThumbnailSequence::Impl::setSelection(const PageId& page_id) { - const ItemsById::iterator id_it(m_itemsById.find(page_id)); - if (id_it == m_itemsById.end()) { - return false; - } +bool ThumbnailSequence::Impl::setSelection(const PageId& page_id, const SelectionAction selection_action) { + const ItemsById::iterator id_it(m_itemsById.find(page_id)); + if (id_it == m_itemsById.end()) { + return false; + } - const bool was_selection_leader = (&*id_it == m_pSelectionLeader); + const bool was_selection_leader = (&*id_it == m_selectionLeader); + if (selection_action != KEEP_SELECTION) { // Clear selection from all items except the one for which // selection is requested. SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin()); while (it != m_selectedThenUnselected.end()) { - const Item& item = *it; - if (!item.isSelected()) { - break; - } + const Item& item = *it; + if (!item.isSelected()) { + break; + } - ++it; + ++it; - if (&*id_it != &item) { - item.setSelected(false); - moveToUnselected(&item); - if (m_pSelectionLeader == &item) { - m_pSelectionLeader = 0; - } + if (&*id_it != &item) { + item.setSelected(false); + moveToUnselected(&item); + if (m_selectionLeader == &item) { + m_selectionLeader = nullptr; } + } } - - if (!was_selection_leader) { - m_pSelectionLeader = &*id_it; - m_pSelectionLeader->setSelectionLeader(true); - moveToSelected(m_pSelectionLeader); + } else { + // Only reset selection leader. + if (m_selectionLeader && !was_selection_leader) { + m_selectionLeader->setSelectionLeader(false); + m_selectionLeader = nullptr; } + } - SelectionFlags flags = DEFAULT_SELECTION_FLAGS; - if (was_selection_leader) { - flags |= REDUNDANT_SELECTION; - } + if (!was_selection_leader) { + m_selectionLeader = &*id_it; + m_selectionLeader->setSelectionLeader(true); + moveToSelected(m_selectionLeader); + } + + SelectionFlags flags = DEFAULT_SELECTION_FLAGS; + if (was_selection_leader) { + flags |= REDUNDANT_SELECTION; + } - m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); + m_owner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); - return true; + return true; } // ThumbnailSequence::Impl::setSelection PageInfo ThumbnailSequence::Impl::selectionLeader() const { - if (m_pSelectionLeader) { - return m_pSelectionLeader->pageInfo; - } else { - return PageInfo(); - } + if (m_selectionLeader) { + return m_selectionLeader->pageInfo; + } else { + return PageInfo(); + } } PageInfo ThumbnailSequence::Impl::prevPage(const PageId& reference_page) const { - ItemsInOrder::iterator ord_it; + ItemsInOrder::iterator ord_it; - if (m_pSelectionLeader && (m_pSelectionLeader->pageInfo.id() == reference_page)) { - // Common case optimization. - ord_it = m_itemsInOrder.iterator_to(*m_pSelectionLeader); - } else { - ord_it = m_items.project(m_itemsById.find(reference_page)); - } + if (m_selectionLeader && (m_selectionLeader->pageInfo.id() == reference_page)) { + // Common case optimization. + ord_it = m_itemsInOrder.iterator_to(*m_selectionLeader); + } else { + ord_it = m_items.project(m_itemsById.find(reference_page)); + } - if (ord_it != m_itemsInOrder.end()) { - if (ord_it != m_itemsInOrder.begin()) { - --ord_it; + if (ord_it != m_itemsInOrder.end()) { + if (ord_it != m_itemsInOrder.begin()) { + --ord_it; - return ord_it->pageInfo; - } + return ord_it->pageInfo; } + } - return PageInfo(); + return PageInfo(); } PageInfo ThumbnailSequence::Impl::nextPage(const PageId& reference_page) const { - ItemsInOrder::iterator ord_it; + ItemsInOrder::iterator ord_it; - if (m_pSelectionLeader && (m_pSelectionLeader->pageInfo.id() == reference_page)) { - // Common case optimization. - ord_it = m_itemsInOrder.iterator_to(*m_pSelectionLeader); - } else { - ord_it = m_items.project(m_itemsById.find(reference_page)); - } + if (m_selectionLeader && (m_selectionLeader->pageInfo.id() == reference_page)) { + // Common case optimization. + ord_it = m_itemsInOrder.iterator_to(*m_selectionLeader); + } else { + ord_it = m_items.project(m_itemsById.find(reference_page)); + } + if (ord_it != m_itemsInOrder.end()) { + ++ord_it; if (ord_it != m_itemsInOrder.end()) { - ++ord_it; - if (ord_it != m_itemsInOrder.end()) { - return ord_it->pageInfo; - } + return ord_it->pageInfo; } + } - return PageInfo(); + return PageInfo(); } -PageInfo ThumbnailSequence::Impl::firstPage() const { - if (m_items.empty()) { - return PageInfo(); +PageInfo ThumbnailSequence::Impl::prevSelectedPage(const PageId& reference_page) const { + ItemsInOrder::iterator ord_it; + + if (m_selectionLeader && (m_selectionLeader->pageInfo.id() == reference_page)) { + // Common case optimization. + ord_it = m_itemsInOrder.iterator_to(*m_selectionLeader); + } else { + ord_it = m_items.project(m_itemsById.find(reference_page)); + } + + if (ord_it != m_itemsInOrder.end()) { + while (ord_it != m_itemsInOrder.begin()) { + --ord_it; + if (ord_it->isSelected()) { + return ord_it->pageInfo; + } } + } - return m_itemsInOrder.front().pageInfo; + return PageInfo(); } -PageInfo ThumbnailSequence::Impl::lastPage() const { - if (m_items.empty()) { - return PageInfo(); +PageInfo ThumbnailSequence::Impl::nextSelectedPage(const PageId& reference_page) const { + ItemsInOrder::iterator ord_it; + + if (m_selectionLeader && (m_selectionLeader->pageInfo.id() == reference_page)) { + // Common case optimization. + ord_it = m_itemsInOrder.iterator_to(*m_selectionLeader); + } else { + ord_it = m_items.project(m_itemsById.find(reference_page)); + } + + if (ord_it != m_itemsInOrder.end()) { + ++ord_it; + while (ord_it != m_itemsInOrder.end()) { + if (ord_it->isSelected()) { + return ord_it->pageInfo; + } + ++ord_it; } + } - return m_itemsInOrder.back().pageInfo; + return PageInfo(); } -void ThumbnailSequence::Impl::insert(const PageInfo& page_info, BeforeOrAfter before_or_after, const ImageId& image) { - ItemsInOrder::iterator ord_it; +PageInfo ThumbnailSequence::Impl::firstPage() const { + if (m_items.empty()) { + return PageInfo(); + } - if ((before_or_after == BEFORE) && image.isNull()) { - ord_it = m_itemsInOrder.end(); - } else { - // Note that we have to use lower_bound() rather than find() because - // we are not searching for PageId(image) exactly, which implies - // PageId::SINGLE_PAGE configuration, but rather we search for - // a page with any configuration, as long as it references the same image. - ItemsById::iterator id_it(m_itemsById.lower_bound(PageId(image))); - if ((id_it == m_itemsById.end()) || (id_it->pageInfo.imageId() != image)) { - // Reference page not found. - return; - } + return m_itemsInOrder.front().pageInfo; +} + +PageInfo ThumbnailSequence::Impl::lastPage() const { + if (m_items.empty()) { + return PageInfo(); + } - ord_it = m_items.project(id_it); + return m_itemsInOrder.back().pageInfo; +} - if (before_or_after == AFTER) { - ++ord_it; - if (!m_ptrOrderProvider) { - // Advance past not only the target page, but also its other half, if it follows. - while (ord_it != m_itemsInOrder.end() && ord_it->pageInfo.imageId() == image) { - ++ord_it; - } - } +void ThumbnailSequence::Impl::insert(const PageInfo& page_info, BeforeOrAfter before_or_after, const ImageId& image) { + ItemsInOrder::iterator ord_it; + + if ((before_or_after == BEFORE) && image.isNull()) { + ord_it = m_itemsInOrder.end(); + } else { + // Note that we have to use lower_bound() rather than find() because + // we are not searching for PageId(image) exactly, which implies + // PageId::SINGLE_PAGE configuration, but rather we search for + // a page with any configuration, as long as it references the same image. + ItemsById::iterator id_it(m_itemsById.lower_bound(PageId(image))); + if ((id_it == m_itemsById.end()) || (id_it->pageInfo.imageId() != image)) { + // Reference page not found. + return; + } + + ord_it = m_items.project(id_it); + + if (before_or_after == AFTER) { + ++ord_it; + if (!m_orderProvider) { + // Advance past not only the target page, but also its other half, if it follows. + while (ord_it != m_itemsInOrder.end() && ord_it->pageInfo.imageId() == image) { + ++ord_it; } + } } + } - // If m_ptrOrderProvider is not set, ord_it won't change. - ord_it = itemInsertPosition(m_itemsInOrder.begin(), m_itemsInOrder.end(), page_info.id(), + // If m_orderProvider is not set, ord_it won't change. + ord_it = itemInsertPosition(m_itemsInOrder.begin(), m_itemsInOrder.end(), page_info.id(), - /*page_incomplete=*/true, ord_it); + /*page_incomplete=*/true, ord_it); - double offset = 0.0; - if (!m_items.empty()) { - if (ord_it != m_itemsInOrder.end()) { - offset = ord_it->composite->pos().y(); - } else { - ItemsInOrder::iterator it(ord_it); - --it; - offset = it->composite->y() + it->composite->boundingRect().height() + SPACING; - } + double offset = 0.0; + if (!m_items.empty()) { + if (ord_it != m_itemsInOrder.end()) { + offset = ord_it->composite->pos().y(); + } else { + ItemsInOrder::iterator it(ord_it); + --it; + offset = it->composite->y() + it->composite->boundingRect().height() + SPACING; } - std::unique_ptr composite(getCompositeItem(0, page_info)); - composite->setPos(0.0, offset); - composite->updateSceneRect(m_sceneRect); + } + std::unique_ptr composite(getCompositeItem(0, page_info)); + composite->setPos(0.0, offset); + composite->updateSceneRect(m_sceneRect); - const QPointF pos_delta(0.0, composite->boundingRect().height() + SPACING); + const QPointF pos_delta(0.0, composite->boundingRect().height() + SPACING); - const Item item(page_info, composite.get()); - const std::pair ins(m_itemsInOrder.insert(ord_it, item)); - composite->setItem(&*ins.first); - m_graphicsScene.addItem(composite.release()); + const Item item(page_info, composite.get()); + const std::pair ins(m_itemsInOrder.insert(ord_it, item)); + composite->setItem(&*ins.first); + m_graphicsScene.addItem(composite.release()); - const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); - for (; ord_it != ord_end; ++ord_it) { - ord_it->composite->setPos(ord_it->composite->pos() + pos_delta); - ord_it->composite->updateSceneRect(m_sceneRect); - } + const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); + for (; ord_it != ord_end; ++ord_it) { + ord_it->composite->setPos(ord_it->composite->pos() + pos_delta); + ord_it->composite->updateSceneRect(m_sceneRect); + } - commitSceneRect(); + commitSceneRect(); } // ThumbnailSequence::Impl::insert void ThumbnailSequence::Impl::removePages(const std::set& to_remove) { - m_sceneRect = QRectF(0, 0, 0, 0); - - const std::set::const_iterator to_remove_end(to_remove.end()); - QPointF pos_delta(0, 0); - - ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); - const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); - while (ord_it != ord_end) { - if (to_remove.find(ord_it->pageInfo.id()) == to_remove_end) { - // Keeping this page. - if (pos_delta != QPointF(0, 0)) { - ord_it->composite->setPos(ord_it->composite->pos() + pos_delta); - } - ord_it->composite->updateSceneRect(m_sceneRect); - ++ord_it; - } else { - // Removing this page. - if (m_pSelectionLeader == &*ord_it) { - m_pSelectionLeader = 0; - } - pos_delta.ry() -= ord_it->composite->boundingRect().height() + SPACING; - delete ord_it->composite; - m_itemsInOrder.erase(ord_it++); - } + m_sceneRect = QRectF(0, 0, 0, 0); + + const std::set::const_iterator to_remove_end(to_remove.end()); + QPointF pos_delta(0, 0); + + ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); + const ItemsInOrder::iterator ord_end(m_itemsInOrder.end()); + while (ord_it != ord_end) { + if (to_remove.find(ord_it->pageInfo.id()) == to_remove_end) { + // Keeping this page. + if (pos_delta != QPointF(0, 0)) { + ord_it->composite->setPos(ord_it->composite->pos() + pos_delta); + } + ord_it->composite->updateSceneRect(m_sceneRect); + ++ord_it; + } else { + // Removing this page. + if (m_selectionLeader == &*ord_it) { + m_selectionLeader = nullptr; + } + pos_delta.ry() -= ord_it->composite->boundingRect().height() + SPACING; + delete ord_it->composite; + m_itemsInOrder.erase(ord_it++); } + } - commitSceneRect(); + commitSceneRect(); } bool ThumbnailSequence::Impl::multipleItemsSelected() const { - SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin()); - const SelectedThenUnselected::iterator end(m_selectedThenUnselected.end()); - for (int i = 0; i < 2; ++i, ++it) { - if ((it == end) || !it->isSelected()) { - return false; - } + SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin()); + const SelectedThenUnselected::iterator end(m_selectedThenUnselected.end()); + for (int i = 0; i < 2; ++i, ++it) { + if ((it == end) || !it->isSelected()) { + return false; } + } - return true; + return true; } void ThumbnailSequence::Impl::moveToSelected(const Item* item) { - m_selectedThenUnselected.relocate(m_selectedThenUnselected.begin(), m_selectedThenUnselected.iterator_to(*item)); + m_selectedThenUnselected.relocate(m_selectedThenUnselected.begin(), m_selectedThenUnselected.iterator_to(*item)); } void ThumbnailSequence::Impl::moveToUnselected(const Item* item) { - m_selectedThenUnselected.relocate(m_selectedThenUnselected.end(), m_selectedThenUnselected.iterator_to(*item)); + m_selectedThenUnselected.relocate(m_selectedThenUnselected.end(), m_selectedThenUnselected.iterator_to(*item)); } QRectF ThumbnailSequence::Impl::selectionLeaderSceneRect() const { - if (!m_pSelectionLeader) { - return QRectF(); - } + if (!m_selectionLeader) { + return QRectF(); + } - return m_pSelectionLeader->composite->mapToScene(m_pSelectionLeader->composite->boundingRect()).boundingRect(); + return m_selectionLeader->composite->mapToScene(m_selectionLeader->composite->boundingRect()).boundingRect(); } std::set ThumbnailSequence::Impl::selectedItems() const { - std::set selection; - for (const Item& item : m_selectedThenUnselected) { - if (!item.isSelected()) { - break; - } - selection.insert(item.pageInfo.id()); + std::set selection; + for (const Item& item : m_selectedThenUnselected) { + if (!item.isSelected()) { + break; } + selection.insert(item.pageInfo.id()); + } - return selection; + return selection; } std::vector ThumbnailSequence::Impl::selectedRanges() const { - std::vector ranges; + std::vector ranges; - ItemsInOrder::iterator it(m_itemsInOrder.begin()); - const ItemsInOrder::iterator end(m_itemsInOrder.end()); - for (;;) { - for (; it != end && !it->isSelected(); ++it) { - // Skip unselected items. - } - if (it == end) { - break; - } + ItemsInOrder::iterator it(m_itemsInOrder.begin()); + const ItemsInOrder::iterator end(m_itemsInOrder.end()); + while (true) { + for (; it != end && !it->isSelected(); ++it) { + // Skip unselected items. + } + if (it == end) { + break; + } - ranges.push_back(PageRange()); - PageRange& range = ranges.back(); - for (; it != end && it->isSelected(); ++it) { - range.pages.push_back(it->pageInfo.id()); - } + ranges.push_back(PageRange()); + PageRange& range = ranges.back(); + for (; it != end && it->isSelected(); ++it) { + range.pages.push_back(it->pageInfo.id()); } + } - return ranges; + return ranges; } void ThumbnailSequence::Impl::contextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected) { - emit m_rOwner.pageContextMenuRequested(page_info, screen_pos, selected); + emit m_owner.pageContextMenuRequested(page_info, screen_pos, selected); } void ThumbnailSequence::Impl::sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt) { - if (!m_itemsInOrder.empty()) { - CompositeItem* composite = m_itemsInOrder.back().composite; - const QRectF last_thumb_rect(composite->mapToScene(composite->boundingRect()).boundingRect()); - if (evt->scenePos().y() <= last_thumb_rect.bottom()) { - return; - } + if (!m_itemsInOrder.empty()) { + CompositeItem* composite = m_itemsInOrder.back().composite; + const QRectF last_thumb_rect(composite->mapToScene(composite->boundingRect()).boundingRect()); + if (evt->scenePos().y() <= last_thumb_rect.bottom()) { + return; } + } - emit m_rOwner.pastLastPageContextMenuRequested(evt->screenPos()); + emit m_owner.pastLastPageContextMenuRequested(evt->screenPos()); } void ThumbnailSequence::Impl::itemSelectedByUser(CompositeItem* composite, const Qt::KeyboardModifiers modifiers) { - const ItemsById::iterator id_it(m_itemsById.iterator_to(*composite->item())); + const ItemsById::iterator id_it(m_itemsById.iterator_to(*composite->item())); - if (modifiers & Qt::ControlModifier) { - selectItemWithControl(id_it); - } else if (modifiers & Qt::ShiftModifier) { - selectItemWithShift(id_it); - } else { - selectItemNoModifiers(id_it); - } + if (modifiers & Qt::ControlModifier) { + selectItemWithControl(id_it); + } else if (modifiers & Qt::ShiftModifier) { + selectItemWithShift(id_it); + } else { + selectItemNoModifiers(id_it); + } } void ThumbnailSequence::Impl::selectItemWithControl(const ItemsById::iterator& id_it) { - SelectionFlags flags = SELECTED_BY_USER; - - if (!id_it->isSelected()) { - if (m_pSelectionLeader) { - m_pSelectionLeader->setSelectionLeader(false); - } - m_pSelectionLeader = &*id_it; - m_pSelectionLeader->setSelectionLeader(true); - moveToSelected(m_pSelectionLeader); - - m_rOwner.emitNewSelectionLeader(m_pSelectionLeader->pageInfo, m_pSelectionLeader->composite, flags); - - return; - } - - if (!multipleItemsSelected()) { - // Clicked on the only selected item. - flags |= REDUNDANT_SELECTION; - m_rOwner.emitNewSelectionLeader(m_pSelectionLeader->pageInfo, m_pSelectionLeader->composite, flags); - - return; - } - - // Unselect it. - id_it->setSelected(false); - moveToUnselected(&*id_it); - - if (m_pSelectionLeader != &*id_it) { - // The selection leader remains the same - we are done. - return; - } - // Select the new selection leader among other selected items. - m_pSelectionLeader = 0; - flags |= AVOID_SCROLLING_TO; - ItemsInOrder::iterator ord_it1(m_items.project(id_it)); - ItemsInOrder::iterator ord_it2(ord_it1); - for (;;) { - if (ord_it1 != m_itemsInOrder.begin()) { - --ord_it1; - if (ord_it1->isSelected()) { - m_pSelectionLeader = &*ord_it1; - break; - } - } - if (ord_it2 != m_itemsInOrder.end()) { - ++ord_it2; - if (ord_it2 != m_itemsInOrder.end()) { - if (ord_it2->isSelected()) { - m_pSelectionLeader = &*ord_it2; - break; - } - } + SelectionFlags flags = SELECTED_BY_USER; + + if (!id_it->isSelected()) { + if (m_selectionLeader) { + m_selectionLeader->setSelectionLeader(false); + } + m_selectionLeader = &*id_it; + m_selectionLeader->setSelectionLeader(true); + moveToSelected(m_selectionLeader); + + m_owner.emitNewSelectionLeader(m_selectionLeader->pageInfo, m_selectionLeader->composite, flags); + + return; + } + + if (!multipleItemsSelected()) { + // Clicked on the only selected item. + flags |= REDUNDANT_SELECTION; + m_owner.emitNewSelectionLeader(m_selectionLeader->pageInfo, m_selectionLeader->composite, flags); + + return; + } + + // Unselect it. + id_it->setSelected(false); + moveToUnselected(&*id_it); + + if (m_selectionLeader != &*id_it) { + // The selection leader remains the same - we are done. + return; + } + // Select the new selection leader among other selected items. + m_selectionLeader = nullptr; + flags |= AVOID_SCROLLING_TO; + ItemsInOrder::iterator ord_it1(m_items.project(id_it)); + ItemsInOrder::iterator ord_it2(ord_it1); + while (true) { + if (ord_it1 != m_itemsInOrder.begin()) { + --ord_it1; + if (ord_it1->isSelected()) { + m_selectionLeader = &*ord_it1; + break; + } + } + if (ord_it2 != m_itemsInOrder.end()) { + ++ord_it2; + if (ord_it2 != m_itemsInOrder.end()) { + if (ord_it2->isSelected()) { + m_selectionLeader = &*ord_it2; + break; } + } } - assert(m_pSelectionLeader); // We had multiple selected items. + } + assert(m_selectionLeader); // We had multiple selected items. - m_pSelectionLeader->setSelectionLeader(true); - // No need to moveToSelected() as it was and remains selected. + m_selectionLeader->setSelectionLeader(true); + // No need to moveToSelected() as it was and remains selected. - m_rOwner.emitNewSelectionLeader(m_pSelectionLeader->pageInfo, m_pSelectionLeader->composite, flags); + m_owner.emitNewSelectionLeader(m_selectionLeader->pageInfo, m_selectionLeader->composite, flags); } // ThumbnailSequence::Impl::selectItemWithControl void ThumbnailSequence::Impl::selectItemWithShift(const ItemsById::iterator& id_it) { - if (!m_pSelectionLeader) { - selectItemNoModifiers(id_it); - - return; - } - - SelectionFlags flags = SELECTED_BY_USER; - if (m_pSelectionLeader == &*id_it) { - flags |= REDUNDANT_SELECTION; - } - - // Select all the items between the selection leader and the item that was clicked. - ItemsInOrder::iterator endpoint1(m_itemsInOrder.iterator_to(*m_pSelectionLeader)); - ItemsInOrder::iterator endpoint2(m_items.project(id_it)); - - if (endpoint1 == endpoint2) { - // One-element sequence, already selected. - return; - } - // The problem is that we don't know which endpoint precedes the other. - // Let's find out. - ItemsInOrder::iterator ord_it1(endpoint1); - ItemsInOrder::iterator ord_it2(endpoint1); - for (;;) { - if (ord_it1 != m_itemsInOrder.begin()) { - --ord_it1; - if (ord_it1 == endpoint2) { - // endpoint2 was found before endpoint1. - std::swap(endpoint1, endpoint2); - break; - } + if (!m_selectionLeader) { + selectItemNoModifiers(id_it); + + return; + } + + SelectionFlags flags = SELECTED_BY_USER; + if (m_selectionLeader == &*id_it) { + flags |= REDUNDANT_SELECTION; + } + + // Select all the items between the selection leader and the item that was clicked. + ItemsInOrder::iterator endpoint1(m_itemsInOrder.iterator_to(*m_selectionLeader)); + ItemsInOrder::iterator endpoint2(m_items.project(id_it)); + + if (endpoint1 == endpoint2) { + // One-element sequence, already selected. + return; + } + // The problem is that we don't know which endpoint precedes the other. + // Let's find out. + ItemsInOrder::iterator ord_it1(endpoint1); + ItemsInOrder::iterator ord_it2(endpoint1); + while (true) { + if (ord_it1 != m_itemsInOrder.begin()) { + --ord_it1; + if (ord_it1 == endpoint2) { + // endpoint2 was found before endpoint1. + std::swap(endpoint1, endpoint2); + break; + } + } + if (ord_it2 != m_itemsInOrder.end()) { + ++ord_it2; + if (ord_it2 != m_itemsInOrder.end()) { + if (ord_it2 == endpoint2) { + // endpoint2 was found after endpoint1. + break; } - if (ord_it2 != m_itemsInOrder.end()) { - ++ord_it2; - if (ord_it2 != m_itemsInOrder.end()) { - if (ord_it2 == endpoint2) { - // endpoint2 was found after endpoint1. - break; - } - } - } - } - - ++endpoint2; // Make the interval inclusive. - for (; endpoint1 != endpoint2; ++endpoint1) { - endpoint1->setSelected(true); - moveToSelected(&*endpoint1); - } - // Switch the selection leader. - assert(m_pSelectionLeader); - m_pSelectionLeader->setSelectionLeader(false); - m_pSelectionLeader = &*id_it; - m_pSelectionLeader->setSelectionLeader(true); - - m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); + } + } + } + + ++endpoint2; // Make the interval inclusive. + for (; endpoint1 != endpoint2; ++endpoint1) { + endpoint1->setSelected(true); + moveToSelected(&*endpoint1); + } + // Switch the selection leader. + assert(m_selectionLeader); + m_selectionLeader->setSelectionLeader(false); + m_selectionLeader = &*id_it; + m_selectionLeader->setSelectionLeader(true); + + m_owner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); } // ThumbnailSequence::Impl::selectItemWithShift void ThumbnailSequence::Impl::selectItemNoModifiers(const ItemsById::iterator& id_it) { - SelectionFlags flags = SELECTED_BY_USER; - if (m_pSelectionLeader == &*id_it) { - flags |= REDUNDANT_SELECTION; - } + SelectionFlags flags = SELECTED_BY_USER; + if (m_selectionLeader == &*id_it) { + flags |= REDUNDANT_SELECTION; + } - clearSelection(); + clearSelection(); - m_pSelectionLeader = &*id_it; - m_pSelectionLeader->setSelectionLeader(true); - moveToSelected(m_pSelectionLeader); + m_selectionLeader = &*id_it; + m_selectionLeader->setSelectionLeader(true); + moveToSelected(m_selectionLeader); - m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); + m_owner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); } void ThumbnailSequence::Impl::clear() { - m_pSelectionLeader = 0; + m_selectionLeader = nullptr; - ItemsInOrder::iterator it(m_itemsInOrder.begin()); - const ItemsInOrder::iterator end(m_itemsInOrder.end()); - while (it != end) { - delete it->composite; - m_itemsInOrder.erase(it++); - } + ItemsInOrder::iterator it(m_itemsInOrder.begin()); + const ItemsInOrder::iterator end(m_itemsInOrder.end()); + while (it != end) { + delete it->composite; + m_itemsInOrder.erase(it++); + } - assert(m_graphicsScene.items().empty()); + assert(m_graphicsScene.items().empty()); - m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); - commitSceneRect(); + m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); + commitSceneRect(); } void ThumbnailSequence::Impl::clearSelection() { - m_pSelectionLeader = 0; + m_selectionLeader = nullptr; - for (const Item& item : m_selectedThenUnselected) { - if (!item.isSelected()) { - break; - } - item.setSelected(false); + for (const Item& item : m_selectedThenUnselected) { + if (!item.isSelected()) { + break; } + item.setSelected(false); + } } ThumbnailSequence::Impl::ItemsInOrder::iterator ThumbnailSequence::Impl::itemInsertPosition( - const ItemsInOrder::iterator begin, - const ItemsInOrder::iterator end, - const PageId& page_id, - const bool page_incomplete, - const ItemsInOrder::iterator hint, - int* dist_from_hint) { - // Note that to preserve stable ordering, this function *must* return hint, - // as long as it's an acceptable position. - - if (!m_ptrOrderProvider) { - if (dist_from_hint) { - *dist_from_hint = 0; - } - - return hint; - } - - ItemsInOrder::iterator ins_pos(hint); - int dist = 0; - // While the element immediately preceeding ins_pos is supposed to - // follow the page we are inserting, move ins_pos one element back. - while (ins_pos != begin) { - ItemsInOrder::iterator prev(ins_pos); - --prev; - const bool precedes - = m_ptrOrderProvider->precedes(page_id, page_incomplete, prev->pageId(), prev->incompleteThumbnail); - if (precedes) { - ins_pos = prev; - --dist; - } else { - break; - } - } - - // While the element pointed to by ins_pos is supposed to precede - // the page we are inserting, advance ins_pos. - while (ins_pos != end) { - const bool precedes = m_ptrOrderProvider->precedes(ins_pos->pageId(), ins_pos->incompleteThumbnail, page_id, - page_incomplete); - if (precedes) { - ++ins_pos; - ++dist; - } else { - break; - } - } - + const ItemsInOrder::iterator begin, + const ItemsInOrder::iterator end, + const PageId& page_id, + const bool page_incomplete, + const ItemsInOrder::iterator hint, + int* dist_from_hint) { + // Note that to preserve stable ordering, this function *must* return hint, + // as long as it's an acceptable position. + + if (!m_orderProvider) { if (dist_from_hint) { - *dist_from_hint = dist; + *dist_from_hint = 0; + } + + return hint; + } + + ItemsInOrder::iterator ins_pos(hint); + int dist = 0; + // While the element immediately preceeding ins_pos is supposed to + // follow the page we are inserting, move ins_pos one element back. + while (ins_pos != begin) { + ItemsInOrder::iterator prev(ins_pos); + --prev; + const bool precedes + = m_orderProvider->precedes(page_id, page_incomplete, prev->pageId(), prev->incompleteThumbnail); + if (precedes) { + ins_pos = prev; + --dist; + } else { + break; + } + } + + // While the element pointed to by ins_pos is supposed to precede + // the page we are inserting, advance ins_pos. + while (ins_pos != end) { + const bool precedes + = m_orderProvider->precedes(ins_pos->pageId(), ins_pos->incompleteThumbnail, page_id, page_incomplete); + if (precedes) { + ++ins_pos; + ++dist; + } else { + break; } + } + + if (dist_from_hint) { + *dist_from_hint = dist; + } - return ins_pos; + return ins_pos; } // ThumbnailSequence::Impl::itemInsertPosition std::unique_ptr ThumbnailSequence::Impl::getThumbnail(const PageInfo& page_info) { - std::unique_ptr thumb; + std::unique_ptr thumb; - if (m_ptrFactory) { - thumb = m_ptrFactory->get(page_info); - } + if (m_factory) { + thumb = m_factory->get(page_info); + } - if (!thumb) { - thumb = std::make_unique(m_maxLogicalThumbSize); - } + if (!thumb) { + thumb = std::make_unique(m_maxLogicalThumbSize); + } - return thumb; + return thumb; } std::unique_ptr ThumbnailSequence::Impl::getLabelGroup(const PageInfo& page_info) { - const PageId& page_id = page_info.id(); - const QFileInfo file_info(page_id.imageId().filePath()); - const QString file_name(file_info.baseName()); - - QString text; - if (file_name.size() <= 30) { - text = file_name; - } else { - text = "..." + file_name.right(30); - } - if (page_info.imageId().isMultiPageFile()) { - text = ThumbnailSequence::tr("%1 (page %2)").arg(text).arg(page_id.imageId().page()); - } - - std::unique_ptr normal_text_item(new QGraphicsSimpleTextItem); - normal_text_item->setText(text); - - std::unique_ptr bold_text_item(new QGraphicsSimpleTextItem); - bold_text_item->setText(text); - QFont bold_font(bold_text_item->font()); - bold_font.setWeight(QFont::Bold); - bold_text_item->setFont(bold_font); - - bold_text_item->setBrush(ColorSchemeManager::instance()->getColorParam("thumbnail_sequence_selected_item_text", - QApplication::palette().highlightedText())); - - QRectF normal_text_box(normal_text_item->boundingRect()); - QRectF bold_text_box(bold_text_item->boundingRect()); - normal_text_box.moveCenter(bold_text_box.center()); - normal_text_box.moveRight(bold_text_box.right()); - normal_text_item->setPos(normal_text_box.topLeft()); - bold_text_item->setPos(bold_text_box.topLeft()); - - const char* pixmap_resource = 0; - switch (page_id.subPage()) { - case PageId::LEFT_PAGE: - pixmap_resource = ":/icons/left_page_thumb.png"; - break; - case PageId::RIGHT_PAGE: - pixmap_resource = ":/icons/right_page_thumb.png"; - break; - default: - return std::unique_ptr(new LabelGroup(std::move(normal_text_item), std::move(bold_text_item))); - } - - const QPixmap pixmap(pixmap_resource); - std::unique_ptr pixmap_item(new QGraphicsPixmapItem); - pixmap_item->setPixmap(pixmap); - - const int label_pixmap_spacing = 5; - - QRectF pixmap_box(pixmap_item->boundingRect()); - pixmap_box.moveCenter(bold_text_box.center()); - pixmap_box.moveLeft(bold_text_box.right() + label_pixmap_spacing); - pixmap_item->setPos(pixmap_box.topLeft()); - - return std::unique_ptr( - new LabelGroup(std::move(normal_text_item), std::move(bold_text_item), std::move(pixmap_item))); + const PageId& page_id = page_info.id(); + const QFileInfo file_info(page_id.imageId().filePath()); + const QString file_name(file_info.baseName()); + + QString text; + if (file_name.size() <= 15) { + text = file_name; + } else { + text = "..." + file_name.right(15); + } + if (page_info.imageId().isMultiPageFile()) { + text = ThumbnailSequence::tr("%1 (page %2)").arg(text).arg(page_id.imageId().page()); + } + + std::unique_ptr normal_text_item(new QGraphicsSimpleTextItem); + normal_text_item->setText(text); + + std::unique_ptr bold_text_item(new QGraphicsSimpleTextItem); + bold_text_item->setText(text); + + const QBrush selected_item_text_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::ThumbnailSequenceSelectedItemText, QApplication::palette().highlightedText()); + const QBrush selection_leader_text_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::ThumbnailSequenceSelectionLeaderText, selected_item_text_color); + bold_text_item->setBrush(selection_leader_text_color); + + QRectF normal_text_box(normal_text_item->boundingRect()); + QRectF bold_text_box(bold_text_item->boundingRect()); + normal_text_box.moveCenter(bold_text_box.center()); + normal_text_box.moveRight(bold_text_box.right()); + normal_text_item->setPos(normal_text_box.topLeft()); + bold_text_item->setPos(bold_text_box.topLeft()); + + const char* pixmap_resource = 0; + switch (page_id.subPage()) { + case PageId::LEFT_PAGE: + pixmap_resource = ":/icons/left_page_thumb.png"; + break; + case PageId::RIGHT_PAGE: + pixmap_resource = ":/icons/right_page_thumb.png"; + break; + default: + return std::unique_ptr(new LabelGroup(std::move(normal_text_item), std::move(bold_text_item))); + } + + const QPixmap pixmap(pixmap_resource); + std::unique_ptr pixmap_item(new QGraphicsPixmapItem); + pixmap_item->setPixmap(pixmap); + + const int label_pixmap_spacing = 5; + + QRectF pixmap_box(pixmap_item->boundingRect()); + pixmap_box.moveCenter(bold_text_box.center()); + pixmap_box.moveLeft(bold_text_box.right() + label_pixmap_spacing); + pixmap_item->setPos(pixmap_box.topLeft()); + + return std::unique_ptr( + new LabelGroup(std::move(normal_text_item), std::move(bold_text_item), std::move(pixmap_item))); } // ThumbnailSequence::Impl::getLabelGroup std::unique_ptr ThumbnailSequence::Impl::getCompositeItem(const Item* item, const PageInfo& page_info) { - std::unique_ptr thumb(getThumbnail(page_info)); - std::unique_ptr label_group(getLabelGroup(page_info)); - std::unique_ptr composite(new CompositeItem(*this, std::move(thumb), std::move(label_group))); - composite->setItem(item); + std::unique_ptr thumb(getThumbnail(page_info)); + std::unique_ptr label_group(getLabelGroup(page_info)); + std::unique_ptr composite(new CompositeItem(*this, std::move(thumb), std::move(label_group))); + composite->setItem(item); - return composite; + return composite; } void ThumbnailSequence::Impl::commitSceneRect() { - if (m_sceneRect.isNull()) { - m_graphicsScene.setSceneRect(QRectF(0.0, 0.0, 1.0, 1.0)); - } else { - m_graphicsScene.setSceneRect(m_sceneRect); - } + if (m_sceneRect.isNull()) { + m_graphicsScene.setSceneRect(QRectF(0.0, 0.0, 1.0, 1.0)); + } else { + m_graphicsScene.setSceneRect(m_sceneRect); + } +} + +const QSizeF& ThumbnailSequence::Impl::getMaxLogicalThumbSize() const { + return m_maxLogicalThumbSize; +} + +void ThumbnailSequence::Impl::setMaxLogicalThumbSize(const QSizeF& size) { + m_maxLogicalThumbSize = size; } /*==================== ThumbnailSequence::Item ======================*/ ThumbnailSequence::Item::Item(const PageInfo& page_info, CompositeItem* comp_item) - : pageInfo(page_info), - composite(comp_item), - incompleteThumbnail(comp_item->incompleteThumbnail()), - m_isSelected(false), - m_isSelectionLeader(false) { -} + : pageInfo(page_info), + composite(comp_item), + incompleteThumbnail(comp_item->incompleteThumbnail()), + m_isSelected(false), + m_isSelectionLeader(false) {} void ThumbnailSequence::Item::setSelected(bool selected) const { - const bool was_selected = m_isSelected; - const bool was_selection_leader = m_isSelectionLeader; - m_isSelected = selected; - m_isSelectionLeader = m_isSelectionLeader && selected; + const bool was_selected = m_isSelected; + const bool was_selection_leader = m_isSelectionLeader; + m_isSelected = selected; + m_isSelectionLeader = m_isSelectionLeader && selected; - if ((was_selected != m_isSelected) || (was_selection_leader != m_isSelectionLeader)) { - composite->updateAppearence(m_isSelected, m_isSelectionLeader); - } - if (was_selected != m_isSelected) { - composite->update(); - } + if ((was_selected != m_isSelected) || (was_selection_leader != m_isSelectionLeader)) { + composite->updateAppearence(m_isSelected, m_isSelectionLeader); + composite->update(); + } } void ThumbnailSequence::Item::setSelectionLeader(bool selection_leader) const { - const bool was_selected = m_isSelected; - const bool was_selection_leader = m_isSelectionLeader; - m_isSelected = m_isSelected || selection_leader; - m_isSelectionLeader = selection_leader; + const bool was_selected = m_isSelected; + const bool was_selection_leader = m_isSelectionLeader; + m_isSelected = m_isSelected || selection_leader; + m_isSelectionLeader = selection_leader; - if ((was_selected != m_isSelected) || (was_selection_leader != m_isSelectionLeader)) { - composite->updateAppearence(m_isSelected, m_isSelectionLeader); - } - if (was_selected != m_isSelected) { - composite->update(); - } + if ((was_selected != m_isSelected) || (was_selection_leader != m_isSelectionLeader)) { + composite->updateAppearence(m_isSelected, m_isSelectionLeader); + composite->update(); + } } /*================== ThumbnailSequence::PlaceholderThumb ====================*/ QPainterPath ThumbnailSequence::PlaceholderThumb::m_sCachedPath; -ThumbnailSequence::PlaceholderThumb::PlaceholderThumb(const QSizeF& max_size) : m_maxSize(max_size) { -} +ThumbnailSequence::PlaceholderThumb::PlaceholderThumb(const QSizeF& max_size) : m_maxSize(max_size) {} QRectF ThumbnailSequence::PlaceholderThumb::boundingRect() const { - return QRectF(QPointF(0.0, 0.0), m_maxSize); + return QRectF(QPointF(0.0, 0.0), m_maxSize); } void ThumbnailSequence::PlaceholderThumb::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { - IncompleteThumbnail::drawQuestionMark(*painter, boundingRect()); + IncompleteThumbnail::drawQuestionMark(*painter, boundingRect()); } /*====================== ThumbnailSequence::LabelGroup ======================*/ @@ -1390,34 +1374,37 @@ void ThumbnailSequence::PlaceholderThumb::paint(QPainter* painter, const QStyleO ThumbnailSequence::LabelGroup::LabelGroup(std::unique_ptr normal_label, std::unique_ptr bold_label, std::unique_ptr pixmap) - : m_pNormalLabel(normal_label.get()), m_pBoldLabel(bold_label.get()) { - m_pNormalLabel->setVisible(true); - m_pBoldLabel->setVisible(false); - - bold_label->setPos( - bold_label->pos().x() + 0.5 * (bold_label->boundingRect().width() - normal_label->boundingRect().width()), - bold_label->pos().y()); - - addToGroup(normal_label.release()); - addToGroup(bold_label.release()); - if (pixmap) { - addToGroup(pixmap.release()); - } + : m_normalLabel(normal_label.get()), m_boldLabel(bold_label.get()) { + m_normalLabel->setVisible(true); + m_boldLabel->setVisible(false); + + bold_label->setPos( + bold_label->pos().x() + 0.5 * (bold_label->boundingRect().width() - normal_label->boundingRect().width()), + bold_label->pos().y()); + + addToGroup(normal_label.release()); + addToGroup(bold_label.release()); + if (pixmap) { + addToGroup(pixmap.release()); + } } void ThumbnailSequence::LabelGroup::updateAppearence(bool selected, bool selection_leader) { - m_pNormalLabel->setVisible(!selection_leader); - m_pBoldLabel->setVisible(selection_leader); - - if (selection_leader) { - assert(selected); - } else if (selected) { - m_pNormalLabel->setBrush(ColorSchemeManager::instance()->getColorParam( - "thumbnail_sequence_selected_item_text", QApplication::palette().highlightedText())); - } else { - m_pNormalLabel->setBrush(ColorSchemeManager::instance()->getColorParam("thumbnail_sequence_item_text", - QApplication::palette().text())); - } + m_normalLabel->setVisible(!selection_leader); + m_boldLabel->setVisible(selection_leader); + + const QBrush item_text_color = ColorSchemeManager::instance()->getColorParam(ColorScheme::ThumbnailSequenceItemText, + QApplication::palette().text()); + const QBrush selected_item_text_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::ThumbnailSequenceSelectedItemText, QApplication::palette().highlightedText()); + + if (selection_leader) { + assert(selected); + } else if (selected) { + m_normalLabel->setBrush(selected_item_text_color); + } else { + m_normalLabel->setBrush(item_text_color); + } } /*==================== ThumbnailSequence::CompositeItem =====================*/ @@ -1425,77 +1412,78 @@ void ThumbnailSequence::LabelGroup::updateAppearence(bool selected, bool selecti ThumbnailSequence::CompositeItem::CompositeItem(ThumbnailSequence::Impl& owner, std::unique_ptr thumbnail, std::unique_ptr label_group) - : m_rOwner(owner), m_pItem(0), m_pThumb(thumbnail.get()), m_pLabelGroup(label_group.get()) { - const QSizeF thumb_size(thumbnail->boundingRect().size()); - const QSizeF label_size(label_group->boundingRect().size()); + : m_owner(owner), m_item(0), m_thumb(thumbnail.get()), m_labelGroup(label_group.get()) { + const QSizeF thumb_size(thumbnail->boundingRect().size()); + const QSizeF label_size(label_group->boundingRect().size()); - const int thumb_label_spacing = 1; - thumbnail->setPos(-0.5 * thumb_size.width(), 0.0); - label_group->setPos(thumbnail->pos().x() + 0.5 * (thumb_size.width() - label_size.width()), - thumb_size.height() + thumb_label_spacing); + const int thumb_label_spacing = 1; + thumbnail->setPos(-0.5 * thumb_size.width(), 0.0); + label_group->setPos(thumbnail->pos().x() + 0.5 * (thumb_size.width() - label_size.width()), + thumb_size.height() + thumb_label_spacing); - addToGroup(thumbnail.release()); - addToGroup(label_group.release()); + addToGroup(thumbnail.release()); + addToGroup(label_group.release()); - setCursor(Qt::PointingHandCursor); - setZValue(-1); + setCursor(Qt::PointingHandCursor); + setZValue(-1); } bool ThumbnailSequence::CompositeItem::incompleteThumbnail() const { - return dynamic_cast(m_pThumb) != 0; + return dynamic_cast(m_thumb) != 0; } void ThumbnailSequence::CompositeItem::updateSceneRect(QRectF& scene_rect) { - QRectF rect(m_pThumb->boundingRect()); - rect.translate(m_pThumb->pos()); - rect.translate(pos()); + QRectF rect(m_thumb->boundingRect()); + rect.translate(m_thumb->pos()); + rect.translate(pos()); - QRectF bounding_rect(boundingRect()); - bounding_rect.translate(pos()); + QRectF bounding_rect(boundingRect()); + bounding_rect.translate(pos()); - rect.setTop(bounding_rect.top()); - rect.setBottom(bounding_rect.bottom()); + rect.setTop(bounding_rect.top()); + rect.setBottom(bounding_rect.bottom()); - scene_rect |= rect; + scene_rect |= rect; } void ThumbnailSequence::CompositeItem::updateAppearence(bool selected, bool selection_leader) { - m_pLabelGroup->updateAppearence(selected, selection_leader); + m_labelGroup->updateAppearence(selected, selection_leader); } QRectF ThumbnailSequence::CompositeItem::boundingRect() const { - QRectF rect(QGraphicsItemGroup::boundingRect()); - qreal horizontalAdjustVal = 150 - 0.5 * rect.size().width(); - if (horizontalAdjustVal < 5) { - horizontalAdjustVal = 5; - } + QRectF rect(QGraphicsItemGroup::boundingRect()); + qreal horizontalAdjustVal = std::max(70 - 0.5 * rect.size().width(), 10.0); + rect.adjust(-horizontalAdjustVal, -5, horizontalAdjustVal, 3); - rect.adjust(-horizontalAdjustVal, -5, horizontalAdjustVal, 3); - - return rect; + return rect; } void ThumbnailSequence::CompositeItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - if (m_pItem->isSelected()) { - painter->fillRect(boundingRect(), ColorSchemeManager::instance()->getColorParam( - "thumbnail_sequence_selected_item_background", - QApplication::palette().color(QPalette::Highlight))); - } + const QBrush selected_item_background_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::ThumbnailSequenceSelectedItemBackground, QApplication::palette().color(QPalette::Highlight)); + const QBrush selection_leader_background_color = ColorSchemeManager::instance()->getColorParam( + ColorScheme::ThumbnailSequenceSelectionLeaderBackground, selected_item_background_color); + + if (m_item->isSelectionLeader()) { + painter->fillRect(boundingRect(), selection_leader_background_color); + } else if (m_item->isSelected()) { + painter->fillRect(boundingRect(), selected_item_background_color); + } } void ThumbnailSequence::CompositeItem::mousePressEvent(QGraphicsSceneMouseEvent* const event) { - QGraphicsItemGroup::mousePressEvent(event); + QGraphicsItemGroup::mousePressEvent(event); - event->accept(); + event->accept(); - if (event->button() == Qt::LeftButton) { - m_rOwner.itemSelectedByUser(this, event->modifiers()); - } + if (event->button() == Qt::LeftButton) { + m_owner.itemSelectedByUser(this, event->modifiers()); + } } void ThumbnailSequence::CompositeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* const event) { - event->accept(); // Prevent it from propagating further. - m_rOwner.contextMenuRequested(m_pItem->pageInfo, event->screenPos(), m_pItem->isSelected()); + event->accept(); // Prevent it from propagating further. + m_owner.contextMenuRequested(m_item->pageInfo, event->screenPos(), m_item->isSelected()); } diff --git a/ThumbnailSequence.h b/ThumbnailSequence.h index c30882d83..f68397792 100644 --- a/ThumbnailSequence.h +++ b/ThumbnailSequence.h @@ -19,16 +19,16 @@ #ifndef THUMBNAILSEQUENCE_H_ #define THUMBNAILSEQUENCE_H_ -#include "NonCopyable.h" -#include "FlagOps.h" -#include "intrusive_ptr.h" -#include "PageRange.h" -#include "PageOrderProvider.h" -#include "BeforeOrAfter.h" #include #include -#include #include +#include +#include "BeforeOrAfter.h" +#include "FlagOps.h" +#include "NonCopyable.h" +#include "PageOrderProvider.h" +#include "PageRange.h" +#include "intrusive_ptr.h" class QGraphicsItem; class QGraphicsView; @@ -42,198 +42,218 @@ class QRectF; class QPoint; class ThumbnailSequence : public QObject { - Q_OBJECT - DECLARE_NON_COPYABLE(ThumbnailSequence) - -public: - enum SelectionAction { KEEP_SELECTION, RESET_SELECTION }; - - enum SelectionFlags { - DEFAULT_SELECTION_FLAGS = 0, - - /** Indicates the item was selected by a user action, rather than programmatically. */ - SELECTED_BY_USER = 1 << 0, + Q_OBJECT + DECLARE_NON_COPYABLE(ThumbnailSequence) - /** - * Indicates that the request to make this item a selection leader was redundant, - * as it's already a selection leader. - */ - REDUNDANT_SELECTION = 1 << 1, + public: + enum SelectionAction { KEEP_SELECTION, RESET_SELECTION }; - /** - * This flag is set when Ctrl-clicking the current selection leader while other - * selected items exist. In this case, the leader will become unselected, and - * one of the other selected items will be promoted to a selection leader. - * In these circumstances, scrolling to make the new selection leader visible - * is undesireable. - */ - AVOID_SCROLLING_TO = 1 << 2 - }; + enum SelectionFlags { + DEFAULT_SELECTION_FLAGS = 0, - explicit ThumbnailSequence(const QSizeF& max_logical_thumb_size); - - ~ThumbnailSequence() override; - - void setThumbnailFactory(intrusive_ptr factory); - - void attachView(QGraphicsView* view); - - /** - * \brief Re-populate the list of thumbnails. - * - * \param pages Pages to put in the sequence. - * \param selection_action Whether to keep the selection, provided - * selected item(s) are still present in the new list of pages. - * \param order_provider The source of ordering information. It will - * be preserved until the next reset() call and will be taken - * into account by other methods, like invalidateThumbnail() - * and insert(). A null order provider indicates to keep the - * order of ProjectPages. - */ - void reset(const PageSequence& pages, - SelectionAction selection_action, - intrusive_ptr order_provider = nullptr); - - /** Returns the current page order provider, which may be null. */ - intrusive_ptr pageOrderProvider() const; - - PageSequence toPageSequence() const; - - /** - * \brief Updates appearence and possibly position of a thumbnail. - * - * If thumbnail's size or position have changed and this thumbnail - * is a selection leader, newSelectionLeader() signal will be emitted - * with REDUNDANT_SELECTION flag set. - * - * \note This function assumes the thumbnail specified by page_id - * is the only thumbnail at incorrect position. If you do - * something that changes the logical position of more than - * one thumbnail at once, use invalidateAllThumbnails() - * instead of sequentially calling invalidateThumbnail(). - */ - void invalidateThumbnail(const PageId& page_id); - - /** - * This signature differs from invalidateThumbnail(PageId) in that - * it will cause PageInfo stored by ThumbnailSequence to be updated. - */ - void invalidateThumbnail(const PageInfo& page_info); - - /** - * \brief Updates appearence of all thumbnails and possibly their order. - * - * Whether or not order will be updated depends on whether an order provider - * was specified by the most recent reset() call. - */ - void invalidateAllThumbnails(); + /** Indicates the item was selected by a user action, rather than programmatically. */ + SELECTED_BY_USER = 1 << 0, /** - * \brief Makes the item a selection leader, and unselects other items. - * - * \param page_id The page to select. - * \return true on success, false if the requested page wasn't found. - * - * On success, the newSelectionLeader() signal is emitted, possibly - * with REDUNDANT_SELECTION flag set, in case our page was already the - * selection leader. + * Indicates that the request to make this item a selection leader was redundant, + * as it's already a selection leader. */ - bool setSelection(const PageId& page_id); + REDUNDANT_SELECTION = 1 << 1, /** - * \brief Returns the current selection leader. - * - * A null PageInfo is returned if no items are currently selected. + * This flag is set when Ctrl-clicking the current selection leader while other + * selected items exist. In this case, the leader will become unselected, and + * one of the other selected items will be promoted to a selection leader. + * In these circumstances, scrolling to make the new selection leader visible + * is undesireable. */ - PageInfo selectionLeader() const; - - /** - * \brief Returns the page immediately following the given one. - * - * A null PageInfo is returned if the given page wasn't found or - * there are no pages preceeding it. - */ - PageInfo prevPage(const PageId& reference_page) const; - - /** - * \brief Returns the page immediately following the given one. - * - * A null PageInfo is returned if the given page wasn't found or - * there are no pages following it. - */ - PageInfo nextPage(const PageId& reference_page) const; - - /** - * \brief Returns the first page in the sequence. - * - * A null PageInfo is returned if the sequence is empty. - */ - PageInfo firstPage() const; - - /** - * \brief Returns the last page in the sequence. - * - * A null PageInfo is returned if the sequence is empty. - */ - PageInfo lastPage() const; - - /** - * \brief Inserts a page before the first page with matching ImageId. - * - * If no order provider was specified by the previous reset() call, - * we won't allow inserting a page between two halves of another page, - * to be compatible with what reset() does. Otherwise, the new - * page will be inserted at a correct position according to the current - * order provider. In this case \p before_or_after doesn't really matter. - * - * If there are no pages with matching ImageId, the new page won't - * be inserted, unless the request is to insert BEFORE a null ImageId(), - * which would cause insertion at the end. - */ - void insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image); - - void removePages(const std::set& pages); - - /** - * \brief The bounding rectangle in scene coordinates of the selection leader. - * - * Returns a null rectangle if no item is currently selected. - */ - QRectF selectionLeaderSceneRect() const; - - std::set selectedItems() const; - - std::vector selectedRanges() const; - -signals: - - void newSelectionLeader(const PageInfo& page_info, - const QRectF& thumb_rect, - ThumbnailSequence::SelectionFlags flags); - - /** - * Emitted when a user right-clicks on a page thumbnail. - */ - void pageContextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); - - /** - * Emitted when a user right clicks on area below the last page. - * In the absence of any pages, all the area is considered to be - * below the last page. - */ - void pastLastPageContextMenuRequested(const QPoint& screen_pos); - -private: - class Item; - class Impl; - class GraphicsScene; - class PlaceholderThumb; - class LabelGroup; - class CompositeItem; - - void emitNewSelectionLeader(const PageInfo& page_info, const CompositeItem* composite, SelectionFlags flags); - - std::unique_ptr m_ptrImpl; + AVOID_SCROLLING_TO = 1 << 2 + }; + + explicit ThumbnailSequence(const QSizeF& max_logical_thumb_size); + + ~ThumbnailSequence() override; + + void setThumbnailFactory(intrusive_ptr factory); + + void attachView(QGraphicsView* view); + + /** + * \brief Re-populate the list of thumbnails. + * + * \param pages Pages to put in the sequence. + * \param selection_action Whether to keep the selection, provided + * selected item(s) are still present in the new list of pages. + * \param order_provider The source of ordering information. It will + * be preserved until the next reset() call and will be taken + * into account by other methods, like invalidateThumbnail() + * and insert(). A null order provider indicates to keep the + * order of ProjectPages. + */ + void reset(const PageSequence& pages, + SelectionAction selection_action, + intrusive_ptr order_provider = nullptr); + + /** Returns the current page order provider, which may be null. */ + intrusive_ptr pageOrderProvider() const; + + PageSequence toPageSequence() const; + + /** + * \brief Updates appearence and possibly position of a thumbnail. + * + * If thumbnail's size or position have changed and this thumbnail + * is a selection leader, newSelectionLeader() signal will be emitted + * with REDUNDANT_SELECTION flag set. + * + * \note This function assumes the thumbnail specified by page_id + * is the only thumbnail at incorrect position. If you do + * something that changes the logical position of more than + * one thumbnail at once, use invalidateAllThumbnails() + * instead of sequentially calling invalidateThumbnail(). + */ + void invalidateThumbnail(const PageId& page_id); + + /** + * This signature differs from invalidateThumbnail(PageId) in that + * it will cause PageInfo stored by ThumbnailSequence to be updated. + */ + void invalidateThumbnail(const PageInfo& page_info); + + /** + * \brief Updates appearence of all thumbnails and possibly their order. + * + * Whether or not order will be updated depends on whether an order provider + * was specified by the most recent reset() call. + */ + void invalidateAllThumbnails(); + + /** + * \brief Makes the item a selection leader, and unselects other items. + * + * \param page_id The page to select. + * \param selection_action Whether to keep the selection, provided + * selected item(s) are still present in the new list of pages. + * \return true on success, false if the requested page wasn't found. + * + * On success, the newSelectionLeader() signal is emitted, possibly + * with REDUNDANT_SELECTION flag set, in case our page was already the + * selection leader. + */ + bool setSelection(const PageId& page_id, SelectionAction selection_action = RESET_SELECTION); + + /** + * \brief Returns the current selection leader. + * + * A null PageInfo is returned if no items are currently selected. + */ + PageInfo selectionLeader() const; + + /** + * \brief Returns the page immediately preceding the given one. + * + * A null PageInfo is returned if the given page wasn't found or + * there are no pages preceding it. + */ + PageInfo prevPage(const PageId& reference_page) const; + + /** + * \brief Returns the page immediately following the given one. + * + * A null PageInfo is returned if the given page wasn't found or + * there are no pages following it. + */ + PageInfo nextPage(const PageId& reference_page) const; + + /** + * \brief Returns the selected page preceding the given one. + * + * A null PageInfo is returned if the given page wasn't found or + * there are no pages preceding it. + */ + PageInfo prevSelectedPage(const PageId& reference_page) const; + + /** + * \brief Returns the selected page following the given one. + * + * A null PageInfo is returned if the given page wasn't found or + * there are no pages following it. + */ + PageInfo nextSelectedPage(const PageId& reference_page) const; + + /** + * \brief Returns the first page in the sequence. + * + * A null PageInfo is returned if the sequence is empty. + */ + PageInfo firstPage() const; + + /** + * \brief Returns the last page in the sequence. + * + * A null PageInfo is returned if the sequence is empty. + */ + PageInfo lastPage() const; + + /** + * \brief Inserts a page before the first page with matching ImageId. + * + * If no order provider was specified by the previous reset() call, + * we won't allow inserting a page between two halves of another page, + * to be compatible with what reset() does. Otherwise, the new + * page will be inserted at a correct position according to the current + * order provider. In this case \p before_or_after doesn't really matter. + * + * If there are no pages with matching ImageId, the new page won't + * be inserted, unless the request is to insert BEFORE a null ImageId(), + * which would cause insertion at the end. + */ + void insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image); + + void removePages(const std::set& pages); + + /** + * \brief The bounding rectangle in scene coordinates of the selection leader. + * + * Returns a null rectangle if no item is currently selected. + */ + QRectF selectionLeaderSceneRect() const; + + std::set selectedItems() const; + + std::vector selectedRanges() const; + + const QSizeF& getMaxLogicalThumbSize() const; + + void setMaxLogicalThumbSize(const QSizeF& size); + + signals: + + void newSelectionLeader(const PageInfo& page_info, const QRectF& thumb_rect, ThumbnailSequence::SelectionFlags flags); + + /** + * Emitted when a user right-clicks on a page thumbnail. + */ + void pageContextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected); + + /** + * Emitted when a user right clicks on area below the last page. + * In the absence of any pages, all the area is considered to be + * below the last page. + */ + void pastLastPageContextMenuRequested(const QPoint& screen_pos); + + private: + class Item; + class Impl; + class GraphicsScene; + class PlaceholderThumb; + class LabelGroup; + class CompositeItem; + + void emitNewSelectionLeader(const PageInfo& page_info, const CompositeItem* composite, SelectionFlags flags); + + std::unique_ptr m_impl; }; diff --git a/TiffMetadataLoader.cpp b/TiffMetadataLoader.cpp index dbe5e323f..0f63da18c 100644 --- a/TiffMetadataLoader.cpp +++ b/TiffMetadataLoader.cpp @@ -20,14 +20,14 @@ #include "TiffReader.h" void TiffMetadataLoader::registerMyself() { - static bool registered = false; - if (!registered) { - ImageMetadataLoader::registerLoader(intrusive_ptr(new TiffMetadataLoader)); - registered = true; - } + static bool registered = false; + if (!registered) { + ImageMetadataLoader::registerLoader(make_intrusive()); + registered = true; + } } ImageMetadataLoader::Status TiffMetadataLoader::loadMetadata(QIODevice& io_device, const VirtualFunction& out) { - return TiffReader::readMetadata(io_device, out); + return TiffReader::readMetadata(io_device, out); } diff --git a/TiffMetadataLoader.h b/TiffMetadataLoader.h index aa20a614c..551759620 100644 --- a/TiffMetadataLoader.h +++ b/TiffMetadataLoader.h @@ -19,25 +19,25 @@ #ifndef TIFFMETADATALOADER_H_ #define TIFFMETADATALOADER_H_ +#include #include "ImageMetadataLoader.h" #include "VirtualFunction.h" -#include class QIODevice; class ImageMetadata; class TiffMetadataLoader : public ImageMetadataLoader { -public: - /** - * \brief Register this loader in the global registry. - * - * The same restrictions apply here as for - * ImageMetadataLoader::registerLoader() - */ - static void registerMyself(); - -protected: - Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; + public: + /** + * \brief Register this loader in the global registry. + * + * The same restrictions apply here as for + * ImageMetadataLoader::registerLoader() + */ + static void registerMyself(); + + protected: + Status loadMetadata(QIODevice& io_device, const VirtualFunction& out) override; }; diff --git a/TiffReader.cpp b/TiffReader.cpp index fc2fab96e..339aba693 100644 --- a/TiffReader.cpp +++ b/TiffReader.cpp @@ -17,498 +17,483 @@ */ #include "TiffReader.h" -#include "ImageMetadata.h" -#include "NonCopyable.h" -#include "Dpm.h" -#include -#include -#include #include #include +#include +#include +#include #include #include +#include "Dpm.h" +#include "ImageMetadata.h" +#include "NonCopyable.h" class TiffReader::TiffHeader { -public: - enum Signature { INVALID_SIGNATURE, TIFF_BIG_ENDIAN, TIFF_LITTLE_ENDIAN }; + public: + enum Signature { INVALID_SIGNATURE, TIFF_BIG_ENDIAN, TIFF_LITTLE_ENDIAN }; - TiffHeader() : m_signature(INVALID_SIGNATURE), m_version(0) { - } + TiffHeader() : m_signature(INVALID_SIGNATURE), m_version(0) {} - TiffHeader(Signature signature, int version) : m_signature(signature), m_version(version) { - } + TiffHeader(Signature signature, int version) : m_signature(signature), m_version(version) {} - Signature signature() const { - return m_signature; - } + Signature signature() const { return m_signature; } - int version() const { - return m_version; - } + int version() const { return m_version; } -private: - Signature m_signature; - int m_version; + private: + Signature m_signature; + int m_version; }; class TiffReader::TiffHandle { -public: - explicit TiffHandle(TIFF* handle) : m_pHandle(handle) { - } + public: + explicit TiffHandle(TIFF* handle) : m_handle(handle) {} - ~TiffHandle() { - if (m_pHandle) { - TIFFClose(m_pHandle); - } + ~TiffHandle() { + if (m_handle) { + TIFFClose(m_handle); } + } - TIFF* handle() const { - return m_pHandle; - } + TIFF* handle() const { return m_handle; } -private: - TIFF* m_pHandle; + private: + TIFF* m_handle; }; -template +template class TiffReader::TiffBuffer { - DECLARE_NON_COPYABLE(TiffBuffer) + DECLARE_NON_COPYABLE(TiffBuffer) -public: - TiffBuffer() : m_pData(nullptr) { - } + public: + TiffBuffer() : m_data(nullptr) {} - explicit TiffBuffer(tsize_t num_items) { - m_pData = (T*) _TIFFmalloc(num_items * sizeof(T)); - if (!m_pData) { - throw std::bad_alloc(); - } + explicit TiffBuffer(tsize_t num_items) { + m_data = (T*) _TIFFmalloc(num_items * sizeof(T)); + if (!m_data) { + throw std::bad_alloc(); } + } - ~TiffBuffer() { - if (m_pData) { - _TIFFfree(m_pData); - } + ~TiffBuffer() { + if (m_data) { + _TIFFfree(m_data); } + } - T* data() { - return m_pData; - } + T* data() { return m_data; } - void swap(TiffBuffer& other) { - std::swap(m_pData, other.m_pData); - } + void swap(TiffBuffer& other) { std::swap(m_data, other.m_data); } -private: - T* m_pData; + private: + T* m_data; }; struct TiffReader::TiffInfo { - int width; - int height; - uint16 bits_per_sample; - uint16 samples_per_pixel; - uint16 sample_format; - uint16 photometric; - bool host_big_endian; - bool file_big_endian; - - TiffInfo(const TiffHandle& tif, const TiffHeader& header); - - bool mapsToBinaryOrIndexed8() const; + int width; + int height; + uint16 bits_per_sample; + uint16 samples_per_pixel; + uint16 sample_format; + uint16 photometric; + bool host_big_endian; + bool file_big_endian; + + TiffInfo(const TiffHandle& tif, const TiffHeader& header); + + bool mapsToBinaryOrIndexed8() const; }; TiffReader::TiffInfo::TiffInfo(const TiffHandle& tif, const TiffHeader& header) - : width(0), - height(0), - bits_per_sample(1), - samples_per_pixel(1), - sample_format(SAMPLEFORMAT_UINT), - photometric(PHOTOMETRIC_MINISBLACK), - host_big_endian(QSysInfo::ByteOrder == QSysInfo::BigEndian), - file_big_endian(header.signature() == TiffHeader::TIFF_BIG_ENDIAN) { - uint16 compression = 1; - TIFFGetField(tif.handle(), TIFFTAG_COMPRESSION, &compression); - switch (compression) { - case COMPRESSION_CCITTFAX3: - case COMPRESSION_CCITTFAX4: - case COMPRESSION_CCITTRLE: - case COMPRESSION_CCITTRLEW: - photometric = PHOTOMETRIC_MINISWHITE; - break; - default: - break; - } - - TIFFGetField(tif.handle(), TIFFTAG_IMAGEWIDTH, &width); - TIFFGetField(tif.handle(), TIFFTAG_IMAGELENGTH, &height); - TIFFGetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, &bits_per_sample); - TIFFGetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); - TIFFGetField(tif.handle(), TIFFTAG_SAMPLEFORMAT, &sample_format); - TIFFGetField(tif.handle(), TIFFTAG_PHOTOMETRIC, &photometric); + : width(0), + height(0), + bits_per_sample(1), + samples_per_pixel(1), + sample_format(SAMPLEFORMAT_UINT), + photometric(PHOTOMETRIC_MINISBLACK), + host_big_endian(QSysInfo::ByteOrder == QSysInfo::BigEndian), + file_big_endian(header.signature() == TiffHeader::TIFF_BIG_ENDIAN) { + uint16 compression = 1; + TIFFGetField(tif.handle(), TIFFTAG_COMPRESSION, &compression); + switch (compression) { + case COMPRESSION_CCITTFAX3: + case COMPRESSION_CCITTFAX4: + case COMPRESSION_CCITTRLE: + case COMPRESSION_CCITTRLEW: + photometric = PHOTOMETRIC_MINISWHITE; + break; + default: + break; + } + + TIFFGetField(tif.handle(), TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(tif.handle(), TIFFTAG_IMAGELENGTH, &height); + TIFFGetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + TIFFGetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); + TIFFGetField(tif.handle(), TIFFTAG_SAMPLEFORMAT, &sample_format); + TIFFGetField(tif.handle(), TIFFTAG_PHOTOMETRIC, &photometric); } bool TiffReader::TiffInfo::mapsToBinaryOrIndexed8() const { - if ((samples_per_pixel != 1) || (sample_format != SAMPLEFORMAT_UINT) || (bits_per_sample > 8)) { - return false; - } - - switch (photometric) { - case PHOTOMETRIC_PALETTE: - case PHOTOMETRIC_MINISBLACK: - case PHOTOMETRIC_MINISWHITE: - return true; - default: - break; - } - + if ((samples_per_pixel != 1) || (sample_format != SAMPLEFORMAT_UINT) || (bits_per_sample > 8)) { return false; + } + + switch (photometric) { + case PHOTOMETRIC_PALETTE: + case PHOTOMETRIC_MINISBLACK: + case PHOTOMETRIC_MINISWHITE: + return true; + default: + break; + } + + return false; } static tsize_t deviceRead(thandle_t context, tdata_t data, tsize_t size) { - auto* dev = (QIODevice*) context; + auto* dev = (QIODevice*) context; - return (tsize_t) dev->read(static_cast(data), size); + return (tsize_t) dev->read(static_cast(data), size); } static tsize_t deviceWrite(thandle_t context, tdata_t data, tsize_t size) { - // Not implemented. - return 0; + // Not implemented. + return 0; } static toff_t deviceSeek(thandle_t context, toff_t offset, int whence) { - auto* dev = (QIODevice*) context; - - switch (whence) { - case SEEK_SET: - dev->seek(offset); - break; - case SEEK_CUR: - dev->seek(dev->pos() + offset); - break; - case SEEK_END: - dev->seek(dev->size() + offset); - break; - default: - break; - } - - return dev->pos(); + auto* dev = (QIODevice*) context; + + switch (whence) { + case SEEK_SET: + dev->seek(offset); + break; + case SEEK_CUR: + dev->seek(dev->pos() + offset); + break; + case SEEK_END: + dev->seek(dev->size() + offset); + break; + default: + break; + } + + return dev->pos(); } static int deviceClose(thandle_t context) { - auto* dev = (QIODevice*) context; - dev->close(); + auto* dev = (QIODevice*) context; + dev->close(); - return 0; + return 0; } static toff_t deviceSize(thandle_t context) { - auto* dev = (QIODevice*) context; + auto* dev = (QIODevice*) context; - return dev->size(); + return dev->size(); } static int deviceMap(thandle_t, tdata_t*, toff_t*) { - // Not implemented. - return 0; + // Not implemented. + return 0; } static void deviceUnmap(thandle_t, tdata_t, toff_t) { - // Not implemented. + // Not implemented. } bool TiffReader::canRead(QIODevice& device) { - if (!device.isReadable()) { - return false; - } - if (device.isSequential()) { - // libtiff needs to be able to seek. - return false; - } + if (!device.isReadable()) { + return false; + } + if (device.isSequential()) { + // libtiff needs to be able to seek. + return false; + } - TiffHeader header(readHeader(device)); + TiffHeader header(readHeader(device)); - return checkHeader(header); + return checkHeader(header); } ImageMetadataLoader::Status TiffReader::readMetadata(QIODevice& device, const VirtualFunction& out) { - if (!device.isReadable()) { - return ImageMetadataLoader::GENERIC_ERROR; - } - if (device.isSequential()) { - // libtiff needs to be able to seek. - return ImageMetadataLoader::GENERIC_ERROR; - } - - if (!checkHeader(TiffHeader(readHeader(device)))) { - return ImageMetadataLoader::FORMAT_NOT_RECOGNIZED; - } - - TiffHandle tif(TIFFClientOpen("file", "rBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, - &deviceSize, &deviceMap, &deviceUnmap)); - if (!tif.handle()) { - return ImageMetadataLoader::GENERIC_ERROR; - } - - do { - out(currentPageMetadata(tif)); - } while (TIFFReadDirectory(tif.handle())); - - return ImageMetadataLoader::LOADED; + if (!device.isReadable()) { + return ImageMetadataLoader::GENERIC_ERROR; + } + if (device.isSequential()) { + // libtiff needs to be able to seek. + return ImageMetadataLoader::GENERIC_ERROR; + } + + if (!checkHeader(TiffHeader(readHeader(device)))) { + return ImageMetadataLoader::FORMAT_NOT_RECOGNIZED; + } + + TiffHandle tif(TIFFClientOpen("file", "rBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, + &deviceSize, &deviceMap, &deviceUnmap)); + if (!tif.handle()) { + return ImageMetadataLoader::GENERIC_ERROR; + } + + do { + out(currentPageMetadata(tif)); + } while (TIFFReadDirectory(tif.handle())); + + return ImageMetadataLoader::LOADED; } static void convertAbgrToArgb(const uint32* src, uint32* dst, int count) { - for (int i = 0; i < count; ++i) { - const uint32 src_word = src[i]; - uint32 dst_word = src_word & 0xFF000000; // A - dst_word |= (src_word & 0x00FF0000) >> 16; // B - dst_word |= src_word & 0x0000FF00; // G - dst_word |= (src_word & 0x000000FF) << 16; // R - dst[i] = dst_word; - } + for (int i = 0; i < count; ++i) { + const uint32 src_word = src[i]; + uint32 dst_word = src_word & 0xFF000000; // A + dst_word |= (src_word & 0x00FF0000) >> 16; // B + dst_word |= src_word & 0x0000FF00; // G + dst_word |= (src_word & 0x000000FF) << 16; // R + dst[i] = dst_word; + } } QImage TiffReader::readImage(QIODevice& device, const int page_num) { - if (!device.isReadable()) { - return QImage(); - } - if (device.isSequential()) { - // libtiff needs to be able to seek. - return QImage(); + if (!device.isReadable()) { + return QImage(); + } + if (device.isSequential()) { + // libtiff needs to be able to seek. + return QImage(); + } + + TiffHeader header(readHeader(device)); + if (!checkHeader(header)) { + return QImage(); + } + + TiffHandle tif(TIFFClientOpen("file", "rBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, + &deviceSize, &deviceMap, &deviceUnmap)); + if (!tif.handle()) { + return QImage(); + } + + if (!TIFFSetDirectory(tif.handle(), (uint16) page_num)) { + return QImage(); + } + + const TiffInfo info(tif, header); + + const ImageMetadata metadata(currentPageMetadata(tif)); + + QImage image; + + if (info.mapsToBinaryOrIndexed8()) { + // Common case optimization. + image = extractBinaryOrIndexed8Image(tif, info); + } else { + // General case. + image = QImage(info.width, info.height, info.samples_per_pixel == 3 ? QImage::Format_RGB32 : QImage::Format_ARGB32); + if (image.isNull()) { + throw std::bad_alloc(); } - TiffHeader header(readHeader(device)); - if (!checkHeader(header)) { - return QImage(); - } + // For ABGR -> ARGB conversion. + TiffBuffer tmp_buffer; + const uint32* src_line = nullptr; - TiffHandle tif(TIFFClientOpen("file", "rBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, - &deviceSize, &deviceMap, &deviceUnmap)); - if (!tif.handle()) { + if (image.bytesPerLine() == 4 * info.width) { + // We can avoid creating a temporary buffer in this case. + if (!TIFFReadRGBAImageOriented(tif.handle(), info.width, info.height, (uint32*) image.bits(), ORIENTATION_TOPLEFT, + 0)) { return QImage(); - } - - if (!TIFFSetDirectory(tif.handle(), (uint16) page_num)) { + } + src_line = (const uint32*) image.bits(); + } else { + TiffBuffer(info.width * info.height).swap(tmp_buffer); + if (!TIFFReadRGBAImageOriented(tif.handle(), info.width, info.height, tmp_buffer.data(), ORIENTATION_TOPLEFT, + 0)) { return QImage(); + } + src_line = tmp_buffer.data(); } - const TiffInfo info(tif, header); - - const ImageMetadata metadata(currentPageMetadata(tif)); - - QImage image; - - if (info.mapsToBinaryOrIndexed8()) { - // Common case optimization. - image = extractBinaryOrIndexed8Image(tif, info); - } else { - // General case. - image = QImage(info.width, info.height, - info.samples_per_pixel == 3 ? QImage::Format_RGB32 : QImage::Format_ARGB32); - if (image.isNull()) { - throw std::bad_alloc(); - } - - // For ABGR -> ARGB conversion. - TiffBuffer tmp_buffer; - const uint32* src_line = nullptr; - - if (image.bytesPerLine() == 4 * info.width) { - // We can avoid creating a temporary buffer in this case. - if (!TIFFReadRGBAImageOriented(tif.handle(), info.width, info.height, (uint32*) image.bits(), - ORIENTATION_TOPLEFT, 0)) { - return QImage(); - } - src_line = (const uint32*) image.bits(); - } else { - TiffBuffer(info.width * info.height).swap(tmp_buffer); - if (!TIFFReadRGBAImageOriented(tif.handle(), info.width, info.height, tmp_buffer.data(), - ORIENTATION_TOPLEFT, 0)) { - return QImage(); - } - src_line = tmp_buffer.data(); - } - - auto* dst_line = (uint32*) image.bits(); - assert(image.bytesPerLine() % 4 == 0); - const int dst_stride = image.bytesPerLine() / 4; - for (int y = 0; y < info.height; ++y) { - convertAbgrToArgb(src_line, dst_line, info.width); - src_line += info.width; - dst_line += dst_stride; - } + auto* dst_line = (uint32*) image.bits(); + assert(image.bytesPerLine() % 4 == 0); + const int dst_stride = image.bytesPerLine() / 4; + for (int y = 0; y < info.height; ++y) { + convertAbgrToArgb(src_line, dst_line, info.width); + src_line += info.width; + dst_line += dst_stride; } + } - if (!metadata.dpi().isNull()) { - const Dpm dpm(metadata.dpi()); - image.setDotsPerMeterX(dpm.horizontal()); - image.setDotsPerMeterY(dpm.vertical()); - } + if (!metadata.dpi().isNull()) { + const Dpm dpm(metadata.dpi()); + image.setDotsPerMeterX(dpm.horizontal()); + image.setDotsPerMeterY(dpm.vertical()); + } - return image; + return image; } // TiffReader::readImage TiffReader::TiffHeader TiffReader::readHeader(QIODevice& device) { - unsigned char data[4]; - if (device.peek((char*) data, sizeof(data)) != sizeof(data)) { - return TiffHeader(); - } + unsigned char data[4]; + if (device.peek((char*) data, sizeof(data)) != sizeof(data)) { + return TiffHeader(); + } - const uint16 version_byte0 = data[2]; - const uint16 version_byte1 = data[3]; + const uint16 version_byte0 = data[2]; + const uint16 version_byte1 = data[3]; - if ((data[0] == 0x4d) && (data[1] == 0x4d)) { - const uint16 version = (version_byte0 << 8) + version_byte1; + if ((data[0] == 0x4d) && (data[1] == 0x4d)) { + const uint16 version = (version_byte0 << 8) + version_byte1; - return TiffHeader(TiffHeader::TIFF_BIG_ENDIAN, version); - } else if ((data[0] == 0x49) && (data[1] == 0x49)) { - const uint16 version = (version_byte1 << 8) + version_byte0; + return TiffHeader(TiffHeader::TIFF_BIG_ENDIAN, version); + } else if ((data[0] == 0x49) && (data[1] == 0x49)) { + const uint16 version = (version_byte1 << 8) + version_byte0; - return TiffHeader(TiffHeader::TIFF_LITTLE_ENDIAN, version); - } else { - return TiffHeader(); - } + return TiffHeader(TiffHeader::TIFF_LITTLE_ENDIAN, version); + } else { + return TiffHeader(); + } } bool TiffReader::checkHeader(const TiffHeader& header) { - if (header.signature() == TiffHeader::INVALID_SIGNATURE) { - return false; - } - if ((header.version() != 42) && (header.version() != 43)) { - return false; - } + if (header.signature() == TiffHeader::INVALID_SIGNATURE) { + return false; + } + if ((header.version() != 42) && (header.version() != 43)) { + return false; + } - return true; + return true; } ImageMetadata TiffReader::currentPageMetadata(const TiffHandle& tif) { - uint32 width = 0, height = 0; - float xres = 0, yres = 0; - uint16 res_unit = 0; - TIFFGetField(tif.handle(), TIFFTAG_IMAGEWIDTH, &width); - TIFFGetField(tif.handle(), TIFFTAG_IMAGELENGTH, &height); - TIFFGetField(tif.handle(), TIFFTAG_XRESOLUTION, &xres); - TIFFGetField(tif.handle(), TIFFTAG_YRESOLUTION, &yres); - TIFFGetFieldDefaulted(tif.handle(), TIFFTAG_RESOLUTIONUNIT, &res_unit); - - return ImageMetadata(QSize(width, height), getDpi(xres, yres, res_unit)); + uint32 width = 0, height = 0; + float xres = 0, yres = 0; + uint16 res_unit = 0; + TIFFGetField(tif.handle(), TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(tif.handle(), TIFFTAG_IMAGELENGTH, &height); + TIFFGetField(tif.handle(), TIFFTAG_XRESOLUTION, &xres); + TIFFGetField(tif.handle(), TIFFTAG_YRESOLUTION, &yres); + TIFFGetFieldDefaulted(tif.handle(), TIFFTAG_RESOLUTIONUNIT, &res_unit); + + return ImageMetadata(QSize(width, height), getDpi(xres, yres, res_unit)); } Dpi TiffReader::getDpi(float xres, float yres, unsigned res_unit) { - switch (res_unit) { - case RESUNIT_INCH: // inch - return Dpi(qRound(xres), qRound(yres)); - case RESUNIT_CENTIMETER: // cm - return Dpm(qRound(xres * 100), qRound(yres * 100)); - default: - break; - } - - return Dpi(); + switch (res_unit) { + case RESUNIT_INCH: // inch + return Dpi(qRound(xres), qRound(yres)); + case RESUNIT_CENTIMETER: // cm + return Dpm(qRound(xres * 100), qRound(yres * 100)); + default: + break; + } + + return Dpi(); } QImage TiffReader::extractBinaryOrIndexed8Image(const TiffHandle& tif, const TiffInfo& info) { - QImage::Format format = QImage::Format_Indexed8; - if (info.bits_per_sample == 1) { - // Because we specify B option when opening, we can - // always use Format_Mono, and not Format_MonoLSB. - format = QImage::Format_Mono; - } - - QImage image(info.width, info.height, format); - if (image.isNull()) { - throw std::bad_alloc(); - } - - const int num_colors = 1 << info.bits_per_sample; - image.setColorCount(num_colors); - - if (info.photometric == PHOTOMETRIC_PALETTE) { - uint16* pr = nullptr; - uint16* pg = nullptr; - uint16* pb = nullptr; - TIFFGetField(tif.handle(), TIFFTAG_COLORMAP, &pr, &pg, &pb); - if (!pr || !pg || !pb) { - return QImage(); - } - if (info.host_big_endian != info.file_big_endian) { - TIFFSwabArrayOfShort(pr, num_colors); - TIFFSwabArrayOfShort(pg, num_colors); - TIFFSwabArrayOfShort(pb, num_colors); - } - const double f = 255.0 / 65535.0; - for (int i = 0; i < num_colors; ++i) { - const auto r = (uint32) std::lround(pr[i] * f); - const auto g = (uint32) std::lround(pg[i] * f); - const auto b = (uint32) std::lround(pb[i] * f); - const uint32 a = 0xFF000000; - image.setColor(i, a | (r << 16) | (g << 8) | b); - } - } else if (info.photometric == PHOTOMETRIC_MINISBLACK) { - const double f = 255.0 / (num_colors - 1); - for (int i = 0; i < num_colors; ++i) { - const auto gray = (int) std::lround(i * f); - image.setColor(i, qRgb(gray, gray, gray)); - } - } else if (info.photometric == PHOTOMETRIC_MINISWHITE) { - const double f = 255.0 / (num_colors - 1); - int c = num_colors - 1; - for (int i = 0; i < num_colors; ++i, --c) { - const auto gray = (int) std::lround(c * f); - image.setColor(i, qRgb(gray, gray, gray)); - } - } else { - return QImage(); - } - - if ((info.bits_per_sample == 1) || (info.bits_per_sample == 8)) { - readLines(tif, image); - } else { - readAndUnpackLines(tif, info, image); - } - - return image; + QImage::Format format = QImage::Format_Indexed8; + if (info.bits_per_sample == 1) { + // Because we specify B option when opening, we can + // always use Format_Mono, and not Format_MonoLSB. + format = QImage::Format_Mono; + } + + QImage image(info.width, info.height, format); + if (image.isNull()) { + throw std::bad_alloc(); + } + + const int num_colors = 1 << info.bits_per_sample; + image.setColorCount(num_colors); + + if (info.photometric == PHOTOMETRIC_PALETTE) { + uint16* pr = nullptr; + uint16* pg = nullptr; + uint16* pb = nullptr; + TIFFGetField(tif.handle(), TIFFTAG_COLORMAP, &pr, &pg, &pb); + if (!pr || !pg || !pb) { + return QImage(); + } + if (info.host_big_endian != info.file_big_endian) { + TIFFSwabArrayOfShort(pr, num_colors); + TIFFSwabArrayOfShort(pg, num_colors); + TIFFSwabArrayOfShort(pb, num_colors); + } + const double f = 255.0 / 65535.0; + for (int i = 0; i < num_colors; ++i) { + const auto r = (uint32) std::lround(pr[i] * f); + const auto g = (uint32) std::lround(pg[i] * f); + const auto b = (uint32) std::lround(pb[i] * f); + const uint32 a = 0xFF000000; + image.setColor(i, a | (r << 16) | (g << 8) | b); + } + } else if (info.photometric == PHOTOMETRIC_MINISBLACK) { + const double f = 255.0 / (num_colors - 1); + for (int i = 0; i < num_colors; ++i) { + const auto gray = (int) std::lround(i * f); + image.setColor(i, qRgb(gray, gray, gray)); + } + } else if (info.photometric == PHOTOMETRIC_MINISWHITE) { + const double f = 255.0 / (num_colors - 1); + int c = num_colors - 1; + for (int i = 0; i < num_colors; ++i, --c) { + const auto gray = (int) std::lround(c * f); + image.setColor(i, qRgb(gray, gray, gray)); + } + } else { + return QImage(); + } + + if ((info.bits_per_sample == 1) || (info.bits_per_sample == 8)) { + readLines(tif, image); + } else { + readAndUnpackLines(tif, info, image); + } + + return image; } // TiffReader::extractBinaryOrIndexed8Image void TiffReader::readLines(const TiffHandle& tif, QImage& image) { - const int height = image.height(); - for (int y = 0; y < height; ++y) { - TIFFReadScanline(tif.handle(), image.scanLine(y), y); - } + const int height = image.height(); + for (int y = 0; y < height; ++y) { + TIFFReadScanline(tif.handle(), image.scanLine(y), y); + } } void TiffReader::readAndUnpackLines(const TiffHandle& tif, const TiffInfo& info, QImage& image) { - TiffBuffer buf(TIFFScanlineSize(tif.handle())); - - const int width = image.width(); - const int height = image.height(); - const int bits_per_sample = info.bits_per_sample; - const unsigned dst_mask = (1 << bits_per_sample) - 1; - - for (int y = 0; y < height; ++y) { - TIFFReadScanline(tif.handle(), buf.data(), y); - - unsigned accum = 0; - int bits_in_accum = 0; - - const uint8* src = buf.data(); - auto* dst = image.scanLine(y); - - for (int i = width; i > 0; --i, ++dst) { - while (bits_in_accum < bits_per_sample) { - accum <<= 8; - accum |= *src; - bits_in_accum += 8; - ++src; - } - bits_in_accum -= bits_per_sample; - *dst = static_cast((accum >> bits_in_accum) & dst_mask); - } + TiffBuffer buf(TIFFScanlineSize(tif.handle())); + + const int width = image.width(); + const int height = image.height(); + const int bits_per_sample = info.bits_per_sample; + const unsigned dst_mask = (1 << bits_per_sample) - 1; + + for (int y = 0; y < height; ++y) { + TIFFReadScanline(tif.handle(), buf.data(), y); + + unsigned accum = 0; + int bits_in_accum = 0; + + const uint8* src = buf.data(); + auto* dst = image.scanLine(y); + + for (int i = width; i > 0; --i, ++dst) { + while (bits_in_accum < bits_per_sample) { + accum <<= 8; + accum |= *src; + bits_in_accum += 8; + ++src; + } + bits_in_accum -= bits_per_sample; + *dst = static_cast((accum >> bits_in_accum) & dst_mask); } + } } diff --git a/TiffReader.h b/TiffReader.h index 424d5d785..253b11a9b 100644 --- a/TiffReader.h +++ b/TiffReader.h @@ -28,45 +28,45 @@ class ImageMetadata; class Dpi; class TiffReader { -public: - static bool canRead(QIODevice& device); + public: + static bool canRead(QIODevice& device); - static ImageMetadataLoader::Status readMetadata(QIODevice& device, - const VirtualFunction& out); + static ImageMetadataLoader::Status readMetadata(QIODevice& device, + const VirtualFunction& out); - /** - * \brief Reads the image from io device to QImage. - * - * \param device The device to read from. This device must be - * opened for reading and must be seekable. - * \param page_num A zero-based page number within a multi-page - * TIFF file. - * \return The resulting image, or a null image in case of failure. - */ - static QImage readImage(QIODevice& device, int page_num = 0); + /** + * \brief Reads the image from io device to QImage. + * + * \param device The device to read from. This device must be + * opened for reading and must be seekable. + * \param page_num A zero-based page number within a multi-page + * TIFF file. + * \return The resulting image, or a null image in case of failure. + */ + static QImage readImage(QIODevice& device, int page_num = 0); -private: - class TiffHeader; - class TiffHandle; + private: + class TiffHeader; + class TiffHandle; - struct TiffInfo; + struct TiffInfo; - template - class TiffBuffer; + template + class TiffBuffer; - static TiffHeader readHeader(QIODevice& device); + static TiffHeader readHeader(QIODevice& device); - static bool checkHeader(const TiffHeader& header); + static bool checkHeader(const TiffHeader& header); - static ImageMetadata currentPageMetadata(const TiffHandle& tif); + static ImageMetadata currentPageMetadata(const TiffHandle& tif); - static Dpi getDpi(float xres, float yres, unsigned res_unit); + static Dpi getDpi(float xres, float yres, unsigned res_unit); - static QImage extractBinaryOrIndexed8Image(const TiffHandle& tif, const TiffInfo& info); + static QImage extractBinaryOrIndexed8Image(const TiffHandle& tif, const TiffInfo& info); - static void readLines(const TiffHandle& tif, QImage& image); + static void readLines(const TiffHandle& tif, QImage& image); - static void readAndUnpackLines(const TiffHandle& tif, const TiffInfo& info, QImage& image); + static void readAndUnpackLines(const TiffHandle& tif, const TiffInfo& info, QImage& image); }; diff --git a/TiffWriter.cpp b/TiffWriter.cpp index 1408d1ec7..6c837e7de 100644 --- a/TiffWriter.cpp +++ b/TiffWriter.cpp @@ -17,399 +17,395 @@ */ #include "TiffWriter.h" -#include "imageproc/Grayscale.h" -#include "Dpm.h" -#include "imageproc/Constants.h" -#include #include -#include -#include +#include #include +#include #include +#include +#include "Dpm.h" +#include "imageproc/Constants.h" +#include "imageproc/Grayscale.h" /** * m_reverseBitsLUT[byte] gives the same byte, but with bit order reversed. */ const uint8_t TiffWriter::m_reverseBitsLUT[256] - = {0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, - 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, - 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, - 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, - 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, - 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, - 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, - 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, - 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, - 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, - 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, - 0x3f, 0xbf, 0x7f, 0xff}; + = {0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, + 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, + 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, + 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, + 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, + 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, + 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, + 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, + 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, + 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, + 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, + 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, + 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff}; class TiffWriter::TiffHandle { -public: - explicit TiffHandle(TIFF* handle) : m_pHandle(handle) { - } + public: + explicit TiffHandle(TIFF* handle) : m_handle(handle) {} - ~TiffHandle() { - if (m_pHandle) { - TIFFClose(m_pHandle); - } + ~TiffHandle() { + if (m_handle) { + TIFFClose(m_handle); } + } - TIFF* handle() const { - return m_pHandle; - } + TIFF* handle() const { return m_handle; } -private: - TIFF* m_pHandle; + private: + TIFF* m_handle; }; static tsize_t deviceRead(thandle_t context, tdata_t data, tsize_t size) { - // Not implemented. - return 0; + // Not implemented. + return 0; } static tsize_t deviceWrite(thandle_t context, tdata_t data, tsize_t size) { - auto* dev = (QIODevice*) context; + auto* dev = (QIODevice*) context; - return (tsize_t) dev->write(static_cast(data), size); + return (tsize_t) dev->write(static_cast(data), size); } static toff_t deviceSeek(thandle_t context, toff_t offset, int whence) { - auto* dev = (QIODevice*) context; - - switch (whence) { - case SEEK_SET: - dev->seek(offset); - break; - case SEEK_CUR: - dev->seek(dev->pos() + offset); - break; - case SEEK_END: - dev->seek(dev->size() + offset); - break; - default: - break; - } - - return dev->pos(); + auto* dev = (QIODevice*) context; + + switch (whence) { + case SEEK_SET: + dev->seek(offset); + break; + case SEEK_CUR: + dev->seek(dev->pos() + offset); + break; + case SEEK_END: + dev->seek(dev->size() + offset); + break; + default: + break; + } + + return dev->pos(); } static int deviceClose(thandle_t context) { - auto* dev = (QIODevice*) context; - dev->close(); + auto* dev = (QIODevice*) context; + dev->close(); - return 0; + return 0; } static toff_t deviceSize(thandle_t context) { - auto* dev = (QIODevice*) context; + auto* dev = (QIODevice*) context; - return dev->size(); + return dev->size(); } static int deviceMap(thandle_t, tdata_t*, toff_t*) { - // Not implemented. - return 0; + // Not implemented. + return 0; } static void deviceUnmap(thandle_t, tdata_t, toff_t) { - // Not implemented. + // Not implemented. } bool TiffWriter::writeImage(const QString& file_path, const QImage& image) { - if (image.isNull()) { - return false; - } + if (image.isNull()) { + return false; + } - QFile file(file_path); - if (!file.open(QFile::WriteOnly)) { - return false; - } + QFile file(file_path); + if (!file.open(QFile::WriteOnly)) { + return false; + } - if (!writeImage(file, image)) { - file.remove(); + if (!writeImage(file, image)) { + file.remove(); - return false; - } + return false; + } - return true; + return true; } bool TiffWriter::writeImage(QIODevice& device, const QImage& image) { - if (image.isNull()) { - return false; - } - if (!device.isWritable()) { - return false; - } - if (device.isSequential()) { - // libtiff needs to be able to seek. - return false; - } - - TiffHandle tif(TIFFClientOpen( - // Libtiff seems to be buggy with L or H flags, - // so we use B. - "file", "wBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, &deviceSize, &deviceMap, - &deviceUnmap)); - if (!tif.handle()) { - return false; - } - - TIFFSetField(tif.handle(), TIFFTAG_IMAGEWIDTH, uint32(image.width())); - TIFFSetField(tif.handle(), TIFFTAG_IMAGELENGTH, uint32(image.height())); - TIFFSetField(tif.handle(), TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); - TIFFSetField(tif.handle(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - setDpm(tif, Dpm(image)); - - switch (image.format()) { - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - case QImage::Format_Indexed8: - return writeBitonalOrIndexed8Image(tif, image); - default:; - } - if (image.hasAlphaChannel()) { - return writeARGB32Image(tif, image.convertToFormat(QImage::Format_ARGB32)); - } else { - return writeRGB32Image(tif, image.convertToFormat(QImage::Format_RGB32)); - } + if (image.isNull()) { + return false; + } + if (!device.isWritable()) { + return false; + } + if (device.isSequential()) { + // libtiff needs to be able to seek. + return false; + } + + TiffHandle tif(TIFFClientOpen( + // Libtiff seems to be buggy with L or H flags, + // so we use B. + "file", "wBm", &device, &deviceRead, &deviceWrite, &deviceSeek, &deviceClose, &deviceSize, &deviceMap, + &deviceUnmap)); + if (!tif.handle()) { + return false; + } + + TIFFSetField(tif.handle(), TIFFTAG_IMAGEWIDTH, uint32(image.width())); + TIFFSetField(tif.handle(), TIFFTAG_IMAGELENGTH, uint32(image.height())); + TIFFSetField(tif.handle(), TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif.handle(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + setDpm(tif, Dpm(image)); + + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + return writeBitonalOrIndexed8Image(tif, image); + default:; + } + if (image.hasAlphaChannel()) { + return writeARGB32Image(tif, image.convertToFormat(QImage::Format_ARGB32)); + } else { + return writeRGB32Image(tif, image.convertToFormat(QImage::Format_RGB32)); + } } // TiffWriter::writeImage /** * Set the physical resolution, if it's defined. */ void TiffWriter::setDpm(const TiffHandle& tif, const Dpm& dpm) { - using namespace imageproc::constants; - - if (dpm.isNull()) { - return; - } - - auto xres = static_cast(0.01 * dpm.horizontal()); // cm - auto yres = static_cast(0.01 * dpm.vertical()); // cm - uint16 unit = RESUNIT_CENTIMETER; - - // If we have a round (or almost round) DPI, then - // write it as DPI rather than dots per cm. - const double xdpi = dpm.horizontal() * DPM2DPI; - const double ydpi = dpm.vertical() * DPM2DPI; - const double rounded_xdpi = std::floor(xdpi + 0.5); - const double rounded_ydpi = std::floor(ydpi + 0.5); - if ((std::fabs(xdpi - rounded_xdpi) < 0.02) && (std::fabs(ydpi - rounded_ydpi) < 0.02)) { - xres = (float) rounded_xdpi; - yres = (float) rounded_ydpi; - unit = RESUNIT_INCH; - } - - TIFFSetField(tif.handle(), TIFFTAG_XRESOLUTION, xres); - TIFFSetField(tif.handle(), TIFFTAG_YRESOLUTION, yres); - TIFFSetField(tif.handle(), TIFFTAG_RESOLUTIONUNIT, unit); + using namespace imageproc::constants; + + if (dpm.isNull()) { + return; + } + + auto xres = static_cast(0.01 * dpm.horizontal()); // cm + auto yres = static_cast(0.01 * dpm.vertical()); // cm + uint16 unit = RESUNIT_CENTIMETER; + + // If we have a round (or almost round) DPI, then + // write it as DPI rather than dots per cm. + const double xdpi = dpm.horizontal() * DPM2DPI; + const double ydpi = dpm.vertical() * DPM2DPI; + const double rounded_xdpi = std::floor(xdpi + 0.5); + const double rounded_ydpi = std::floor(ydpi + 0.5); + if ((std::fabs(xdpi - rounded_xdpi) < 0.02) && (std::fabs(ydpi - rounded_ydpi) < 0.02)) { + xres = (float) rounded_xdpi; + yres = (float) rounded_ydpi; + unit = RESUNIT_INCH; + } + + TIFFSetField(tif.handle(), TIFFTAG_XRESOLUTION, xres); + TIFFSetField(tif.handle(), TIFFTAG_YRESOLUTION, yres); + TIFFSetField(tif.handle(), TIFFTAG_RESOLUTIONUNIT, unit); } bool TiffWriter::writeBitonalOrIndexed8Image(const TiffHandle& tif, const QImage& image) { - TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(1)); + TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(1)); + + uint16 bits_per_sample = 8; + uint16 photometric = PHOTOMETRIC_PALETTE; + if (image.isGrayscale()) { + photometric = PHOTOMETRIC_MINISBLACK; + } + + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + bits_per_sample = 1; + if (image.colorCount() < 2) { + photometric = PHOTOMETRIC_MINISWHITE; + } else { + // Some programs don't understand + // palettized binary images, so don't + // use a palette for black and white images. + const uint32_t c0 = image.color(0); + const uint32_t c1 = image.color(1); + if ((c0 == 0xffffffff) && (c1 == 0xff000000)) { + photometric = PHOTOMETRIC_MINISWHITE; + } else if ((c0 == 0xff000000) && (c1 == 0xffffffff)) { + photometric = PHOTOMETRIC_MINISBLACK; + } + } + break; + default:; + } - uint16 bits_per_sample = 8; - uint16 photometric = PHOTOMETRIC_PALETTE; - if (image.isGrayscale()) { - photometric = PHOTOMETRIC_MINISBLACK; - } + if (image.format() == QImage::Format_Indexed8) { + TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, + uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); + } else { + TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, + uint16(QSettings().value("settings/bw_compression", COMPRESSION_CCITTFAX4).toInt())); + } - switch (image.format()) { - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - bits_per_sample = 1; - if (image.colorCount() < 2) { - photometric = PHOTOMETRIC_MINISWHITE; - } else { - // Some programs don't understand - // palettized binary images, so don't - // use a palette for black and white images. - const uint32_t c0 = image.color(0); - const uint32_t c1 = image.color(1); - if ((c0 == 0xffffffff) && (c1 == 0xff000000)) { - photometric = PHOTOMETRIC_MINISWHITE; - } else if ((c0 == 0xff000000) && (c1 == 0xffffffff)) { - photometric = PHOTOMETRIC_MINISBLACK; - } - } - break; - default:; - } + TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, bits_per_sample); + TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, photometric); - if (image.format() == QImage::Format_Indexed8) { - TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, - uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); - } else { - TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, - uint16(QSettings().value("settings/bw_compression", COMPRESSION_CCITTFAX4).toInt())); + if (photometric == PHOTOMETRIC_PALETTE) { + const int num_colors = 1 << bits_per_sample; + QVector color_table(image.colorTable()); + if (color_table.size() > num_colors) { + color_table.resize(num_colors); } - - TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, bits_per_sample); - TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, photometric); - - if (photometric == PHOTOMETRIC_PALETTE) { - const int num_colors = 1 << bits_per_sample; - QVector color_table(image.colorTable()); - if (color_table.size() > num_colors) { - color_table.resize(num_colors); - } - std::vector pr(num_colors, 0); - std::vector pg(num_colors, 0); - std::vector pb(num_colors, 0); - for (int i = 0; i < color_table.size(); ++i) { - const QRgb rgb = color_table[i]; - pr[i] = static_cast((0xFFFF * qRed(rgb) + 128) / 255); - pg[i] = static_cast((0xFFFF * qGreen(rgb) + 128) / 255); - pb[i] = static_cast((0xFFFF * qBlue(rgb) + 128) / 255); - } - TIFFSetField(tif.handle(), TIFFTAG_COLORMAP, &pr[0], &pg[0], &pb[0]); + std::vector pr(num_colors, 0); + std::vector pg(num_colors, 0); + std::vector pb(num_colors, 0); + for (int i = 0; i < color_table.size(); ++i) { + const QRgb rgb = color_table[i]; + pr[i] = static_cast((0xFFFF * qRed(rgb) + 128) / 255); + pg[i] = static_cast((0xFFFF * qGreen(rgb) + 128) / 255); + pb[i] = static_cast((0xFFFF * qBlue(rgb) + 128) / 255); } - - if (image.format() == QImage::Format_Indexed8) { - return write8bitLines(tif, image); + TIFFSetField(tif.handle(), TIFFTAG_COLORMAP, &pr[0], &pg[0], &pb[0]); + } + + if (image.format() == QImage::Format_Indexed8) { + return write8bitLines(tif, image); + } else { + if (image.format() == QImage::Format_MonoLSB) { + return writeBinaryLinesReversed(tif, image); } else { - if (image.format() == QImage::Format_MonoLSB) { - return writeBinaryLinesReversed(tif, image); - } else { - return writeBinaryLinesAsIs(tif, image); - } + return writeBinaryLinesAsIs(tif, image); } + } } // TiffWriter::writeBitonalOrIndexed8Image bool TiffWriter::writeRGB32Image(const TiffHandle& tif, const QImage& image) { - assert(image.format() == QImage::Format_RGB32); - - TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(3)); - TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, - uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); - TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); - TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - - const int width = image.width(); - const int height = image.height(); - - std::vector tmp_line(width * 3); - - // Libtiff expects "RR GG BB" sequences regardless of CPU byte order. - - for (int y = 0; y < height; ++y) { - const auto* p_src = (const uint32_t*) image.scanLine(y); - uint8_t* p_dst = &tmp_line[0]; - for (int x = 0; x < width; ++x) { - const uint32_t ARGB = *p_src; - p_dst[0] = static_cast(ARGB >> 16); - p_dst[1] = static_cast(ARGB >> 8); - p_dst[2] = static_cast(ARGB); - ++p_src; - p_dst += 3; - } - if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { - return false; - } + assert(image.format() == QImage::Format_RGB32); + + TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(3)); + TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, + uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); + TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); + TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + + const int width = image.width(); + const int height = image.height(); + + std::vector tmp_line(width * 3); + + // Libtiff expects "RR GG BB" sequences regardless of CPU byte order. + + for (int y = 0; y < height; ++y) { + const auto* p_src = (const uint32_t*) image.scanLine(y); + uint8_t* p_dst = &tmp_line[0]; + for (int x = 0; x < width; ++x) { + const uint32_t ARGB = *p_src; + p_dst[0] = static_cast(ARGB >> 16); + p_dst[1] = static_cast(ARGB >> 8); + p_dst[2] = static_cast(ARGB); + ++p_src; + p_dst += 3; } + if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { + return false; + } + } - return true; + return true; } // TiffWriter::writeRGB32Image bool TiffWriter::writeARGB32Image(const TiffHandle& tif, const QImage& image) { - assert(image.format() == QImage::Format_ARGB32); - - TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(4)); - TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, - uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); - TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); - TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - - const int width = image.width(); - const int height = image.height(); - - std::vector tmp_line(width * 4); - - // Libtiff expects "RR GG BB AA" sequences regardless of CPU byte order. - - for (int y = 0; y < height; ++y) { - const auto* p_src = (const uint32_t*) image.scanLine(y); - uint8_t* p_dst = &tmp_line[0]; - for (int x = 0; x < width; ++x) { - const uint32_t ARGB = *p_src; - p_dst[0] = static_cast(ARGB >> 16); - p_dst[1] = static_cast(ARGB >> 8); - p_dst[2] = static_cast(ARGB); - p_dst[3] = static_cast(ARGB >> 24); - ++p_src; - p_dst += 4; - } - if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { - return false; - } + assert(image.format() == QImage::Format_ARGB32); + + TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(4)); + TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, + uint16(QSettings().value("settings/color_compression", COMPRESSION_LZW).toInt())); + TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); + TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + + const int width = image.width(); + const int height = image.height(); + + std::vector tmp_line(width * 4); + + // Libtiff expects "RR GG BB AA" sequences regardless of CPU byte order. + + for (int y = 0; y < height; ++y) { + const auto* p_src = (const uint32_t*) image.scanLine(y); + uint8_t* p_dst = &tmp_line[0]; + for (int x = 0; x < width; ++x) { + const uint32_t ARGB = *p_src; + p_dst[0] = static_cast(ARGB >> 16); + p_dst[1] = static_cast(ARGB >> 8); + p_dst[2] = static_cast(ARGB); + p_dst[3] = static_cast(ARGB >> 24); + ++p_src; + p_dst += 4; } + if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { + return false; + } + } - return true; + return true; } // TiffWriter::writeARGB32Image bool TiffWriter::write8bitLines(const TiffHandle& tif, const QImage& image) { - const int width = image.width(); - const int height = image.height(); - - // TIFFWriteScanline() can actually modify the data you pass it, - // so we have to use a temporary buffer even when no coversion - // is required. - std::vector tmp_line(width, 0); - - for (int y = 0; y < height; ++y) { - const uint8_t* src_line = image.scanLine(y); - memcpy(&tmp_line[0], src_line, tmp_line.size()); - if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { - return false; - } + const int width = image.width(); + const int height = image.height(); + + // TIFFWriteScanline() can actually modify the data you pass it, + // so we have to use a temporary buffer even when no coversion + // is required. + std::vector tmp_line(width, 0); + + for (int y = 0; y < height; ++y) { + const uint8_t* src_line = image.scanLine(y); + memcpy(&tmp_line[0], src_line, tmp_line.size()); + if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { + return false; } + } - return true; + return true; } bool TiffWriter::writeBinaryLinesAsIs(const TiffHandle& tif, const QImage& image) { - const int width = image.width(); - const int height = image.height(); - // TIFFWriteScanline() can actually modify the data you pass it, - // so we have to use a temporary buffer even when no coversion - // is required. - const int bpl = (width + 7) / 8; - std::vector tmp_line(bpl, 0); - - for (int y = 0; y < height; ++y) { - const uint8_t* src_line = image.scanLine(y); - memcpy(&tmp_line[0], src_line, bpl); - if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { - return false; - } + const int width = image.width(); + const int height = image.height(); + // TIFFWriteScanline() can actually modify the data you pass it, + // so we have to use a temporary buffer even when no coversion + // is required. + const int bpl = (width + 7) / 8; + std::vector tmp_line(bpl, 0); + + for (int y = 0; y < height; ++y) { + const uint8_t* src_line = image.scanLine(y); + memcpy(&tmp_line[0], src_line, bpl); + if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { + return false; } + } - return true; + return true; } bool TiffWriter::writeBinaryLinesReversed(const TiffHandle& tif, const QImage& image) { - const int width = image.width(); - const int height = image.height(); + const int width = image.width(); + const int height = image.height(); - const int bpl = (width + 7) / 8; - std::vector tmp_line(bpl, 0); + const int bpl = (width + 7) / 8; + std::vector tmp_line(bpl, 0); - for (int y = 0; y < height; ++y) { - const uint8_t* src_line = image.scanLine(y); - for (int i = 0; i < bpl; ++i) { - tmp_line[i] = m_reverseBitsLUT[src_line[i]]; - } - if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { - return false; - } + for (int y = 0; y < height; ++y) { + const uint8_t* src_line = image.scanLine(y); + for (int i = 0; i < bpl; ++i) { + tmp_line[i] = m_reverseBitsLUT[src_line[i]]; + } + if (TIFFWriteScanline(tif.handle(), &tmp_line[0], y) == -1) { + return false; } + } - return true; + return true; } diff --git a/TiffWriter.h b/TiffWriter.h index 75c27f8fa..8f5d737f8 100644 --- a/TiffWriter.h +++ b/TiffWriter.h @@ -19,9 +19,9 @@ #ifndef TIFFWRITER_H_ #define TIFFWRITER_H_ -#include -#include #include +#include +#include class QIODevice; class QString; @@ -29,44 +29,44 @@ class QImage; class Dpm; class TiffWriter { -public: - /** - * \brief Writes a QImage in TIFF format to a file. - * - * \param file_path The full path to the file. - * \param image The image to write. Writing a null image will fail. - * \return True on success, false on failure. - */ - static bool writeImage(const QString& file_path, const QImage& image); + public: + /** + * \brief Writes a QImage in TIFF format to a file. + * + * \param file_path The full path to the file. + * \param image The image to write. Writing a null image will fail. + * \return True on success, false on failure. + */ + static bool writeImage(const QString& file_path, const QImage& image); - /** - * \brief Writes a QImage in TIFF format to an IO device. - * - * \param device The device to write to. This device must be - * opened for writing and seekable. - * \param image The image to write. Writing a null image will fail. - * \return True on success, false on failure. - */ - static bool writeImage(QIODevice& device, const QImage& image); + /** + * \brief Writes a QImage in TIFF format to an IO device. + * + * \param device The device to write to. This device must be + * opened for writing and seekable. + * \param image The image to write. Writing a null image will fail. + * \return True on success, false on failure. + */ + static bool writeImage(QIODevice& device, const QImage& image); -private: - class TiffHandle; + private: + class TiffHandle; - static void setDpm(const TiffHandle& tif, const Dpm& dpm); + static void setDpm(const TiffHandle& tif, const Dpm& dpm); - static bool writeBitonalOrIndexed8Image(const TiffHandle& tif, const QImage& image); + static bool writeBitonalOrIndexed8Image(const TiffHandle& tif, const QImage& image); - static bool writeRGB32Image(const TiffHandle& tif, const QImage& image); + static bool writeRGB32Image(const TiffHandle& tif, const QImage& image); - static bool writeARGB32Image(const TiffHandle& tif, const QImage& image); + static bool writeARGB32Image(const TiffHandle& tif, const QImage& image); - static bool write8bitLines(const TiffHandle& tif, const QImage& image); + static bool write8bitLines(const TiffHandle& tif, const QImage& image); - static bool writeBinaryLinesAsIs(const TiffHandle& tif, const QImage& image); + static bool writeBinaryLinesAsIs(const TiffHandle& tif, const QImage& image); - static bool writeBinaryLinesReversed(const TiffHandle& tif, const QImage& image); + static bool writeBinaryLinesReversed(const TiffHandle& tif, const QImage& image); - static const uint8_t m_reverseBitsLUT[256]; + static const uint8_t m_reverseBitsLUT[256]; }; diff --git a/Units.cpp b/Units.cpp index 07e7dc34d..f8b18bd5c 100644 --- a/Units.cpp +++ b/Units.cpp @@ -1,55 +1,55 @@ -#include #include "Units.h" +#include QString unitsToString(Units units) { - QString unitsStr; - switch (units) { - case PIXELS: - unitsStr = "px"; - break; - case MILLIMETRES: - unitsStr = "mm"; - break; - case CENTIMETRES: - unitsStr = "cm"; - break; - case INCHES: - unitsStr = "in"; - break; - } + QString unitsStr; + switch (units) { + case PIXELS: + unitsStr = "px"; + break; + case MILLIMETRES: + unitsStr = "mm"; + break; + case CENTIMETRES: + unitsStr = "cm"; + break; + case INCHES: + unitsStr = "in"; + break; + } - return unitsStr; + return unitsStr; } Units unitsFromString(const QString& string) { - if (string == "px") { - return PIXELS; - } else if (string == "cm") { - return CENTIMETRES; - } else if (string == "in") { - return INCHES; - } else { - return MILLIMETRES; - } + if (string == "px") { + return PIXELS; + } else if (string == "cm") { + return CENTIMETRES; + } else if (string == "in") { + return INCHES; + } else { + return MILLIMETRES; + } } QString unitsToLocalizedString(Units units) { - QString unitsStr; - switch (units) { - case PIXELS: - unitsStr = QObject::tr("px"); - break; - case MILLIMETRES: - unitsStr = QObject::tr("mm"); - break; - case CENTIMETRES: - unitsStr = QObject::tr("cm"); - break; - case INCHES: - unitsStr = QObject::tr("in"); - break; - } + QString unitsStr; + switch (units) { + case PIXELS: + unitsStr = QObject::tr("px"); + break; + case MILLIMETRES: + unitsStr = QObject::tr("mm"); + break; + case CENTIMETRES: + unitsStr = QObject::tr("cm"); + break; + case INCHES: + unitsStr = QObject::tr("in"); + break; + } - return unitsStr; + return unitsStr; } \ No newline at end of file diff --git a/UnitsConverter.cpp b/UnitsConverter.cpp index 95ee5b039..5c5bca048 100644 --- a/UnitsConverter.cpp +++ b/UnitsConverter.cpp @@ -2,103 +2,102 @@ #include "UnitsConverter.h" #include "Dpm.h" -UnitsConverter::UnitsConverter(const Dpi& dpi) : dpi(dpi) { -} +UnitsConverter::UnitsConverter(const Dpi& dpi) : m_dpi(dpi) {} void UnitsConverter::convert(double& horizontalValue, double& verticalValue, Units fromUnits, Units toUnits) const { - if (dpi.isNull() || (fromUnits == toUnits)) { - return; - } + if (m_dpi.isNull() || (fromUnits == toUnits)) { + return; + } - auto dpm = Dpm(dpi); - switch (fromUnits) { - case PIXELS: - switch (toUnits) { - case MILLIMETRES: - horizontalValue = horizontalValue / dpm.horizontal() * 1000.; - verticalValue = verticalValue / dpm.vertical() * 1000.; - break; - case CENTIMETRES: - horizontalValue = horizontalValue / dpm.horizontal() * 100.; - verticalValue = verticalValue / dpm.vertical() * 100.; - break; - case INCHES: - horizontalValue /= dpi.horizontal(); - verticalValue /= dpi.vertical(); - break; - default: - break; - } - break; + auto dpm = Dpm(m_dpi); + switch (fromUnits) { + case PIXELS: + switch (toUnits) { case MILLIMETRES: - switch (toUnits) { - case PIXELS: - horizontalValue = horizontalValue / 1000. * dpm.horizontal(); - verticalValue = verticalValue / 1000. * dpm.vertical(); - break; - case CENTIMETRES: - horizontalValue = horizontalValue / 10.; - verticalValue = verticalValue / 10.; - break; - case INCHES: - horizontalValue = horizontalValue / 1000. * dpm.horizontal() / dpi.horizontal(); - verticalValue = verticalValue / 1000. * dpm.vertical() / dpi.vertical(); - break; - default: - break; - } - break; + horizontalValue = horizontalValue / dpm.horizontal() * 1000.; + verticalValue = verticalValue / dpm.vertical() * 1000.; + break; + case CENTIMETRES: + horizontalValue = horizontalValue / dpm.horizontal() * 100.; + verticalValue = verticalValue / dpm.vertical() * 100.; + break; + case INCHES: + horizontalValue /= m_dpi.horizontal(); + verticalValue /= m_dpi.vertical(); + break; + default: + break; + } + break; + case MILLIMETRES: + switch (toUnits) { + case PIXELS: + horizontalValue = horizontalValue / 1000. * dpm.horizontal(); + verticalValue = verticalValue / 1000. * dpm.vertical(); + break; case CENTIMETRES: - switch (toUnits) { - case PIXELS: - horizontalValue = horizontalValue / 100. * dpm.horizontal(); - verticalValue = verticalValue / 100. * dpm.vertical(); - break; - case MILLIMETRES: - horizontalValue = horizontalValue * 10.; - verticalValue = verticalValue * 10.; - break; - case INCHES: - horizontalValue = horizontalValue / 100. * dpm.horizontal() / dpi.horizontal(); - verticalValue = verticalValue / 100. * dpm.vertical() / dpi.vertical(); - break; - default: - break; - } - break; + horizontalValue = horizontalValue / 10.; + verticalValue = verticalValue / 10.; + break; case INCHES: - switch (toUnits) { - case PIXELS: - horizontalValue *= dpi.horizontal(); - verticalValue *= dpi.vertical(); - break; - case MILLIMETRES: - horizontalValue = horizontalValue * dpi.horizontal() / dpm.horizontal() * 1000.; - verticalValue = verticalValue * dpi.vertical() / dpm.vertical() * 1000.; - break; - case CENTIMETRES: - horizontalValue = horizontalValue * dpi.horizontal() / dpm.horizontal() * 100.; - verticalValue = verticalValue * dpi.vertical() / dpm.vertical() * 100.; - break; - default: - break; - } - break; - } + horizontalValue = horizontalValue / 1000. * dpm.horizontal() / m_dpi.horizontal(); + verticalValue = verticalValue / 1000. * dpm.vertical() / m_dpi.vertical(); + break; + default: + break; + } + break; + case CENTIMETRES: + switch (toUnits) { + case PIXELS: + horizontalValue = horizontalValue / 100. * dpm.horizontal(); + verticalValue = verticalValue / 100. * dpm.vertical(); + break; + case MILLIMETRES: + horizontalValue = horizontalValue * 10.; + verticalValue = verticalValue * 10.; + break; + case INCHES: + horizontalValue = horizontalValue / 100. * dpm.horizontal() / m_dpi.horizontal(); + verticalValue = verticalValue / 100. * dpm.vertical() / m_dpi.vertical(); + break; + default: + break; + } + break; + case INCHES: + switch (toUnits) { + case PIXELS: + horizontalValue *= m_dpi.horizontal(); + verticalValue *= m_dpi.vertical(); + break; + case MILLIMETRES: + horizontalValue = horizontalValue * m_dpi.horizontal() / dpm.horizontal() * 1000.; + verticalValue = verticalValue * m_dpi.vertical() / dpm.vertical() * 1000.; + break; + case CENTIMETRES: + horizontalValue = horizontalValue * m_dpi.horizontal() / dpm.horizontal() * 100.; + verticalValue = verticalValue * m_dpi.vertical() / dpm.vertical() * 100.; + break; + default: + break; + } + break; + } } QTransform UnitsConverter::transform(Units fromUnits, Units toUnits) const { - double xScaleFactor = 1.0; - double yScaleFactor = 1.0; - convert(xScaleFactor, yScaleFactor, fromUnits, toUnits); + double xScaleFactor = 1.0; + double yScaleFactor = 1.0; + convert(xScaleFactor, yScaleFactor, fromUnits, toUnits); - return QTransform().scale(xScaleFactor, yScaleFactor); + return QTransform().scale(xScaleFactor, yScaleFactor); } const Dpi& UnitsConverter::getDpi() const { - return dpi; + return m_dpi; } void UnitsConverter::setDpi(const Dpi& dpi) { - UnitsConverter::dpi = dpi; + UnitsConverter::m_dpi = dpi; } diff --git a/UnitsConverter.h b/UnitsConverter.h index 8930986cb..4a2985a9a 100644 --- a/UnitsConverter.h +++ b/UnitsConverter.h @@ -8,21 +8,21 @@ #include "Units.h" class UnitsConverter { -private: - Dpi dpi; + public: + UnitsConverter() = default; -public: - UnitsConverter() = default; + explicit UnitsConverter(const Dpi& dpi); - explicit UnitsConverter(const Dpi& dpi); + void convert(double& horizontalValue, double& verticalValue, Units fromUnits, Units toUnits) const; - void convert(double& horizontalValue, double& verticalValue, Units fromUnits, Units toUnits) const; + QTransform transform(Units fromUnits, Units toUnits) const; - QTransform transform(Units fromUnits, Units toUnits) const; + const Dpi& getDpi() const; - const Dpi& getDpi() const; + void setDpi(const Dpi& dpi); - void setDpi(const Dpi& dpi); + private: + Dpi m_dpi; }; diff --git a/UnitsObserver.cpp b/UnitsObserver.cpp index c84622b17..a20dea7b6 100644 --- a/UnitsObserver.cpp +++ b/UnitsObserver.cpp @@ -3,9 +3,9 @@ #include "UnitsProvider.h" UnitsObserver::UnitsObserver() { - UnitsProvider::getInstance()->attachObserver(this); + UnitsProvider::getInstance()->attachObserver(this); } UnitsObserver::~UnitsObserver() { - UnitsProvider::getInstance()->detachObserver(this); + UnitsProvider::getInstance()->detachObserver(this); } \ No newline at end of file diff --git a/UnitsObserver.h b/UnitsObserver.h index fe74a82c7..185a5b8c4 100644 --- a/UnitsObserver.h +++ b/UnitsObserver.h @@ -8,12 +8,13 @@ class Dpi; class UnitsObserver { -public: - UnitsObserver(); + protected: + UnitsObserver(); - virtual ~UnitsObserver(); + public: + virtual ~UnitsObserver(); - virtual void updateUnits(Units units) = 0; + virtual void updateUnits(Units units) = 0; }; #endif // SCANTAILOR_UNITSOBSERVER_H diff --git a/UnitsProvider.cpp b/UnitsProvider.cpp index 3c3c3545e..2f4e122cc 100644 --- a/UnitsProvider.cpp +++ b/UnitsProvider.cpp @@ -1,50 +1,49 @@ -#include -#include #include "UnitsProvider.h" +#include +#include #include "Dpm.h" #include "UnitsConverter.h" -std::unique_ptr UnitsProvider::instance = nullptr; +std::unique_ptr UnitsProvider::m_instance = nullptr; -UnitsProvider::UnitsProvider() : units(unitsFromString(QSettings().value("settings/units", "mm").toString())) { -} +UnitsProvider::UnitsProvider() : m_units(unitsFromString(QSettings().value("settings/units", "mm").toString())) {} UnitsProvider* UnitsProvider::getInstance() { - if (instance == nullptr) { - instance.reset(new UnitsProvider()); - } + if (m_instance == nullptr) { + m_instance.reset(new UnitsProvider()); + } - return instance.get(); + return m_instance.get(); } Units UnitsProvider::getUnits() const { - return units; + return m_units; } void UnitsProvider::setUnits(Units units) { - UnitsProvider::units = units; - unitsChanged(); + UnitsProvider::m_units = units; + unitsChanged(); } void UnitsProvider::attachObserver(UnitsObserver* observer) { - observers.push_back(observer); + m_observers.push_back(observer); } void UnitsProvider::detachObserver(UnitsObserver* observer) { - observers.remove(observer); + m_observers.remove(observer); } void UnitsProvider::unitsChanged() { - for (UnitsObserver* observer : observers) { - observer->updateUnits(units); - } + for (UnitsObserver* observer : m_observers) { + observer->updateUnits(m_units); + } } void UnitsProvider::convertFrom(double& horizontalValue, double& verticalValue, Units fromUnits, const Dpi& dpi) const { - UnitsConverter(dpi).convert(horizontalValue, verticalValue, fromUnits, units); + UnitsConverter(dpi).convert(horizontalValue, verticalValue, fromUnits, m_units); } void UnitsProvider::convertTo(double& horizontalValue, double& verticalValue, Units toUnits, const Dpi& dpi) const { - UnitsConverter(dpi).convert(horizontalValue, verticalValue, units, toUnits); + UnitsConverter(dpi).convert(horizontalValue, verticalValue, m_units, toUnits); } diff --git a/UnitsProvider.h b/UnitsProvider.h index 58aba9c19..80c749f6a 100644 --- a/UnitsProvider.h +++ b/UnitsProvider.h @@ -2,38 +2,41 @@ #ifndef SCANTAILOR_UNITSPROVIDER_H #define SCANTAILOR_UNITSPROVIDER_H -#include +#include #include +#include #include "UnitsObserver.h" class Dpi; class UnitsProvider { -private: - static std::unique_ptr instance; + DECLARE_NON_COPYABLE(UnitsProvider) + private: + UnitsProvider(); - std::list observers; - Units units; + public: + static UnitsProvider* getInstance(); - UnitsProvider(); + Units getUnits() const; -public: - static UnitsProvider* getInstance(); + void setUnits(Units units); - Units getUnits() const; + void attachObserver(UnitsObserver* observer); - void setUnits(Units units); + void detachObserver(UnitsObserver* observer); - void attachObserver(UnitsObserver* observer); + void convertFrom(double& horizontalValue, double& verticalValue, Units fromUnits, const Dpi& dpi) const; - void detachObserver(UnitsObserver* observer); + void convertTo(double& horizontalValue, double& verticalValue, Units toUnits, const Dpi& dpi) const; - void convertFrom(double& horizontalValue, double& verticalValue, Units fromUnits, const Dpi& dpi) const; + protected: + void unitsChanged(); - void convertTo(double& horizontalValue, double& verticalValue, Units toUnits, const Dpi& dpi) const; + private: + static std::unique_ptr m_instance; -protected: - void unitsChanged(); + std::list m_observers; + Units m_units; }; diff --git a/Utils.cpp b/Utils.cpp index e82987a32..11aeb73b0 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -18,50 +18,48 @@ #include "Utils.h" #include +#include #include #ifdef Q_OS_WIN - #include - #else - #include #endif bool Utils::overwritingRename(const QString& from, const QString& to) { #ifdef Q_OS_WIN - return MoveFileExW((WCHAR*) from.utf16(), (WCHAR*) to.utf16(), MOVEFILE_REPLACE_EXISTING) != 0; - + return MoveFileExW((WCHAR*) from.utf16(), (WCHAR*) to.utf16(), MOVEFILE_REPLACE_EXISTING) != 0; #else - return rename(QFile::encodeName(from).data(), QFile::encodeName(to).data()) == 0; + return rename(QFile::encodeName(from).data(), QFile::encodeName(to).data()) == 0; #endif } QString Utils::richTextForLink(const QString& label, const QString& target) { - return QString::fromLatin1("" - "" - "

%2

") - .arg(target.toHtmlEscaped(), label.toHtmlEscaped()); + return QString::fromLatin1( + "" + "" + "

%2

") + .arg(target.toHtmlEscaped(), label.toHtmlEscaped()); } void Utils::maybeCreateCacheDir(const QString& output_dir) { - QDir(output_dir).mkdir(QString::fromLatin1("cache")); + QDir(output_dir).mkdir(QString::fromLatin1("cache")); - // QDir::mkdir() returns false if the directory already exists, - // so to prevent confusion this function return void. + // QDir::mkdir() returns false if the directory already exists, + // so to prevent confusion this function return void. } QString Utils::outputDirToThumbDir(const QString& output_dir) { - return output_dir + QLatin1String("/cache/thumbs"); + return output_dir + QLatin1String("/cache/thumbs"); } intrusive_ptr Utils::createThumbnailCache(const QString& output_dir) { - const QSize max_pixmap_size(200, 200); - const QString thumbs_cache_path(outputDirToThumbDir(output_dir)); + const QSize max_pixmap_size = QSettings().value("settings/thumbnail_quality", QSize(200, 200)).toSize(); + const QString thumbs_cache_path(outputDirToThumbDir(output_dir)); - return make_intrusive(thumbs_cache_path, max_pixmap_size, 40, 5); + return make_intrusive(thumbs_cache_path, max_pixmap_size, 40, 5); } diff --git a/Utils.h b/Utils.h index a3782f9f5..dce60b779 100644 --- a/Utils.h +++ b/Utils.h @@ -20,109 +20,107 @@ #define UTILS_H_ #include -#include "ThumbnailPixmapCache.h" #include #include +#include "ThumbnailPixmapCache.h" class Utils { -public: - template - static typename std::map::iterator mapSetValue(std::map& map, - const K& key, - const V& val); - - template - static typename std::unordered_map::iterator - mapSetValue(std::unordered_map& map, const K& key, const V& val); - - template - static T castOrFindChild(QObject* object); - - /** - * \brief If \p output_dir exists, creates a "cache" subdirectory under it. - * - * The idea is to prevent creating a bogus directory structure when loading - * a project created on another machine. - */ - static void maybeCreateCacheDir(const QString& output_dir); - - static QString outputDirToThumbDir(const QString& output_dir); - - static intrusive_ptr createThumbnailCache(const QString& output_dir); - - /** - * Unlike QFile::rename(), this one overwrites existing files. - */ - static bool overwritingRename(const QString& from, const QString& to); - - /** - * \brief A high precision, locale independent number to string conversion. - * - * This function is intended to be used instead of - * QDomElement::setAttribute(double), which is locale dependent. - */ - static QString doubleToString(double val) { - return QString::number(val, 'g', 16); - } - - /** - * \brief Generate rich text, complete with headers and stuff, - * for a clickable link. - * - * \param label The text to show as a link. - * \param target A URL or something else. If the link will - * be used in a QLable, this string will be passed - * to QLabel::linkActivated(const QString&). - * \return The resulting reach text. - */ - static QString richTextForLink(const QString& label, const QString& target = QString(QChar('#'))); + public: + template + static typename std::map::iterator mapSetValue(std::map& map, + const K& key, + const V& val); + + template + static typename std::unordered_map::iterator + mapSetValue(std::unordered_map& map, const K& key, const V& val); + + template + static T castOrFindChild(QObject* object); + + /** + * \brief If \p output_dir exists, creates a "cache" subdirectory under it. + * + * The idea is to prevent creating a bogus directory structure when loading + * a project created on another machine. + */ + static void maybeCreateCacheDir(const QString& output_dir); + + static QString outputDirToThumbDir(const QString& output_dir); + + static intrusive_ptr createThumbnailCache(const QString& output_dir); + + /** + * Unlike QFile::rename(), this one overwrites existing files. + */ + static bool overwritingRename(const QString& from, const QString& to); + + /** + * \brief A high precision, locale independent number to string conversion. + * + * This function is intended to be used instead of + * QDomElement::setAttribute(double), which is locale dependent. + */ + static QString doubleToString(double val) { return QString::number(val, 'g', 16); } + + /** + * \brief Generate rich text, complete with headers and stuff, + * for a clickable link. + * + * \param label The text to show as a link. + * \param target A URL or something else. If the link will + * be used in a QLable, this string will be passed + * to QLabel::linkActivated(const QString&). + * \return The resulting reach text. + */ + static QString richTextForLink(const QString& label, const QString& target = QString(QChar('#'))); }; -template +template typename std::map::iterator Utils::mapSetValue(std::map& map, const K& key, const V& val) { - const auto it(map.lower_bound(key)); - if ((it == map.end()) || map.key_comp()(key, it->first)) { - return map.insert(it, typename std::map::value_type(key, val)); - } else { - it->second = val; - - return it; - } + const auto it(map.lower_bound(key)); + if ((it == map.end()) || map.key_comp()(key, it->first)) { + return map.insert(it, typename std::map::value_type(key, val)); + } else { + it->second = val; + + return it; + } } -template +template typename std::unordered_map::iterator Utils::mapSetValue(std::unordered_map& map, const K& key, const V& val) { - const auto it(map.find(key)); - if (it == map.end()) { - return map.insert(it, typename std::unordered_map::value_type(key, val)); - } else { - it->second = val; - - return it; - } + const auto it(map.find(key)); + if (it == map.end()) { + return map.insert(it, typename std::unordered_map::value_type(key, val)); + } else { + it->second = val; + + return it; + } } -template +template T Utils::castOrFindChild(QObject* object) { - if (object == nullptr) { - return nullptr; - } + if (object == nullptr) { + return nullptr; + } - if (auto result = dynamic_cast(object)) { + if (auto result = dynamic_cast(object)) { + return result; + } else { + for (QObject* child : object->children()) { + if (result = castOrFindChild(child)) { return result; - } else { - for (QObject* child : object->children()) { - if (result = castOrFindChild(child)) { - return result; - } - } + } } + } - return nullptr; + return nullptr; } #endif // ifndef UTILS_H_ diff --git a/WorkerThreadPool.cpp b/WorkerThreadPool.cpp index a50e14287..0ad2db498 100644 --- a/WorkerThreadPool.cpp +++ b/WorkerThreadPool.cpp @@ -17,99 +17,94 @@ */ #include "WorkerThreadPool.h" -#include "OutOfMemoryHandler.h" #include #include #include +#include "OutOfMemoryHandler.h" class WorkerThreadPool::TaskResultEvent : public QEvent { -public: - TaskResultEvent(BackgroundTaskPtr task, FilterResultPtr result) - : QEvent(User), m_ptrTask(std::move(task)), m_ptrResult(std::move(result)) { - } + public: + TaskResultEvent(BackgroundTaskPtr task, FilterResultPtr result) + : QEvent(User), m_task(std::move(task)), m_result(std::move(result)) {} - const BackgroundTaskPtr& task() const { - return m_ptrTask; - } + const BackgroundTaskPtr& task() const { return m_task; } - const FilterResultPtr& result() const { - return m_ptrResult; - } + const FilterResultPtr& result() const { return m_result; } -private: - BackgroundTaskPtr m_ptrTask; - FilterResultPtr m_ptrResult; + private: + BackgroundTaskPtr m_task; + FilterResultPtr m_result; }; -WorkerThreadPool::WorkerThreadPool(QObject* parent) : QObject(parent), m_pPool(new QThreadPool(this)) { - updateNumberOfThreads(); +WorkerThreadPool::WorkerThreadPool(QObject* parent) : QObject(parent), m_pool(new QThreadPool(this)) { + updateNumberOfThreads(); } WorkerThreadPool::~WorkerThreadPool() = default; void WorkerThreadPool::shutdown() { - m_pPool->waitForDone(); + m_pool->waitForDone(); } bool WorkerThreadPool::hasSpareCapacity() const { - return m_pPool->activeThreadCount() < m_pPool->maxThreadCount(); + return m_pool->activeThreadCount() < m_pool->maxThreadCount(); } void WorkerThreadPool::submitTask(const BackgroundTaskPtr& task) { - class Runnable : public QRunnable { - public: - Runnable(WorkerThreadPool& owner, BackgroundTaskPtr task) : m_rOwner(owner), m_ptrTask(std::move(task)) { - setAutoDelete(true); - } + class Runnable : public QRunnable { + public: + Runnable(WorkerThreadPool& owner, BackgroundTaskPtr task) : m_owner(owner), m_task(std::move(task)) { + setAutoDelete(true); + } + + void run() + + override { + if (m_task->isCancelled()) { + return; + } - void run() - - override { - if (m_ptrTask->isCancelled()) { - return; - } - - try { - const FilterResultPtr result((*m_ptrTask)()); - if (result) { - QCoreApplication::postEvent(&m_rOwner, new TaskResultEvent(m_ptrTask, result)); - } - } catch (const std::bad_alloc&) { - OutOfMemoryHandler::instance().handleOutOfMemorySituation(); - } + try { + const FilterResultPtr result((*m_task)()); + if (result) { + QCoreApplication::postEvent(&m_owner, new TaskResultEvent(m_task, result)); } + } catch (const std::bad_alloc&) { + OutOfMemoryHandler::instance().handleOutOfMemorySituation(); + } + } - private: - WorkerThreadPool& m_rOwner; - BackgroundTaskPtr m_ptrTask; - }; + private: + WorkerThreadPool& m_owner; + BackgroundTaskPtr m_task; + }; - updateNumberOfThreads(); - m_pPool->start(new Runnable(*this, task)); + updateNumberOfThreads(); + m_pool->start(new Runnable(*this, task)); } // WorkerThreadPool::submitTask void WorkerThreadPool::customEvent(QEvent* event) { - if (auto* evt = dynamic_cast(event)) { - emit taskResult(evt->task(), evt->result()); - } + if (auto* evt = dynamic_cast(event)) { + emit taskResult(evt->task(), evt->result()); + } } void WorkerThreadPool::updateNumberOfThreads() { - int max_threads; - if (sizeof(void*) <= 4) { - // Restricting num of processors for 32-bit due to - // address space constraints. - max_threads = QThread::idealThreadCount(); - if (max_threads > 2) { - max_threads = 2; - } - } else { - max_threads = QThread::idealThreadCount(); + int max_threads; + if (sizeof(void*) <= 4) { + // Restricting num of processors for 32-bit due to + // address space constraints. + max_threads = QThread::idealThreadCount(); + if (max_threads > 2) { + max_threads = 2; } + } else { + max_threads = QThread::idealThreadCount(); + } - int num_threads = m_settings.value("settings/batch_processing_threads", max_threads).toInt(); - num_threads = std::min(num_threads, max_threads); - m_pPool->setMaxThreadCount(num_threads); + int num_threads = m_settings.value("settings/batch_processing_threads", max_threads).toInt(); + num_threads = std::min(num_threads, max_threads); + m_pool->setMaxThreadCount(num_threads); } diff --git a/WorkerThreadPool.h b/WorkerThreadPool.h index 9dc8c8f65..395691273 100644 --- a/WorkerThreadPool.h +++ b/WorkerThreadPool.h @@ -19,46 +19,46 @@ #ifndef WORKERTHREADPOOL_H_ #define WORKERTHREADPOOL_H_ -#include "BackgroundTask.h" -#include "FilterResult.h" #include #include #include +#include "BackgroundTask.h" +#include "FilterResult.h" class QThreadPool; class WorkerThreadPool : public QObject { - Q_OBJECT -public: - explicit WorkerThreadPool(QObject* parent = nullptr); + Q_OBJECT + public: + explicit WorkerThreadPool(QObject* parent = nullptr); - ~WorkerThreadPool() override; + ~WorkerThreadPool() override; - /** - * \brief Waits for pending jobs to finish and stop the thread. - * - * The destructor also performs these tasks, so this method is only - * useful to prematuraly stop task processing. - */ - void shutdown(); + /** + * \brief Waits for pending jobs to finish and stop the thread. + * + * The destructor also performs these tasks, so this method is only + * useful to prematuraly stop task processing. + */ + void shutdown(); - bool hasSpareCapacity() const; + bool hasSpareCapacity() const; - void submitTask(const BackgroundTaskPtr& task); + void submitTask(const BackgroundTaskPtr& task); -signals: + signals: - void taskResult(const BackgroundTaskPtr& task, const FilterResultPtr& result); + void taskResult(const BackgroundTaskPtr& task, const FilterResultPtr& result); -private: - class TaskResultEvent; + private: + class TaskResultEvent; - void customEvent(QEvent* event) override; + void customEvent(QEvent* event) override; - void updateNumberOfThreads(); + void updateNumberOfThreads(); - QThreadPool* m_pPool; - QSettings m_settings; + QThreadPool* m_pool; + QSettings m_settings; }; diff --git a/XmlMarshaller.cpp b/XmlMarshaller.cpp index 1886725e0..b18ad4340 100644 --- a/XmlMarshaller.cpp +++ b/XmlMarshaller.cpp @@ -17,115 +17,115 @@ */ #include "XmlMarshaller.h" -#include "OrthogonalRotation.h" -#include "Margins.h" #include "Dpi.h" +#include "Margins.h" +#include "OrthogonalRotation.h" #include "Utils.h" QDomElement XmlMarshaller::string(const QString& str, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.appendChild(m_doc.createTextNode(str)); + QDomElement el(m_doc.createElement(name)); + el.appendChild(m_doc.createTextNode(str)); - return el; + return el; } QDomElement XmlMarshaller::size(const QSize& size, const QString& name) { - if (size.isNull()) { - return QDomElement(); - } + if (size.isNull()) { + return QDomElement(); + } - QDomElement el(m_doc.createElement(name)); - el.setAttribute("width", size.width()); - el.setAttribute("height", size.height()); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("width", size.width()); + el.setAttribute("height", size.height()); - return el; + return el; } QDomElement XmlMarshaller::sizeF(const QSizeF& size, const QString& name) { - if (size.isNull()) { - return QDomElement(); - } + if (size.isNull()) { + return QDomElement(); + } - QDomElement el(m_doc.createElement(name)); - el.setAttribute("width", Utils::doubleToString(size.width())); - el.setAttribute("height", Utils::doubleToString(size.height())); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("width", Utils::doubleToString(size.width())); + el.setAttribute("height", Utils::doubleToString(size.height())); - return el; + return el; } QDomElement XmlMarshaller::dpi(const Dpi& dpi, const QString& name) { - if (dpi.isNull()) { - return QDomElement(); - } + if (dpi.isNull()) { + return QDomElement(); + } - QDomElement el(m_doc.createElement(name)); - el.setAttribute("horizontal", dpi.horizontal()); - el.setAttribute("vertical", dpi.vertical()); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("horizontal", dpi.horizontal()); + el.setAttribute("vertical", dpi.vertical()); - return el; + return el; } QDomElement XmlMarshaller::rotation(const OrthogonalRotation& rotation, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.setAttribute("degrees", rotation.toDegrees()); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("degrees", rotation.toDegrees()); - return el; + return el; } QDomElement XmlMarshaller::pointF(const QPointF& p, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.setAttribute("x", Utils::doubleToString(p.x())); - el.setAttribute("y", Utils::doubleToString(p.y())); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("x", Utils::doubleToString(p.x())); + el.setAttribute("y", Utils::doubleToString(p.y())); - return el; + return el; } QDomElement XmlMarshaller::lineF(const QLineF& line, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.appendChild(pointF(line.p1(), "p1")); - el.appendChild(pointF(line.p2(), "p2")); + QDomElement el(m_doc.createElement(name)); + el.appendChild(pointF(line.p1(), "p1")); + el.appendChild(pointF(line.p2(), "p2")); - return el; + return el; } QDomElement XmlMarshaller::rect(const QRect& rect, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.setAttribute("x", QString::number(rect.x())); - el.setAttribute("y", QString::number(rect.y())); - el.setAttribute("width", QString::number(rect.width())); - el.setAttribute("height", QString::number(rect.height())); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("x", QString::number(rect.x())); + el.setAttribute("y", QString::number(rect.y())); + el.setAttribute("width", QString::number(rect.width())); + el.setAttribute("height", QString::number(rect.height())); - return el; + return el; } QDomElement XmlMarshaller::rectF(const QRectF& rect, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.setAttribute("x", Utils::doubleToString(rect.x())); - el.setAttribute("y", Utils::doubleToString(rect.y())); - el.setAttribute("width", Utils::doubleToString(rect.width())); - el.setAttribute("height", Utils::doubleToString(rect.height())); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("x", Utils::doubleToString(rect.x())); + el.setAttribute("y", Utils::doubleToString(rect.y())); + el.setAttribute("width", Utils::doubleToString(rect.width())); + el.setAttribute("height", Utils::doubleToString(rect.height())); - return el; + return el; } QDomElement XmlMarshaller::polygonF(const QPolygonF& poly, const QString& name) { - QDomElement el(m_doc.createElement(name)); + QDomElement el(m_doc.createElement(name)); - QPolygonF::const_iterator it(poly.begin()); - const QPolygonF::const_iterator end(poly.end()); - for (; it != end; ++it) { - el.appendChild(pointF(*it, "point")); - } + QPolygonF::const_iterator it(poly.begin()); + const QPolygonF::const_iterator end(poly.end()); + for (; it != end; ++it) { + el.appendChild(pointF(*it, "point")); + } - return el; + return el; } QDomElement XmlMarshaller::margins(const Margins& margins, const QString& name) { - QDomElement el(m_doc.createElement(name)); - el.setAttribute("left", Utils::doubleToString(margins.left())); - el.setAttribute("right", Utils::doubleToString(margins.right())); - el.setAttribute("top", Utils::doubleToString(margins.top())); - el.setAttribute("bottom", Utils::doubleToString(margins.bottom())); + QDomElement el(m_doc.createElement(name)); + el.setAttribute("left", Utils::doubleToString(margins.left())); + el.setAttribute("right", Utils::doubleToString(margins.right())); + el.setAttribute("top", Utils::doubleToString(margins.top())); + el.setAttribute("bottom", Utils::doubleToString(margins.bottom())); - return el; + return el; } diff --git a/XmlMarshaller.h b/XmlMarshaller.h index 599105e0d..c534944be 100644 --- a/XmlMarshaller.h +++ b/XmlMarshaller.h @@ -35,34 +35,33 @@ class QRect; class QRectF; class XmlMarshaller { -public: - explicit XmlMarshaller(const QDomDocument& doc) : m_doc(doc) { - } + public: + explicit XmlMarshaller(const QDomDocument& doc) : m_doc(doc) {} - QDomElement string(const QString& str, const QString& name); + QDomElement string(const QString& str, const QString& name); - QDomElement size(const QSize& size, const QString& name); + QDomElement size(const QSize& size, const QString& name); - QDomElement sizeF(const QSizeF& size, const QString& name); + QDomElement sizeF(const QSizeF& size, const QString& name); - QDomElement dpi(const Dpi& dpi, const QString& name); + QDomElement dpi(const Dpi& dpi, const QString& name); - QDomElement rotation(const OrthogonalRotation& rotation, const QString& name); + QDomElement rotation(const OrthogonalRotation& rotation, const QString& name); - QDomElement pointF(const QPointF& p, const QString& name); + QDomElement pointF(const QPointF& p, const QString& name); - QDomElement lineF(const QLineF& line, const QString& name); + QDomElement lineF(const QLineF& line, const QString& name); - QDomElement rect(const QRect& rect, const QString& name); + QDomElement rect(const QRect& rect, const QString& name); - QDomElement rectF(const QRectF& rect, const QString& name); + QDomElement rectF(const QRectF& rect, const QString& name); - QDomElement polygonF(const QPolygonF& poly, const QString& name); + QDomElement polygonF(const QPolygonF& poly, const QString& name); - QDomElement margins(const Margins& margins, const QString& name); + QDomElement margins(const Margins& margins, const QString& name); -private: - QDomDocument m_doc; + private: + QDomDocument m_doc; }; diff --git a/XmlUnmarshaller.cpp b/XmlUnmarshaller.cpp index 7ce894c8e..b05aef36a 100644 --- a/XmlUnmarshaller.cpp +++ b/XmlUnmarshaller.cpp @@ -17,110 +17,110 @@ */ #include "XmlUnmarshaller.h" -#include "Dpi.h" -#include "OrthogonalRotation.h" -#include "Margins.h" -#include -#include +#include #include -#include +#include #include -#include +#include +#include +#include "Dpi.h" +#include "Margins.h" +#include "OrthogonalRotation.h" QString XmlUnmarshaller::string(const QDomElement& el) { - return el.text(); // FIXME: this needs unescaping, but Qt doesn't provide such functionality + return el.text(); // FIXME: this needs unescaping, but Qt doesn't provide such functionality } QSize XmlUnmarshaller::size(const QDomElement& el) { - const int width = el.attribute("width").toInt(); - const int height = el.attribute("height").toInt(); + const int width = el.attribute("width").toInt(); + const int height = el.attribute("height").toInt(); - return QSize(width, height); + return QSize(width, height); } QSizeF XmlUnmarshaller::sizeF(const QDomElement& el) { - const double width = el.attribute("width").toDouble(); - const double height = el.attribute("height").toDouble(); + const double width = el.attribute("width").toDouble(); + const double height = el.attribute("height").toDouble(); - return QSizeF(width, height); + return QSizeF(width, height); } Dpi XmlUnmarshaller::dpi(const QDomElement& el) { - const int hor = el.attribute("horizontal").toInt(); - const int ver = el.attribute("vertical").toInt(); + const int hor = el.attribute("horizontal").toInt(); + const int ver = el.attribute("vertical").toInt(); - return Dpi(hor, ver); + return Dpi(hor, ver); } OrthogonalRotation XmlUnmarshaller::rotation(const QDomElement& el) { - const int degrees = el.attribute("degrees").toInt(); - OrthogonalRotation rotation; - for (int i = 0; i < 4; ++i) { - if (rotation.toDegrees() == degrees) { - break; - } - rotation.nextClockwiseDirection(); + const int degrees = el.attribute("degrees").toInt(); + OrthogonalRotation rotation; + for (int i = 0; i < 4; ++i) { + if (rotation.toDegrees() == degrees) { + break; } + rotation.nextClockwiseDirection(); + } - return rotation; + return rotation; } Margins XmlUnmarshaller::margins(const QDomElement& el) { - Margins margins; - margins.setLeft(el.attribute("left").toDouble()); - margins.setRight(el.attribute("right").toDouble()); - margins.setTop(el.attribute("top").toDouble()); - margins.setBottom(el.attribute("bottom").toDouble()); + Margins margins; + margins.setLeft(el.attribute("left").toDouble()); + margins.setRight(el.attribute("right").toDouble()); + margins.setTop(el.attribute("top").toDouble()); + margins.setBottom(el.attribute("bottom").toDouble()); - return margins; + return margins; } QPointF XmlUnmarshaller::pointF(const QDomElement& el) { - const double x = el.attribute("x").toDouble(); - const double y = el.attribute("y").toDouble(); + const double x = el.attribute("x").toDouble(); + const double y = el.attribute("y").toDouble(); - return QPointF(x, y); + return QPointF(x, y); } QLineF XmlUnmarshaller::lineF(const QDomElement& el) { - const QPointF p1(pointF(el.namedItem("p1").toElement())); - const QPointF p2(pointF(el.namedItem("p2").toElement())); + const QPointF p1(pointF(el.namedItem("p1").toElement())); + const QPointF p2(pointF(el.namedItem("p2").toElement())); - return QLineF(p1, p2); + return QLineF(p1, p2); } QRect XmlUnmarshaller::rect(const QDomElement& el) { - const int x = el.attribute("x").toInt(); - const int y = el.attribute("y").toInt(); - const int width = el.attribute("width").toInt(); - const int height = el.attribute("height").toInt(); + const int x = el.attribute("x").toInt(); + const int y = el.attribute("y").toInt(); + const int width = el.attribute("width").toInt(); + const int height = el.attribute("height").toInt(); - return QRect(x, y, width, height); + return QRect(x, y, width, height); } QRectF XmlUnmarshaller::rectF(const QDomElement& el) { - const double x = el.attribute("x").toDouble(); - const double y = el.attribute("y").toDouble(); - const double width = el.attribute("width").toDouble(); - const double height = el.attribute("height").toDouble(); + const double x = el.attribute("x").toDouble(); + const double y = el.attribute("y").toDouble(); + const double width = el.attribute("width").toDouble(); + const double height = el.attribute("height").toDouble(); - return QRectF(x, y, width, height); + return QRectF(x, y, width, height); } QPolygonF XmlUnmarshaller::polygonF(const QDomElement& el) { - QPolygonF poly; - - const QString point_tag_name("point"); - QDomNode node(el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != point_tag_name) { - continue; - } - poly.push_back(pointF(node.toElement())); + QPolygonF poly; + + const QString point_tag_name("point"); + QDomNode node(el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != point_tag_name) { + continue; } + poly.push_back(pointF(node.toElement())); + } - return poly; + return poly; } diff --git a/XmlUnmarshaller.h b/XmlUnmarshaller.h index ba50b980d..661955091 100644 --- a/XmlUnmarshaller.h +++ b/XmlUnmarshaller.h @@ -33,28 +33,28 @@ class QRectF; class QPolygonF; class XmlUnmarshaller { -public: - static QString string(const QDomElement& el); + public: + static QString string(const QDomElement& el); - static QSize size(const QDomElement& el); + static QSize size(const QDomElement& el); - static QSizeF sizeF(const QDomElement& el); + static QSizeF sizeF(const QDomElement& el); - static Dpi dpi(const QDomElement& el); + static Dpi dpi(const QDomElement& el); - static OrthogonalRotation rotation(const QDomElement& el); + static OrthogonalRotation rotation(const QDomElement& el); - static Margins margins(const QDomElement& el); + static Margins margins(const QDomElement& el); - static QPointF pointF(const QDomElement& el); + static QPointF pointF(const QDomElement& el); - static QLineF lineF(const QDomElement& el); + static QLineF lineF(const QDomElement& el); - static QRect rect(const QDomElement& el); + static QRect rect(const QDomElement& el); - static QRectF rectF(const QDomElement& el); + static QRectF rectF(const QDomElement& el); - static QPolygonF polygonF(const QDomElement& el); + static QPolygonF polygonF(const QDomElement& el); }; diff --git a/cmake/CopyToBuildDir.cmake b/cmake/CopyToBuildDir.cmake index 34a6c1ea3..8e00d2800 100644 --- a/cmake/CopyToBuildDir.cmake +++ b/cmake/CopyToBuildDir.cmake @@ -1,74 +1,74 @@ # Reset variables. -FOREACH (conf_ Debug Release MinSizeRel RelWithDebInfo) - SET( - "COPY_TO_BUILD_DIR_${conf_}" "" CACHE INTERNAL - "Files to copy to ${conf_} build directory" FORCE - ) -ENDFOREACH () +foreach (conf_ Debug Release MinSizeRel RelWithDebInfo) + set( + "COPY_TO_BUILD_DIR_${conf_}" "" CACHE INTERNAL + "Files to copy to ${conf_} build directory" FORCE + ) +endforeach() # Usage: -# COPY_TO_BUILD_DIR(one or more files [SUBDIR subdir] [CONFIGURATIONS] conf1 conf2 ...) -MACRO(COPY_TO_BUILD_DIR) - SET(files_ "") - SET(confs_ "Debug;Release;MinSizeRel;RelWithDebInfo") - SET(subdir_ "") - SET(out_list_ "files_") - FOREACH (arg_ ${ARGV}) - IF ("${arg_}" STREQUAL "SUBDIR") - SET(out_list_ "subdir_") - ELSEIF ("${arg_}" STREQUAL "CONFIGURATIONS") - SET(out_list_ "confs_") - SET(confs_ "") - ELSE () - LIST(APPEND ${out_list_} "${arg_}") - ENDIF () - ENDFOREACH () +# copy_to_build_dir(one or more files [SUBDIR subdir] [CONFIGURATIONS] conf1 conf2 ...) +macro (copy_to_build_dir) + set(files_ "") + set(confs_ "Debug;Release;MinSizeRel;RelWithDebInfo") + set(subdir_ "") + set(out_list_ "files_") + foreach (arg_ ${ARGV}) + if ("${arg_}" STREQUAL "SUBDIR") + set(out_list_ "subdir_") + elseif ("${arg_}" STREQUAL "CONFIGURATIONS") + set(out_list_ "confs_") + set(confs_ "") + else() + list(APPEND ${out_list_} "${arg_}") + endif() + endforeach() - LIST(LENGTH subdir_ num_subdirs_) - IF ("${num_subdirs_}" GREATER 1) - MESSAGE(FATAL_ERROR "Multiple sub-directories aren't allowed!") - ENDIF () + list(LENGTH subdir_ num_subdirs_) + if ("${num_subdirs_}" GREATER 1) + message(FATAL_ERROR "Multiple sub-directories aren't allowed!") + endif() - FOREACH (conf_ ${confs_}) - FOREACH (file_ ${files_}) - IF (EXISTS "${file_}") - IF ("${subdir_}" STREQUAL "") - LIST(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}") - ELSE () - LIST(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}=>${subdir_}") - ENDIF () - ENDIF () - ENDFOREACH () + foreach (conf_ ${confs_}) + foreach (file_ ${files_}) + if (EXISTS "${file_}") + if ("${subdir_}" STREQUAL "") + list(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}") + else() + list(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}=>${subdir_}") + endif() + endif() + endforeach() - # Force the new value to be written to the cache. - SET( - "COPY_TO_BUILD_DIR_${conf_}" ${COPY_TO_BUILD_DIR_${conf_}} - CACHE INTERNAL "Files to copy to ${conf_} build directory" FORCE - ) - ENDFOREACH () -ENDMACRO() + # Force the new value to be written to the cache. + set( + "COPY_TO_BUILD_DIR_${conf_}" ${COPY_TO_BUILD_DIR_${conf_}} + CACHE INTERNAL "Files to copy to ${conf_} build directory" FORCE + ) + endforeach() +endmacro() -MACRO(GENERATE_COPY_TO_BUILD_DIR_TARGET target_name_) - SET(script_ "${CMAKE_BINARY_DIR}/copy_to_build_dir.cmake") - CONFIGURE_FILE("cmake/copy_to_build_dir.cmake.in" "${script_}" @ONLY) +macro (generate_copy_to_build_dir_target target_name_) + set(script_ "${CMAKE_BINARY_DIR}/copy_to_build_dir.cmake") + configure_file("cmake/copy_to_build_dir.cmake.in" "${script_}" @ONLY) - SET( - src_files_ - ${COPY_TO_BUILD_DIR_Debug} ${COPY_TO_BUILD_DIR_Release} - ${COPY_TO_BUILD_DIR_MinSizeRel} ${COPY_TO_BUILD_DIR_RelWithDebInfo} - ) - SET(deps_ "") - FOREACH (src_file_ ${src_files_}) - STRING(REGEX REPLACE "(.*)=>.*" "\\1" src_file_ "${src_file_}") - LIST(APPEND deps_ "${src_file_}") - ENDFOREACH () + set( + src_files_ + ${COPY_TO_BUILD_DIR_Debug} ${COPY_TO_BUILD_DIR_Release} + ${COPY_TO_BUILD_DIR_MinSizeRel} ${COPY_TO_BUILD_DIR_RelWithDebInfo} + ) + set(deps_ "") + foreach (src_file_ ${src_files_}) + string(REGEX REPLACE "(.*)=>.*" "\\1" src_file_ "${src_file_}") + list(APPEND deps_ "${src_file_}") + endforeach() - # Copy DLLs and other stuff to ${CMAKE_BINARY_DIR}/ - ADD_CUSTOM_TARGET( - "${target_name_}" ALL - COMMAND "${CMAKE_COMMAND}" "-DTARGET_DIR=$" - "-DCFG=$" -P "${script_}" - DEPENDS "${script_}" ${deps_} - ) -ENDMACRO() \ No newline at end of file + # Copy DLLs and other stuff to ${CMAKE_BINARY_DIR}/ + add_custom_target( + "${target_name_}" ALL + COMMAND "${CMAKE_COMMAND}" "-DTARGET_DIR=$" + "-DCFG=$" -P "${script_}" + DEPENDS "${script_}" ${deps_} + ) +endmacro() \ No newline at end of file diff --git a/cmake/FindPthreads.cmake b/cmake/FindPthreads.cmake deleted file mode 100644 index dd45c4d14..000000000 --- a/cmake/FindPthreads.cmake +++ /dev/null @@ -1,98 +0,0 @@ -MACRO(FindPthreads) - SET(PTHREADS_FOUND FALSE) - - # This won't overwrite values already in cache. - SET(PTHREADS_CFLAGS "" CACHE STRING "Compiler flags for pthreads") - SET(PTHREADS_LIBS "" CACHE STRING "Linker flags for pthreads") - MARK_AS_ADVANCED(CLEAR PTHREADS_CFLAGS PTHREADS_LIBS) - - SET(_available_flags "") - - IF (PTHREADS_CFLAGS OR PTHREADS_LIBS) - # First try user specified flags. - LIST(APPEND _available_flags "${PTHREADS_CFLAGS}:${PTHREADS_LIBS}") - ENDIF (PTHREADS_CFLAGS OR PTHREADS_LIBS) - - # -pthreads for gcc, -lpthread for Sun's compiler. - # Note that there are non-functional stubs of pthread functions - # in Solaris' libc, so these checks must be done before others. - SET(_solaris_flags "-pthreads:-pthreads" "-D_REENTRANT:-lpthread") - - # No flags required. This means this check has to be the first - # on Darwin / Mac OS X, because the compiler will accept almost - # any flag. - SET(_darwin_flags ":") - - # Must be checked before -lpthread on AIX. - SET(_aix_flags "-D_THREAD_SAFE:-lpthreads") - - # gcc on various OSes - SET(_other_flags "-pthread:-pthread") - - IF (CMAKE_SYSTEM_NAME MATCHES "AIX.*") - LIST(APPEND _available_flags ${_aix_flags}) - SET(_aix_flags "") - ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Solaris.*") - LIST(APPEND _available_flags ${_solaris_flags}) - SET(_solaris_flags "") - ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin.*") - LIST(APPEND _available_flags ${_darwin_flags}) - SET(_darwin_flags "") - ELSE (CMAKE_SYSTEM_NAME MATCHES "AIX.*") - LIST(APPEND _available_flags ${_other_flags}) - SET(_other_flags "") - ENDIF (CMAKE_SYSTEM_NAME MATCHES "AIX.*") - - LIST( - APPEND _available_flags - ${_darwin_flags} ${_aix_flags} ${_solaris_flags} ${_other_flags} - ) - - LIST(LENGTH _available_flags _num_available_flags) - SET(_flags_idx 0) - WHILE (_flags_idx LESS _num_available_flags AND NOT PTHREADS_FOUND) - LIST(GET _available_flags ${_flags_idx} _flag) - MATH(EXPR _flags_idx "${_flags_idx} + 1") - - STRING(REGEX REPLACE ":.*" "" _cflags "${_flag}") - STRING(REGEX REPLACE ".*:" "" _libs "${_flag}") - - FILE(WRITE ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/TestPthreads.c - "#include \n" - "int main()\n" - "{\n" - " pthread_t th;\n" - " pthread_create(&th, 0, 0, 0);\n" - " pthread_join(th, 0);\n" - " pthread_attr_init(0);\n" - " pthread_cleanup_push(0, 0);\n" - " pthread_cleanup_pop(0);\n" - " return 0;\n" - "}\n" - ) - - TRY_COMPILE( - PTHREADS_FOUND "${CMAKE_BINARY_DIR}" - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/TestPthreads.c - CMAKE_FLAGS "-DLINK_LIBRARIES:STRING=${_libs}" - COMPILE_DEFINITIONS "${_cflags}" - OUTPUT_VARIABLE _out - ) - IF (PTHREADS_FOUND) - MESSAGE(STATUS "Checking pthreads with CFLAGS=\"${_cflags}\" and LIBS=\"${_libs}\" -- yes") - SET(PTHREADS_CFLAGS ${_cflags} CACHE STRING "Compiler flags for pthreads" FORCE) - SET(PTHREADS_LIBS ${_libs} CACHE STRING "Linker flags for pthreads" FORCE) - MARK_AS_ADVANCED(FORCE PTHREADS_CFLAGS PTHREADS_LIBS) - ELSE (PTHREADS_FOUND) - MESSAGE(STATUS "Checking pthreads with CFLAGS=\"${_cflags}\" and LIBS=\"${_libs}\" -- no") - FILE(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Pthreads don't work with CFLAGS=\"${_cflags}\" and LIBS=\"${_libs}\"\n" - "Build output follows:\n" - "==========================================\n" - "${_out}\n" - "==========================================\n" - ) - ENDIF (PTHREADS_FOUND) - ENDWHILE (_flags_idx LESS _num_available_flags AND NOT PTHREADS_FOUND) - -ENDMACRO(FindPthreads) diff --git a/cmake/LibToDLL.cmake b/cmake/LibToDLL.cmake index 510f8f48d..b423377a5 100644 --- a/cmake/LibToDLL.cmake +++ b/cmake/LibToDLL.cmake @@ -1,22 +1,22 @@ # Usage: -# LIB_TO_DLL(output_list_of_dlls ${list_of_dot_lib_files}) -MACRO(LIB_TO_DLL out_list_) - SET(${out_list_} "") - FOREACH (lib_file_ ${ARGN}) - #STRING(REGEX REPLACE "\\.lib$" ".dll" dll_file_ "${lib_file_}") - #IF(NOT "${dll_file_}" STREQUAL "${lib_file_}") - # LIST(APPEND ${out_list_} "${dll_file_}") - #ENDIF() - GET_FILENAME_COMPONENT(lib_file_name_ "${lib_file_}" NAME) - GET_FILENAME_COMPONENT(dir_ "${lib_file_}" PATH) +# lib_to_dll(output_list_of_dlls ${list_of_dot_lib_files}) +macro (lib_to_dll out_list_) + set(${out_list_} "") + foreach (lib_file_ ${ARGN}) + #string(REGEX REPLACE "\\.lib$" ".dll" dll_file_ "${lib_file_}") + #if (NOT "${dll_file_}" STREQUAL "${lib_file_}") + # list(APPEND ${out_list_} "${dll_file_}") + #endif() + get_filename_component(lib_file_name_ "${lib_file_}" NAME) + get_filename_component(dir_ "${lib_file_}" PATH) - STRING(REGEX REPLACE "^lib(.*)\\.a$" "\\1.dll" dll_file_name_ "${lib_file_name_}") - IF ("${dll_file_name_}" STREQUAL "${lib_file_name_}") - STRING(REGEX REPLACE "^(.*)\\.lib$" "\\1.dll" dll_file_name_ "${lib_file_name_}") - ENDIF () + string(REGEX REPLACE "^lib(.*)\\.a$" "\\1.dll" dll_file_name_ "${lib_file_name_}") + if ("${dll_file_name_}" STREQUAL "${lib_file_name_}") + string(REGEX REPLACE "^(.*)\\.lib$" "\\1.dll" dll_file_name_ "${lib_file_name_}") + endif() - IF (NOT "${dll_file_name_}" STREQUAL "${lib_file_name_}") - LIST(APPEND ${out_list_} "${dir_}/${dll_file_name_}") - ENDIF () - ENDFOREACH () -ENDMACRO() \ No newline at end of file + if (NOT "${dll_file_name_}" STREQUAL "${lib_file_name_}") + list(APPEND ${out_list_} "${dir_}/${dll_file_name_}") + endif() + endforeach() +endmacro() \ No newline at end of file diff --git a/cmake/SetDefaultBuildType.cmake b/cmake/SetDefaultBuildType.cmake index 99dd406b0..e41c81056 100644 --- a/cmake/SetDefaultBuildType.cmake +++ b/cmake/SetDefaultBuildType.cmake @@ -1,9 +1,9 @@ -MACRO(ST_SET_DEFAULT_BUILD_TYPE TYPE_) - IF (NOT CMAKE_BUILD_TYPE AND NOT DEFAULT_BUILD_TYPE_SET) - SET(DEFAULT_BUILD_TYPE_SET TRUE CACHE INTERNAL "" FORCE) - SET( - CMAKE_BUILD_TYPE "${TYPE_}" CACHE STRING - "Build type (Release Debug RelWithDebInfo MinSizeRel)" FORCE - ) - ENDIF (NOT CMAKE_BUILD_TYPE AND NOT DEFAULT_BUILD_TYPE_SET) -ENDMACRO(ST_SET_DEFAULT_BUILD_TYPE) +macro (st_set_default_build_type TYPE_) + if (NOT CMAKE_BUILD_TYPE AND NOT DEFAULT_BUILD_TYPE_SET) + set(DEFAULT_BUILD_TYPE_SET TRUE CACHE INTERNAL "" FORCE) + set( + CMAKE_BUILD_TYPE "${TYPE_}" CACHE STRING + "Build type (Release Debug RelWithDebInfo MinSizeRel)" FORCE + ) + endif() +endmacro() diff --git a/cmake/UpdateTranslations.cmake b/cmake/UpdateTranslations.cmake index 90b8cbf82..013a6671f 100644 --- a/cmake/UpdateTranslations.cmake +++ b/cmake/UpdateTranslations.cmake @@ -1,4 +1,4 @@ -# TRANSLATION_SOURCES( ) +# translation_sources( ) # # Associates the specified source files with a translation set. A translation # set corresponds to a family of *.ts files with the same prefix, one for each @@ -9,91 +9,91 @@ # This macro may be called multiple times, possibly from different directories. # The typical usage will be like this: # -# TRANSLATION_SOURCES(myapp MainWindow.cpp MainWindow.h MainWindow.ui ...) -# FINALIZE_TRANSLATION_SET(myapp myapp_de.ts myapp_ru.ts myapp_ja.ts ...) -# UPDATE_TRANSLATIONS_TARGET(update_translations myapp) +# translation_sources(myapp MainWindow.cpp MainWindow.h MainWindow.ui ...) +# finalize_translation_set(myapp myapp_de.ts myapp_ru.ts myapp_ja.ts ...) +# update_translations_target(update_translations myapp) # -MACRO(TRANSLATION_SOURCES _set) #, _sources - FILE(GLOB _sources ABSOLUTE ${ARGN}) - LIST(APPEND ${_set}_SOURCES ${_sources}) +macro (translation_sources _set) #, _sources + file(GLOB _sources ABSOLUTE ${ARGN}) + list(APPEND ${_set}_SOURCES ${_sources}) - GET_DIRECTORY_PROPERTY(_inc_dirs INCLUDE_DIRECTORIES) - FILE(GLOB _inc_dirs ${_inc_dirs} .) - LIST(APPEND ${_set}_INC_DIRS ${_inc_dirs}) + get_directory_property(_inc_dirs INCLUDE_DIRECTORIES) + file(GLOB _inc_dirs ${_inc_dirs} .) + list(APPEND ${_set}_INC_DIRS ${_inc_dirs}) - # If there is a parent scope, set these variables there as well. - GET_DIRECTORY_PROPERTY(_parent_dir PARENT_DIRECTORY) - IF (_parent_dir) - SET(${_set}_SOURCES ${${_set}_SOURCES} PARENT_SCOPE) - SET(${_set}_INC_DIRS ${${_set}_INC_DIRS} PARENT_SCOPE) - ENDIF () -ENDMACRO() + # If there is a parent scope, set these variables there as well. + get_directory_property(_parent_dir PARENT_DIRECTORY) + if (_parent_dir) + set(${_set}_SOURCES ${${_set}_SOURCES} PARENT_SCOPE) + set(${_set}_INC_DIRS ${${_set}_INC_DIRS} PARENT_SCOPE) + endif() +endmacro() -# FINALIZE_TRANSLATION_SET(, <*.ts files>) +# finalize_translation_set(, <*.ts files>) # # Associates *.ts files with a translation set. # May be called multiple times for different translation sets. -# To be followed by UPDATE_TRANSLATIONS_TARGET() +# To be followed by update_translations_target() # -MACRO(FINALIZE_TRANSLATION_SET _set) #, _ts_files - SET(_sources_str "") - FOREACH (_file ${${_set}_SOURCES}) - SET(_sources_str "${_sources_str} \"${_file}\"") - ENDFOREACH () +macro (finalize_translation_set _set) #, _ts_files + set(_sources_str "") + foreach (_file ${${_set}_SOURCES}) + set(_sources_str "${_sources_str} \"${_file}\"") + endforeach() - SET(_inc_dirs ${${_set}_INC_DIRS}) - LIST(REMOVE_DUPLICATES _inc_dirs) + set(_inc_dirs ${${_set}_INC_DIRS}) + list(REMOVE_DUPLICATES _inc_dirs) - SET(_filtered_inc_dirs "") - FOREACH (_dir ${_inc_dirs}) - # We are going to accept include directories within our - # source and binary trees and reject all others. Allowing lupdate - # to parse things like boost headers leads to spurious warnings. - FILE(RELATIVE_PATH _dir_rel_to_source "${CMAKE_SOURCE_DIR}" "${_dir}") - FILE(RELATIVE_PATH _dir_rel_to_binary "${CMAKE_BINARY_DIR}" "${_dir}") - IF (NOT _dir_rel_to_source MATCHES "\\.\\..*") - LIST(APPEND _filtered_inc_dirs "${_dir}") - ELSEIF (NOT _dir_rel_to_binary MATCHES "\\.\\..*") - LIST(APPEND _filtered_inc_dirs "${_dir}") - ENDIF () - ENDFOREACH () + set(_filtered_inc_dirs "") + foreach (_dir ${_inc_dirs}) + # We are going to accept include directories within our + # source and binary trees and reject all others. Allowing lupdate + # to parse things like boost headers leads to spurious warnings. + file(RELATIVE_PATH _dir_rel_to_source "${CMAKE_SOURCE_DIR}" "${_dir}") + file(RELATIVE_PATH _dir_rel_to_binary "${CMAKE_BINARY_DIR}" "${_dir}") + if (NOT _dir_rel_to_source MATCHES "\\.\\..*") + list(APPEND _filtered_inc_dirs "${_dir}") + elseif (NOT _dir_rel_to_binary MATCHES "\\.\\..*") + list(APPEND _filtered_inc_dirs "${_dir}") + endif() + endforeach() - SET(_inc_dirs_str "") - FOREACH (_dir ${_filtered_inc_dirs}) - SET(_inc_dirs_str "${_inc_dirs_str} \"${_dir}\"") - ENDFOREACH () + set(_inc_dirs_str "") + foreach (_dir ${_filtered_inc_dirs}) + set(_inc_dirs_str "${_inc_dirs_str} \"${_dir}\"") + endforeach() - SET(_translations_str "") - FOREACH (_file ${ARGN}) - GET_FILENAME_COMPONENT(_abs "${_file}" ABSOLUTE) - SET(_translations_str "${_translations_str} \"${_abs}\"") - ENDFOREACH (_file) + set(_translations_str "") + foreach (_file ${ARGN}) + get_filename_component(_abs "${_file}" ABSOLUTE) + set(_translations_str "${_translations_str} \"${_abs}\"") + endforeach() - FILE( - WRITE "${CMAKE_BINARY_DIR}/update_translations_${_set}.pro" - "SOURCES = ${_sources_str}\nTRANSLATIONS = ${_translations_str}\nINCLUDEPATH = ${_inc_dirs_str}" - ) + file( + WRITE "${CMAKE_BINARY_DIR}/update_translations_${_set}.pro" + "SOURCES = ${_sources_str}\nTRANSLATIONS = ${_translations_str}\nINCLUDEPATH = ${_inc_dirs_str}" + ) - # Note that we can't create a custom target with *.ts files as output, because: - # 1. CMake would pollute our source tree with *.rule fules. - # 2. "make clean" would remove them. -ENDMACRO() + # Note that we can't create a custom target with *.ts files as output, because: + # 1. CMake would pollute our source tree with *.rule fules. + # 2. "make clean" would remove them. +endmacro() -# UPDATE_TRANSLATIONS_TARGET( ) +# update_translations_target( ) # # Creates a target that updates *.ts files assiciated with the specified -# translation sets by FINALIZE_TRANSLATION_SET() +# translation sets by finalize_translation_set() # -MACRO(UPDATE_TRANSLATIONS_TARGET _target) #, _sets - SET(_commands "") - FOREACH (_set ${ARGN}) - LIST( - APPEND _commands COMMAND Qt5::lupdate -locations absolute - -pro "${CMAKE_BINARY_DIR}/update_translations_${_set}.pro" - ) - ENDFOREACH () +macro (update_translations_target _target) #, _sets + set(_commands "") + foreach (_set ${ARGN}) + list( + APPEND _commands COMMAND Qt5::lupdate -locations absolute + -pro "${CMAKE_BINARY_DIR}/update_translations_${_set}.pro" + ) + endforeach() - ADD_CUSTOM_TARGET(${_target} ${_commands} VERBATIM) -ENDMACRO() + add_custom_target(${_target} ${_commands} VERBATIM) +endmacro() diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 000000000..c0fe12ed1 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,20 @@ +if (NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach (file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval) + if (NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else() + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() \ No newline at end of file diff --git a/cmake/copy_to_build_dir.cmake.in b/cmake/copy_to_build_dir.cmake.in index fb6bb4e13..697e56872 100644 --- a/cmake/copy_to_build_dir.cmake.in +++ b/cmake/copy_to_build_dir.cmake.in @@ -1,20 +1,20 @@ -SET("COPY_TO_BUILD_DIR_Debug" "@COPY_TO_BUILD_DIR_Debug@") -SET("COPY_TO_BUILD_DIR_Release" "@COPY_TO_BUILD_DIR_Release@") -SET("COPY_TO_BUILD_DIR_MinSizeRel" "@COPY_TO_BUILD_DIR_MinSizeRel@") -SET("COPY_TO_BUILD_DIR_RelWithDebInfo" "@COPY_TO_BUILD_DIR_RelWithDebInfo@") +set("COPY_TO_BUILD_DIR_Debug" "@COPY_TO_BUILD_DIR_Debug@") +set("COPY_TO_BUILD_DIR_Release" "@COPY_TO_BUILD_DIR_Release@") +set("COPY_TO_BUILD_DIR_MinSizeRel" "@COPY_TO_BUILD_DIR_MinSizeRel@") +set("COPY_TO_BUILD_DIR_RelWithDebInfo" "@COPY_TO_BUILD_DIR_RelWithDebInfo@") -FOREACH(src_file ${COPY_TO_BUILD_DIR_${CFG}}) - SET(subdir "") - IF(src_file MATCHES ".*=>.*") - STRING(REGEX REPLACE ".*=>(.*)" "/\\1" subdir "${src_file}") - STRING(REGEX REPLACE "(.*)=>.*" "\\1" src_file "${src_file}") - ENDIF() - SET(dst_dir "${TARGET_DIR}${subdir}") - - GET_FILENAME_COMPONENT(dst_file "${src_file}" NAME) +foreach (src_file ${COPY_TO_BUILD_DIR_${CFG}}) + set(subdir "") + if (src_file MATCHES ".*=>.*") + string(REGEX REPLACE ".*=>(.*)" "/\\1" subdir "${src_file}") + string(REGEX REPLACE "(.*)=>.*" "\\1" src_file "${src_file}") + endif() + set(dst_dir "${TARGET_DIR}${subdir}") + + get_filename_component(dst_file "${src_file}" NAME) - IF("${src_file}" IS_NEWER_THAN "${dst_dir}/${dst_file}") - MESSAGE(STATUS "Copying ${dst_file} to ${CFG}${subdir}") - CONFIGURE_FILE("${src_file}" "${dst_dir}/${dst_file}" COPYONLY) - ENDIF() -ENDFOREACH() + if ("${src_file}" IS_NEWER_THAN "${dst_dir}/${dst_file}") + message(STATUS "Copying ${dst_file} to ${CFG}${subdir}") + configure_file("${src_file}" "${dst_dir}/${dst_file}" COPYONLY) + endif() +endforeach() diff --git a/config.h.in b/config.h.in index 71fb159ba..eadec1c42 100644 --- a/config.h.in +++ b/config.h.in @@ -6,4 +6,8 @@ #define TRANSLATION_DIRS "@TRANSLATION_DIRS@" #define PLUGIN_DIRS "@PLUGIN_DIRS@" +#define PORTABLE_CONFIG_DIR "@PORTABLE_CONFIG_DIR@" +#define APPLICATION_NAME "@APPLICATION_NAME@" +#define ORGANIZATION_NAME APPLICATION_NAME + #endif diff --git a/dewarping/CMakeLists.txt b/dewarping/CMakeLists.txt index 62bc6a61c..93140848d 100644 --- a/dewarping/CMakeLists.txt +++ b/dewarping/CMakeLists.txt @@ -1,19 +1,19 @@ -PROJECT("Dewarping library") +project("Dewarping library") -SET( - sources - Curve.cpp Curve.h - DistortionModel.cpp DistortionModel.h - DistortionModelBuilder.cpp DistortionModelBuilder.h - DetectVertContentBounds.cpp DetectVertContentBounds.h - TowardsLineTracer.cpp TowardsLineTracer.h - TextLineTracer.cpp TextLineTracer.h - TextLineRefiner.cpp TextLineRefiner.h - TopBottomEdgeTracer.cpp TopBottomEdgeTracer.h - CylindricalSurfaceDewarper.cpp CylindricalSurfaceDewarper.h - DewarpingPointMapper.cpp DewarpingPointMapper.h - RasterDewarper.cpp RasterDewarper.h +set( + sources + Curve.cpp Curve.h + DistortionModel.cpp DistortionModel.h + DistortionModelBuilder.cpp DistortionModelBuilder.h + DetectVertContentBounds.cpp DetectVertContentBounds.h + TowardsLineTracer.cpp TowardsLineTracer.h + TextLineTracer.cpp TextLineTracer.h + TextLineRefiner.cpp TextLineRefiner.h + TopBottomEdgeTracer.cpp TopBottomEdgeTracer.h + CylindricalSurfaceDewarper.cpp CylindricalSurfaceDewarper.h + DewarpingPointMapper.cpp DewarpingPointMapper.h + RasterDewarper.cpp RasterDewarper.h ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) -ADD_LIBRARY(dewarping STATIC ${sources}) +add_library(dewarping STATIC ${sources}) diff --git a/dewarping/Curve.cpp b/dewarping/Curve.cpp index 11ec01fc7..ae389bb4b 100644 --- a/dewarping/Curve.cpp +++ b/dewarping/Curve.cpp @@ -17,151 +17,148 @@ */ #include "Curve.h" +#include +#include "VecNT.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" -#include "VecNT.h" -#include namespace dewarping { struct Curve::CloseEnough { - bool operator()(const QPointF& p1, const QPointF& p2) { - const QPointF d(p1 - p2); + bool operator()(const QPointF& p1, const QPointF& p2) { + const QPointF d(p1 - p2); - return d.x() * d.x() + d.y() * d.y() <= 0.01 * 0.01; - } + return d.x() * d.x() + d.y() * d.y() <= 0.01 * 0.01; + } }; Curve::Curve() = default; -Curve::Curve(const std::vector& polyline) : m_polyline(polyline) { -} +Curve::Curve(const std::vector& polyline) : m_polyline(polyline) {} -Curve::Curve(const XSpline& xspline) : m_xspline(xspline), m_polyline(xspline.toPolyline()) { -} +Curve::Curve(const XSpline& xspline) : m_xspline(xspline), m_polyline(xspline.toPolyline()) {} Curve::Curve(const QDomElement& el) - : m_xspline(deserializeXSpline(el.namedItem("xspline").toElement())), - m_polyline(deserializePolyline(el.namedItem("polyline").toElement())) { -} + : m_xspline(deserializeXSpline(el.namedItem("xspline").toElement())), + m_polyline(deserializePolyline(el.namedItem("polyline").toElement())) {} QDomElement Curve::toXml(QDomDocument& doc, const QString& name) const { - if (!isValid()) { - return QDomElement(); - } + if (!isValid()) { + return QDomElement(); + } - QDomElement el(doc.createElement(name)); - el.appendChild(serializeXSpline(m_xspline, doc, "xspline")); - el.appendChild(serializePolyline(m_polyline, doc, "polyline")); + QDomElement el(doc.createElement(name)); + el.appendChild(serializeXSpline(m_xspline, doc, "xspline")); + el.appendChild(serializePolyline(m_polyline, doc, "polyline")); - return el; + return el; } bool Curve::isValid() const { - return m_polyline.size() > 1 && m_polyline.front() != m_polyline.back(); + return m_polyline.size() > 1 && m_polyline.front() != m_polyline.back(); } bool Curve::matches(const Curve& other) const { - return approxPolylineMatch(m_polyline, other.m_polyline); + return approxPolylineMatch(m_polyline, other.m_polyline); } std::vector Curve::deserializePolyline(const QDomElement& el) { - QByteArray ba(QByteArray::fromBase64(el.text().trimmed().toLatin1())); - QDataStream strm(&ba, QIODevice::ReadOnly); - strm.setVersion(QDataStream::Qt_4_4); - strm.setByteOrder(QDataStream::LittleEndian); - - const auto num_points = static_cast(ba.size() / 8); - std::vector points; - points.reserve(num_points); - - for (unsigned i = 0; i < num_points; ++i) { - float x = 0, y = 0; - strm >> x >> y; - points.emplace_back(x, y); - } - - return points; + QByteArray ba(QByteArray::fromBase64(el.text().trimmed().toLatin1())); + QDataStream strm(&ba, QIODevice::ReadOnly); + strm.setVersion(QDataStream::Qt_4_4); + strm.setByteOrder(QDataStream::LittleEndian); + + const auto num_points = static_cast(ba.size() / 8); + std::vector points; + points.reserve(num_points); + + for (unsigned i = 0; i < num_points; ++i) { + float x = 0, y = 0; + strm >> x >> y; + points.emplace_back(x, y); + } + + return points; } QDomElement Curve::serializePolyline(const std::vector& polyline, QDomDocument& doc, const QString& name) { - if (polyline.empty()) { - return QDomElement(); - } + if (polyline.empty()) { + return QDomElement(); + } - QByteArray ba; - ba.reserve(static_cast(8 * polyline.size())); - QDataStream strm(&ba, QIODevice::WriteOnly); - strm.setVersion(QDataStream::Qt_4_4); - strm.setByteOrder(QDataStream::LittleEndian); + QByteArray ba; + ba.reserve(static_cast(8 * polyline.size())); + QDataStream strm(&ba, QIODevice::WriteOnly); + strm.setVersion(QDataStream::Qt_4_4); + strm.setByteOrder(QDataStream::LittleEndian); - for (const QPointF& pt : polyline) { - strm << (float) pt.x() << (float) pt.y(); - } + for (const QPointF& pt : polyline) { + strm << (float) pt.x() << (float) pt.y(); + } - QDomElement el(doc.createElement(name)); - el.appendChild(doc.createTextNode(QString::fromLatin1(ba.toBase64()))); + QDomElement el(doc.createElement(name)); + el.appendChild(doc.createTextNode(QString::fromLatin1(ba.toBase64()))); - return el; + return el; } bool Curve::approxPolylineMatch(const std::vector& polyline1, const std::vector& polyline2) { - if (polyline1.size() != polyline2.size()) { - return false; - } + if (polyline1.size() != polyline2.size()) { + return false; + } - return std::equal(polyline1.begin(), polyline1.end(), polyline2.begin(), CloseEnough()); + return std::equal(polyline1.begin(), polyline1.end(), polyline2.begin(), CloseEnough()); } QDomElement Curve::serializeXSpline(const XSpline& xspline, QDomDocument& doc, const QString& name) { - if (xspline.numControlPoints() == 0) { - return QDomElement(); - } + if (xspline.numControlPoints() == 0) { + return QDomElement(); + } - QDomElement el(doc.createElement(name)); - XmlMarshaller marshaller(doc); + QDomElement el(doc.createElement(name)); + XmlMarshaller marshaller(doc); - const int num_control_points = xspline.numControlPoints(); - for (int i = 0; i < num_control_points; ++i) { - const QPointF pt(xspline.controlPointPosition(i)); - el.appendChild(marshaller.pointF(pt, "point")); - } + const int num_control_points = xspline.numControlPoints(); + for (int i = 0; i < num_control_points; ++i) { + const QPointF pt(xspline.controlPointPosition(i)); + el.appendChild(marshaller.pointF(pt, "point")); + } - return el; + return el; } XSpline Curve::deserializeXSpline(const QDomElement& el) { - XSpline xspline; - - const QString point_tag_name("point"); - QDomNode node(el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != point_tag_name) { - continue; - } - xspline.appendControlPoint(XmlUnmarshaller::pointF(node.toElement()), 1); - } + XSpline xspline; - if (xspline.numControlPoints() > 0) { - xspline.setControlPointTension(0, 0); - xspline.setControlPointTension(xspline.numControlPoints() - 1, 0); + const QString point_tag_name("point"); + QDomNode node(el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } + if (node.nodeName() != point_tag_name) { + continue; + } + xspline.appendControlPoint(XmlUnmarshaller::pointF(node.toElement()), 1); + } + + if (xspline.numControlPoints() > 0) { + xspline.setControlPointTension(0, 0); + xspline.setControlPointTension(xspline.numControlPoints() - 1, 0); + } - return xspline; + return xspline; } bool Curve::splineHasLoops(const XSpline& spline) { - const int num_control_points = spline.numControlPoints(); - const Vec2d main_direction(spline.pointAt(1) - spline.pointAt(0)); - - for (int i = 1; i < num_control_points; ++i) { - const QPointF cp1(spline.controlPointPosition(i - 1)); - const QPointF cp2(spline.controlPointPosition(i)); - if (Vec2d(cp2 - cp1).dot(main_direction) < 0) { - return true; - } + const int num_control_points = spline.numControlPoints(); + const Vec2d main_direction(spline.pointAt(1) - spline.pointAt(0)); + + for (int i = 1; i < num_control_points; ++i) { + const QPointF cp1(spline.controlPointPosition(i - 1)); + const QPointF cp2(spline.controlPointPosition(i)); + if (Vec2d(cp2 - cp1).dot(main_direction) < 0) { + return true; + } #if 0 const double t1 = spline.controlPointIndexToT(i - 1); const double t2 = spline.controlPointIndexToT(i); @@ -169,8 +166,8 @@ bool Curve::splineHasLoops(const XSpline& spline) { return true; } #endif - } + } - return false; + return false; } } // namespace dewarping diff --git a/dewarping/Curve.h b/dewarping/Curve.h index 613552006..437f75a4d 100644 --- a/dewarping/Curve.h +++ b/dewarping/Curve.h @@ -20,8 +20,8 @@ #define DEWARPING_CURVE_H_ #include -#include "XSpline.h" #include +#include "XSpline.h" class QDomDocument; class QDomElement; @@ -29,46 +29,42 @@ class QString; namespace dewarping { class Curve { -public: - Curve(); + public: + Curve(); - explicit Curve(const std::vector& polyline); + explicit Curve(const std::vector& polyline); - explicit Curve(const XSpline& xspline); + explicit Curve(const XSpline& xspline); - explicit Curve(const QDomElement& el); + explicit Curve(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool isValid() const; + bool isValid() const; - bool matches(const Curve& other) const; + bool matches(const Curve& other) const; - const XSpline& xspline() const { - return m_xspline; - } + const XSpline& xspline() const { return m_xspline; } - const std::vector& polyline() const { - return m_polyline; - } + const std::vector& polyline() const { return m_polyline; } - static bool splineHasLoops(const XSpline& spline); + static bool splineHasLoops(const XSpline& spline); -private: - struct CloseEnough; + private: + struct CloseEnough; - static std::vector deserializePolyline(const QDomElement& el); + static std::vector deserializePolyline(const QDomElement& el); - static QDomElement serializePolyline(const std::vector& polyline, QDomDocument& doc, const QString& name); + static QDomElement serializePolyline(const std::vector& polyline, QDomDocument& doc, const QString& name); - static XSpline deserializeXSpline(const QDomElement& el); + static XSpline deserializeXSpline(const QDomElement& el); - static QDomElement serializeXSpline(const XSpline& xspline, QDomDocument& doc, const QString& name); + static QDomElement serializeXSpline(const XSpline& xspline, QDomDocument& doc, const QString& name); - static bool approxPolylineMatch(const std::vector& polyline1, const std::vector& polyline2); + static bool approxPolylineMatch(const std::vector& polyline1, const std::vector& polyline2); - XSpline m_xspline; - std::vector m_polyline; + XSpline m_xspline; + std::vector m_polyline; }; } // namespace dewarping #endif // ifndef DEWARPING_CURVE_H_ diff --git a/dewarping/CylindricalSurfaceDewarper.cpp b/dewarping/CylindricalSurfaceDewarper.cpp index 2b4eb903b..71ad58a2d 100644 --- a/dewarping/CylindricalSurfaceDewarper.cpp +++ b/dewarping/CylindricalSurfaceDewarper.cpp @@ -17,10 +17,10 @@ */ #include "CylindricalSurfaceDewarper.h" -#include "ToLineProjector.h" -#include "NumericTraits.h" #include #include +#include "NumericTraits.h" +#include "ToLineProjector.h" /* Naming conventions: @@ -43,374 +43,373 @@ namespace dewarping { class CylindricalSurfaceDewarper::CoupledPolylinesIterator { -public: - CoupledPolylinesIterator(const std::vector& img_directrix1, - const std::vector& img_directrix2, - const HomographicTransform<2, double>& pln2img, - const HomographicTransform<2, double>& img2pln); - - bool next(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); - -private: - void next1(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); - - void next2(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); - - void advance1(); - - void advance2(); - - std::vector::const_iterator m_seq1It; - std::vector::const_iterator m_seq2It; - std::vector::const_iterator m_seq1End; - std::vector::const_iterator m_seq2End; - HomographicTransform<2, double> m_pln2img; - HomographicTransform<2, double> m_img2pln; - Vec2d m_prevImgPt1; - Vec2d m_prevImgPt2; - Vec2d m_nextImgPt1; - Vec2d m_nextImgPt2; - double m_nextPlnX1; - double m_nextPlnX2; + public: + CoupledPolylinesIterator(const std::vector& img_directrix1, + const std::vector& img_directrix2, + const HomographicTransform<2, double>& pln2img, + const HomographicTransform<2, double>& img2pln); + + bool next(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); + + private: + void next1(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); + + void next2(QPointF& img_pt1, QPointF& img_pt2, double& pln_x); + + void advance1(); + + void advance2(); + + std::vector::const_iterator m_seq1It; + std::vector::const_iterator m_seq2It; + std::vector::const_iterator m_seq1End; + std::vector::const_iterator m_seq2End; + HomographicTransform<2, double> m_pln2img; + HomographicTransform<2, double> m_img2pln; + Vec2d m_prevImgPt1; + Vec2d m_prevImgPt2; + Vec2d m_nextImgPt1; + Vec2d m_nextImgPt2; + double m_nextPlnX1; + double m_nextPlnX2; }; CylindricalSurfaceDewarper::CylindricalSurfaceDewarper(const std::vector& img_directrix1, const std::vector& img_directrix2, double depth_perception) - : m_pln2img(calcPlnToImgHomography(img_directrix1, img_directrix2)), - m_img2pln(m_pln2img.inv()), - m_depthPerception(depth_perception), - m_plnStraightLineY(calcPlnStraightLineY(img_directrix1, img_directrix2, m_pln2img, m_img2pln)), - m_directrixArcLength(1.0), - m_imgDirectrix1Intersector(img_directrix1), - m_imgDirectrix2Intersector(img_directrix2) { - initArcLengthMapper(img_directrix1, img_directrix2); + : m_pln2img(calcPlnToImgHomography(img_directrix1, img_directrix2)), + m_img2pln(m_pln2img.inv()), + m_depthPerception(depth_perception), + m_plnStraightLineY(calcPlnStraightLineY(img_directrix1, img_directrix2, m_pln2img, m_img2pln)), + m_directrixArcLength(1.0), + m_imgDirectrix1Intersector(img_directrix1), + m_imgDirectrix2Intersector(img_directrix2) { + initArcLengthMapper(img_directrix1, img_directrix2); } CylindricalSurfaceDewarper::Generatrix CylindricalSurfaceDewarper::mapGeneratrix(double crv_x, State& state) const { - const double pln_x = m_arcLengthMapper.arcLenToX(crv_x, state.m_arcLengthHint); - - const Vec2d pln_top_pt(pln_x, 0); - const Vec2d pln_bottom_pt(pln_x, 1); - const Vec2d img_top_pt(m_pln2img(pln_top_pt)); - const Vec2d img_bottom_pt(m_pln2img(pln_bottom_pt)); - const QLineF img_generatrix(img_top_pt, img_bottom_pt); - const ToLineProjector projector(img_generatrix); - const Vec2d img_directrix1_pt(m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1)); - const Vec2d img_directrix2_pt(m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2)); - const Vec2d img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); - const double img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); - const double img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); - const double img_straight_line_proj(projector.projectionScalar(img_straight_line_pt)); - - boost::array, 3> pairs; - pairs[0] = std::make_pair(0.0, img_directrix1_proj); - pairs[1] = std::make_pair(1.0, img_directrix2_proj); - if ((std::fabs(m_plnStraightLineY) < 0.05) || (std::fabs(m_plnStraightLineY - 1.0) < 0.05)) { - pairs[2] = std::make_pair(0.5, 0.5 * (img_directrix1_proj + img_directrix2_proj)); - } else { - pairs[2] = std::make_pair(m_plnStraightLineY, img_straight_line_proj); - } - HomographicTransform<1, double> H(threePoint1DHomography(pairs)); - - return Generatrix(img_generatrix, H); + const double pln_x = m_arcLengthMapper.arcLenToX(crv_x, state.m_arcLengthHint); + + const Vec2d pln_top_pt(pln_x, 0); + const Vec2d pln_bottom_pt(pln_x, 1); + const Vec2d img_top_pt(m_pln2img(pln_top_pt)); + const Vec2d img_bottom_pt(m_pln2img(pln_bottom_pt)); + const QLineF img_generatrix(img_top_pt, img_bottom_pt); + const ToLineProjector projector(img_generatrix); + const Vec2d img_directrix1_pt(m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1)); + const Vec2d img_directrix2_pt(m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2)); + const Vec2d img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); + const double img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); + const double img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); + const double img_straight_line_proj(projector.projectionScalar(img_straight_line_pt)); + + boost::array, 3> pairs; + pairs[0] = std::make_pair(0.0, img_directrix1_proj); + pairs[1] = std::make_pair(1.0, img_directrix2_proj); + if ((std::fabs(m_plnStraightLineY) < 0.05) || (std::fabs(m_plnStraightLineY - 1.0) < 0.05)) { + pairs[2] = std::make_pair(0.5, 0.5 * (img_directrix1_proj + img_directrix2_proj)); + } else { + pairs[2] = std::make_pair(m_plnStraightLineY, img_straight_line_proj); + } + HomographicTransform<1, double> H(threePoint1DHomography(pairs)); + + return Generatrix(img_generatrix, H); } // CylindricalSurfaceDewarper::mapGeneratrix QPointF CylindricalSurfaceDewarper::mapToDewarpedSpace(const QPointF& img_pt) const { - State state; - - const double pln_x = m_img2pln(img_pt)[0]; - const double crv_x = m_arcLengthMapper.xToArcLen(pln_x, state.m_arcLengthHint); - - const Vec2d pln_top_pt(pln_x, 0); - const Vec2d pln_bottom_pt(pln_x, 1); - const Vec2d img_top_pt(m_pln2img(pln_top_pt)); - const Vec2d img_bottom_pt(m_pln2img(pln_bottom_pt)); - const QLineF img_generatrix(img_top_pt, img_bottom_pt); - const ToLineProjector projector(img_generatrix); - const Vec2d img_directrix1_pt(m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1)); - const Vec2d img_directrix2_pt(m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2)); - const Vec2d img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); - const double img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); - const double img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); - const double img_straight_line_proj(projector.projectionScalar(img_straight_line_pt)); - - boost::array, 3> pairs; - pairs[0] = std::make_pair(img_directrix1_proj, 0.0); - pairs[1] = std::make_pair(img_directrix2_proj, 1.0); - if ((std::fabs(m_plnStraightLineY) < 0.05) || (std::fabs(m_plnStraightLineY - 1.0) < 0.05)) { - pairs[2] = std::make_pair(0.5 * (img_directrix1_proj + img_directrix2_proj), 0.5); - } else { - pairs[2] = std::make_pair(img_straight_line_proj, m_plnStraightLineY); - } - const HomographicTransform<1, double> H(threePoint1DHomography(pairs)); - - const double img_pt_proj(projector.projectionScalar(img_pt)); - const double crv_y = H(img_pt_proj); - - return QPointF(crv_x, crv_y); + State state; + + const double pln_x = m_img2pln(img_pt)[0]; + const double crv_x = m_arcLengthMapper.xToArcLen(pln_x, state.m_arcLengthHint); + + const Vec2d pln_top_pt(pln_x, 0); + const Vec2d pln_bottom_pt(pln_x, 1); + const Vec2d img_top_pt(m_pln2img(pln_top_pt)); + const Vec2d img_bottom_pt(m_pln2img(pln_bottom_pt)); + const QLineF img_generatrix(img_top_pt, img_bottom_pt); + const ToLineProjector projector(img_generatrix); + const Vec2d img_directrix1_pt(m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1)); + const Vec2d img_directrix2_pt(m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2)); + const Vec2d img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); + const double img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); + const double img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); + const double img_straight_line_proj(projector.projectionScalar(img_straight_line_pt)); + + boost::array, 3> pairs; + pairs[0] = std::make_pair(img_directrix1_proj, 0.0); + pairs[1] = std::make_pair(img_directrix2_proj, 1.0); + if ((std::fabs(m_plnStraightLineY) < 0.05) || (std::fabs(m_plnStraightLineY - 1.0) < 0.05)) { + pairs[2] = std::make_pair(0.5 * (img_directrix1_proj + img_directrix2_proj), 0.5); + } else { + pairs[2] = std::make_pair(img_straight_line_proj, m_plnStraightLineY); + } + const HomographicTransform<1, double> H(threePoint1DHomography(pairs)); + + const double img_pt_proj(projector.projectionScalar(img_pt)); + const double crv_y = H(img_pt_proj); + + return QPointF(crv_x, crv_y); } // CylindricalSurfaceDewarper::mapToDewarpedSpace QPointF CylindricalSurfaceDewarper::mapToWarpedSpace(const QPointF& crv_pt) const { - State state; - const Generatrix gtx(mapGeneratrix(crv_pt.x(), state)); + State state; + const Generatrix gtx(mapGeneratrix(crv_pt.x(), state)); - return gtx.imgLine.pointAt(gtx.pln2img(crv_pt.y())); + return gtx.imgLine.pointAt(gtx.pln2img(crv_pt.y())); } HomographicTransform<2, double> CylindricalSurfaceDewarper::calcPlnToImgHomography( - const std::vector& img_directrix1, - const std::vector& img_directrix2) { - boost::array, 4> pairs; - pairs[0] = std::make_pair(QPointF(0, 0), img_directrix1.front()); - pairs[1] = std::make_pair(QPointF(1, 0), img_directrix1.back()); - pairs[2] = std::make_pair(QPointF(0, 1), img_directrix2.front()); - pairs[3] = std::make_pair(QPointF(1, 1), img_directrix2.back()); - - return fourPoint2DHomography(pairs); + const std::vector& img_directrix1, + const std::vector& img_directrix2) { + boost::array, 4> pairs; + pairs[0] = std::make_pair(QPointF(0, 0), img_directrix1.front()); + pairs[1] = std::make_pair(QPointF(1, 0), img_directrix1.back()); + pairs[2] = std::make_pair(QPointF(0, 1), img_directrix2.front()); + pairs[3] = std::make_pair(QPointF(1, 1), img_directrix2.back()); + + return fourPoint2DHomography(pairs); } double CylindricalSurfaceDewarper::calcPlnStraightLineY(const std::vector& img_directrix1, const std::vector& img_directrix2, const HomographicTransform<2, double> pln2img, const HomographicTransform<2, double> img2pln) { - double pln_y_accum = 0; - double weight_accum = 0; - - CoupledPolylinesIterator it(img_directrix1, img_directrix2, pln2img, img2pln); - QPointF img_curve1_pt; - QPointF img_curve2_pt; - double pln_x; - while (it.next(img_curve1_pt, img_curve2_pt, pln_x)) { - const QLineF img_generatrix(img_curve1_pt, img_curve2_pt); - const Vec2d img_line1_pt(pln2img(Vec2d(pln_x, 0))); - const Vec2d img_line2_pt(pln2img(Vec2d(pln_x, 1))); - const ToLineProjector projector(img_generatrix); - const double p1 = 0; - const double p2 = projector.projectionScalar(img_line1_pt); - const double p3 = projector.projectionScalar(img_line2_pt); - const double p4 = 1; - const double dp1 = p2 - p1; - const double dp2 = p4 - p3; - const double weight = std::fabs(dp1 + dp2); - if (weight < 0.01) { - continue; - } - - const double p0 = (p3 * dp1 + p2 * dp2) / (dp1 + dp2); - const Vec2d img_pt(img_generatrix.pointAt(p0)); - pln_y_accum += img2pln(img_pt)[1] * weight; - weight_accum += weight; + double pln_y_accum = 0; + double weight_accum = 0; + + CoupledPolylinesIterator it(img_directrix1, img_directrix2, pln2img, img2pln); + QPointF img_curve1_pt; + QPointF img_curve2_pt; + double pln_x; + while (it.next(img_curve1_pt, img_curve2_pt, pln_x)) { + const QLineF img_generatrix(img_curve1_pt, img_curve2_pt); + const Vec2d img_line1_pt(pln2img(Vec2d(pln_x, 0))); + const Vec2d img_line2_pt(pln2img(Vec2d(pln_x, 1))); + const ToLineProjector projector(img_generatrix); + const double p1 = 0; + const double p2 = projector.projectionScalar(img_line1_pt); + const double p3 = projector.projectionScalar(img_line2_pt); + const double p4 = 1; + const double dp1 = p2 - p1; + const double dp2 = p4 - p3; + const double weight = std::fabs(dp1 + dp2); + if (weight < 0.01) { + continue; } - return weight_accum == 0 ? 0.5 : pln_y_accum / weight_accum; + const double p0 = (p3 * dp1 + p2 * dp2) / (dp1 + dp2); + const Vec2d img_pt(img_generatrix.pointAt(p0)); + pln_y_accum += img2pln(img_pt)[1] * weight; + weight_accum += weight; + } + + return weight_accum == 0 ? 0.5 : pln_y_accum / weight_accum; } // CylindricalSurfaceDewarper::calcPlnStraightLineY HomographicTransform<2, double> CylindricalSurfaceDewarper::fourPoint2DHomography( - const boost::array, 4>& pairs) { - VecNT<64, double> A; - VecNT<8, double> B; - double* pa = A.data(); - double* pb = B.data(); - int i = 0; - - typedef std::pair Pair; - for (const Pair& pair : pairs) { - const QPointF from(pair.first); - const QPointF to(pair.second); - - pa[8 * 0] = -from.x(); - pa[8 * 1] = -from.y(); - pa[8 * 2] = -1; - pa[8 * 3] = 0; - pa[8 * 4] = 0; - pa[8 * 5] = 0; - pa[8 * 6] = to.x() * from.x(); - pa[8 * 7] = to.x() * from.y(); - pb[0] = -to.x(); - ++pa; - ++pb; - - pa[8 * 0] = 0; - pa[8 * 1] = 0; - pa[8 * 2] = 0; - pa[8 * 3] = -from.x(); - pa[8 * 4] = -from.y(); - pa[8 * 5] = -1; - pa[8 * 6] = to.y() * from.x(); - pa[8 * 7] = to.y() * from.y(); - pb[0] = -to.y(); - ++pa; - ++pb; - } - - VecNT<9, double> H; - H[8] = 1.0; - - MatrixCalc mc; - mc(A, 8, 8).solve(mc(B, 8, 1)).write(H); - mc(H, 3, 3).trans().write(H); - - return HomographicTransform<2, double>(H); + const boost::array, 4>& pairs) { + VecNT<64, double> A; + VecNT<8, double> B; + double* pa = A.data(); + double* pb = B.data(); + int i = 0; + + typedef std::pair Pair; + for (const Pair& pair : pairs) { + const QPointF from(pair.first); + const QPointF to(pair.second); + + pa[8 * 0] = -from.x(); + pa[8 * 1] = -from.y(); + pa[8 * 2] = -1; + pa[8 * 3] = 0; + pa[8 * 4] = 0; + pa[8 * 5] = 0; + pa[8 * 6] = to.x() * from.x(); + pa[8 * 7] = to.x() * from.y(); + pb[0] = -to.x(); + ++pa; + ++pb; + + pa[8 * 0] = 0; + pa[8 * 1] = 0; + pa[8 * 2] = 0; + pa[8 * 3] = -from.x(); + pa[8 * 4] = -from.y(); + pa[8 * 5] = -1; + pa[8 * 6] = to.y() * from.x(); + pa[8 * 7] = to.y() * from.y(); + pb[0] = -to.y(); + ++pa; + ++pb; + } + + VecNT<9, double> H; + H[8] = 1.0; + + MatrixCalc mc; + mc(A, 8, 8).solve(mc(B, 8, 1)).write(H); + mc(H, 3, 3).trans().write(H); + + return HomographicTransform<2, double>(H); } // CylindricalSurfaceDewarper::fourPoint2DHomography HomographicTransform<1, double> CylindricalSurfaceDewarper::threePoint1DHomography( - const boost::array, 3>& pairs) { - VecNT<9, double> A; - VecNT<3, double> B; - double* pa = A.data(); - double* pb = B.data(); - - typedef std::pair Pair; - for (const Pair& pair : pairs) { - const double from = pair.first; - const double to = pair.second; - - pa[3 * 0] = -from; - pa[3 * 1] = -1; - pa[3 * 2] = from * to; - pb[0] = -to; - ++pa; - ++pb; - } - - Vec4d H; - H[3] = 1.0; - - MatrixCalc mc; - mc(A, 3, 3).solve(mc(B, 3, 1)).write(H); - mc(H, 2, 2).trans().write(H); - - return HomographicTransform<1, double>(H); + const boost::array, 3>& pairs) { + VecNT<9, double> A; + VecNT<3, double> B; + double* pa = A.data(); + double* pb = B.data(); + + typedef std::pair Pair; + for (const Pair& pair : pairs) { + const double from = pair.first; + const double to = pair.second; + + pa[3 * 0] = -from; + pa[3 * 1] = -1; + pa[3 * 2] = from * to; + pb[0] = -to; + ++pa; + ++pb; + } + + Vec4d H; + H[3] = 1.0; + + MatrixCalc mc; + mc(A, 3, 3).solve(mc(B, 3, 1)).write(H); + mc(H, 2, 2).trans().write(H); + + return HomographicTransform<1, double>(H); } void CylindricalSurfaceDewarper::initArcLengthMapper(const std::vector& img_directrix1, const std::vector& img_directrix2) { - double prev_elevation = 0; - CoupledPolylinesIterator it(img_directrix1, img_directrix2, m_pln2img, m_img2pln); - QPointF img_curve1_pt; - QPointF img_curve2_pt; - double prev_pln_x = NumericTraits::min(); - double pln_x; - while (it.next(img_curve1_pt, img_curve2_pt, pln_x)) { - if (pln_x <= prev_pln_x) { - // This means our surface has an S-like shape. - // We don't support that, and to make ReverseArcLength happy, - // we have to skip such points. - continue; - } - - const QLineF img_generatrix(img_curve1_pt, img_curve2_pt); - const Vec2d img_line1_pt(m_pln2img(Vec2d(pln_x, 0))); - const Vec2d img_line2_pt(m_pln2img(Vec2d(pln_x, 1))); - - const ToLineProjector projector(img_generatrix); - const double y1 = projector.projectionScalar(img_line1_pt); - const double y2 = projector.projectionScalar(img_line2_pt); - - double elevation = m_depthPerception * (1.0 - (y2 - y1)); - elevation = qBound(-0.5, elevation, 0.5); - - m_arcLengthMapper.addSample(pln_x, elevation); - prev_elevation = elevation; - prev_pln_x = pln_x; + double prev_elevation = 0; + CoupledPolylinesIterator it(img_directrix1, img_directrix2, m_pln2img, m_img2pln); + QPointF img_curve1_pt; + QPointF img_curve2_pt; + double prev_pln_x = NumericTraits::min(); + double pln_x; + while (it.next(img_curve1_pt, img_curve2_pt, pln_x)) { + if (pln_x <= prev_pln_x) { + // This means our surface has an S-like shape. + // We don't support that, and to make ReverseArcLength happy, + // we have to skip such points. + continue; } - // Needs to go before normalizeRange(). - m_directrixArcLength = m_arcLengthMapper.totalArcLength(); - // Scale arc lengths to the range of [0, 1]. - m_arcLengthMapper.normalizeRange(1); + const QLineF img_generatrix(img_curve1_pt, img_curve2_pt); + const Vec2d img_line1_pt(m_pln2img(Vec2d(pln_x, 0))); + const Vec2d img_line2_pt(m_pln2img(Vec2d(pln_x, 1))); + + const ToLineProjector projector(img_generatrix); + const double y1 = projector.projectionScalar(img_line1_pt); + const double y2 = projector.projectionScalar(img_line2_pt); + + double elevation = m_depthPerception * (1.0 - (y2 - y1)); + elevation = qBound(-0.5, elevation, 0.5); + + m_arcLengthMapper.addSample(pln_x, elevation); + prev_elevation = elevation; + prev_pln_x = pln_x; + } + + // Needs to go before normalizeRange(). + m_directrixArcLength = m_arcLengthMapper.totalArcLength(); + // Scale arc lengths to the range of [0, 1]. + m_arcLengthMapper.normalizeRange(1); } // CylindricalSurfaceDewarper::initArcLengthMapper /*======================= CoupledPolylinesIterator =========================*/ CylindricalSurfaceDewarper::CoupledPolylinesIterator::CoupledPolylinesIterator( - const std::vector& img_directrix1, - const std::vector& img_directrix2, - const HomographicTransform<2, double>& pln2img, - const HomographicTransform<2, double>& img2pln) - : m_seq1It(img_directrix1.begin()), - m_seq2It(img_directrix2.begin()), - m_seq1End(img_directrix1.end()), - m_seq2End(img_directrix2.end()), - m_pln2img(pln2img), - m_img2pln(img2pln), - m_prevImgPt1(*m_seq1It), - m_prevImgPt2(*m_seq2It), - m_nextImgPt1(m_prevImgPt1), - m_nextImgPt2(m_prevImgPt2), - m_nextPlnX1(0), - m_nextPlnX2(0) { -} + const std::vector& img_directrix1, + const std::vector& img_directrix2, + const HomographicTransform<2, double>& pln2img, + const HomographicTransform<2, double>& img2pln) + : m_seq1It(img_directrix1.begin()), + m_seq2It(img_directrix2.begin()), + m_seq1End(img_directrix1.end()), + m_seq2End(img_directrix2.end()), + m_pln2img(pln2img), + m_img2pln(img2pln), + m_prevImgPt1(*m_seq1It), + m_prevImgPt2(*m_seq2It), + m_nextImgPt1(m_prevImgPt1), + m_nextImgPt2(m_prevImgPt2), + m_nextPlnX1(0), + m_nextPlnX2(0) {} bool CylindricalSurfaceDewarper::CoupledPolylinesIterator::next(QPointF& img_pt1, QPointF& img_pt2, double& pln_x) { - if ((m_nextPlnX1 < m_nextPlnX2) && (m_seq1It != m_seq1End)) { - next1(img_pt1, img_pt2, pln_x); + if ((m_nextPlnX1 < m_nextPlnX2) && (m_seq1It != m_seq1End)) { + next1(img_pt1, img_pt2, pln_x); - return true; - } else if (m_seq2It != m_seq2End) { - next2(img_pt1, img_pt2, pln_x); + return true; + } else if (m_seq2It != m_seq2End) { + next2(img_pt1, img_pt2, pln_x); - return true; - } else { - return false; - } + return true; + } else { + return false; + } } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::next1(QPointF& img_pt1, QPointF& img_pt2, double& pln_x) { - const Vec2d pln_pt1(m_img2pln(m_nextImgPt1)); - pln_x = pln_pt1[0]; - img_pt1 = m_nextImgPt1; + const Vec2d pln_pt1(m_img2pln(m_nextImgPt1)); + pln_x = pln_pt1[0]; + img_pt1 = m_nextImgPt1; - const Vec2d pln_ptx(pln_pt1[0], pln_pt1[1] + 1); - const Vec2d img_ptx(m_pln2img(pln_ptx)); + const Vec2d pln_ptx(pln_pt1[0], pln_pt1[1] + 1); + const Vec2d img_ptx(m_pln2img(pln_ptx)); - if (QLineF(img_pt1, img_ptx).intersect(QLineF(m_nextImgPt2, m_prevImgPt2), &img_pt2) == QLineF::NoIntersection) { - img_pt2 = m_nextImgPt2; - } + if (QLineF(img_pt1, img_ptx).intersect(QLineF(m_nextImgPt2, m_prevImgPt2), &img_pt2) == QLineF::NoIntersection) { + img_pt2 = m_nextImgPt2; + } - advance1(); - if ((m_seq2It != m_seq2End) && (Vec2d(m_nextImgPt2 - img_pt2).squaredNorm() < 1)) { - advance2(); - } + advance1(); + if ((m_seq2It != m_seq2End) && (Vec2d(m_nextImgPt2 - img_pt2).squaredNorm() < 1)) { + advance2(); + } } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::next2(QPointF& img_pt1, QPointF& img_pt2, double& pln_x) { - const Vec2d pln_pt2(m_img2pln(m_nextImgPt2)); - pln_x = pln_pt2[0]; - img_pt2 = m_nextImgPt2; + const Vec2d pln_pt2(m_img2pln(m_nextImgPt2)); + pln_x = pln_pt2[0]; + img_pt2 = m_nextImgPt2; - const Vec2d pln_ptx(pln_pt2[0], pln_pt2[1] + 1); - const Vec2d img_ptx(m_pln2img(pln_ptx)); + const Vec2d pln_ptx(pln_pt2[0], pln_pt2[1] + 1); + const Vec2d img_ptx(m_pln2img(pln_ptx)); - if (QLineF(img_pt2, img_ptx).intersect(QLineF(m_nextImgPt1, m_prevImgPt1), &img_pt1) == QLineF::NoIntersection) { - img_pt1 = m_nextImgPt1; - } + if (QLineF(img_pt2, img_ptx).intersect(QLineF(m_nextImgPt1, m_prevImgPt1), &img_pt1) == QLineF::NoIntersection) { + img_pt1 = m_nextImgPt1; + } - advance2(); - if ((m_seq1It != m_seq1End) && (Vec2d(m_nextImgPt1 - img_pt1).squaredNorm() < 1)) { - advance1(); - } + advance2(); + if ((m_seq1It != m_seq1End) && (Vec2d(m_nextImgPt1 - img_pt1).squaredNorm() < 1)) { + advance1(); + } } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::advance1() { - if (++m_seq1It == m_seq1End) { - return; - } + if (++m_seq1It == m_seq1End) { + return; + } - m_prevImgPt1 = m_nextImgPt1; - m_nextImgPt1 = *m_seq1It; - m_nextPlnX1 = m_img2pln(m_nextImgPt1)[0]; + m_prevImgPt1 = m_nextImgPt1; + m_nextImgPt1 = *m_seq1It; + m_nextPlnX1 = m_img2pln(m_nextImgPt1)[0]; } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::advance2() { - if (++m_seq2It == m_seq2End) { - return; - } + if (++m_seq2It == m_seq2End) { + return; + } - m_prevImgPt2 = m_nextImgPt2; - m_nextImgPt2 = *m_seq2It; - m_nextPlnX2 = m_img2pln(m_nextImgPt2)[0]; + m_prevImgPt2 = m_nextImgPt2; + m_nextImgPt2 = *m_seq2It; + m_nextPlnX2 = m_img2pln(m_nextImgPt2)[0]; } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/CylindricalSurfaceDewarper.h b/dewarping/CylindricalSurfaceDewarper.h index ccd48564a..c6be9c50c 100644 --- a/dewarping/CylindricalSurfaceDewarper.h +++ b/dewarping/CylindricalSurfaceDewarper.h @@ -19,99 +19,96 @@ #ifndef DEWARPING_CYLINDRICAL_SURFACE_DEWARPER_H_ #define DEWARPING_CYLINDRICAL_SURFACE_DEWARPER_H_ -#include "HomographicTransform.h" -#include "PolylineIntersector.h" -#include "ArcLengthMapper.h" +#include +#include #include -#include #include -#include -#include +#include +#include "ArcLengthMapper.h" +#include "HomographicTransform.h" +#include "PolylineIntersector.h" namespace dewarping { class CylindricalSurfaceDewarper { -public: - class State { - friend class CylindricalSurfaceDewarper; - - private: - PolylineIntersector::Hint m_intersectionHint1; - PolylineIntersector::Hint m_intersectionHint2; - ArcLengthMapper::Hint m_arcLengthHint; - }; - - - struct Generatrix { - QLineF imgLine; - HomographicTransform<1, double> pln2img; - - Generatrix(const QLineF& img_line, const HomographicTransform<1, double>& H) : imgLine(img_line), pln2img(H) { - } - }; - - /** - * \param depth_perception The distance from the camera to the plane formed - * by two outer generatrixes, in some unknown units :) - * This model assumes that plane is perpendicular to the camera direction. - * In practice, just use values between 1 and 3. - */ - CylindricalSurfaceDewarper(const std::vector& img_directrix1, - const std::vector& img_directrix2, - double depth_perception); - - /** - * \brief Returns the arc length of a directrix, assuming its - * chord length is one. - */ - double directrixArcLength() const { - return m_directrixArcLength; - } - - Generatrix mapGeneratrix(double crv_x, State& state) const; - - /** - * Transforms a point from warped image coordinates - * to dewarped normalized coordinates. See comments - * in the beginning of the *.cpp file for more information - * about coordinate systems we work with. - */ - QPointF mapToDewarpedSpace(const QPointF& img_pt) const; - - /** - * Transforms a point from dewarped normalized coordinates - * to warped image coordinates. See comments in the beginning - * of the *.cpp file for more information about coordinate - * systems we owork with. - */ - QPointF mapToWarpedSpace(const QPointF& crv_pt) const; - -private: - class CoupledPolylinesIterator; - - static HomographicTransform<2, double> calcPlnToImgHomography(const std::vector& img_directrix1, - const std::vector& img_directrix2); - - static double calcPlnStraightLineY(const std::vector& img_directrix1, - const std::vector& img_directrix2, - HomographicTransform<2, double> pln2img, - HomographicTransform<2, double> img2pln); - - static HomographicTransform<2, double> fourPoint2DHomography( - const boost::array, 4>& pairs); - - static HomographicTransform<1, double> threePoint1DHomography( - const boost::array, 3>& pairs); - - void initArcLengthMapper(const std::vector& img_directrix1, const std::vector& img_directrix2); - - HomographicTransform<2, double> m_pln2img; - HomographicTransform<2, double> m_img2pln; - double m_depthPerception; - double m_plnStraightLineY; - double m_directrixArcLength; - ArcLengthMapper m_arcLengthMapper; - PolylineIntersector m_imgDirectrix1Intersector; - PolylineIntersector m_imgDirectrix2Intersector; + public: + class State { + friend class CylindricalSurfaceDewarper; + + private: + PolylineIntersector::Hint m_intersectionHint1; + PolylineIntersector::Hint m_intersectionHint2; + ArcLengthMapper::Hint m_arcLengthHint; + }; + + + struct Generatrix { + QLineF imgLine; + HomographicTransform<1, double> pln2img; + + Generatrix(const QLineF& img_line, const HomographicTransform<1, double>& H) : imgLine(img_line), pln2img(H) {} + }; + + /** + * \param depth_perception The distance from the camera to the plane formed + * by two outer generatrixes, in some unknown units :) + * This model assumes that plane is perpendicular to the camera direction. + * In practice, just use values between 1 and 3. + */ + CylindricalSurfaceDewarper(const std::vector& img_directrix1, + const std::vector& img_directrix2, + double depth_perception); + + /** + * \brief Returns the arc length of a directrix, assuming its + * chord length is one. + */ + double directrixArcLength() const { return m_directrixArcLength; } + + Generatrix mapGeneratrix(double crv_x, State& state) const; + + /** + * Transforms a point from warped image coordinates + * to dewarped normalized coordinates. See comments + * in the beginning of the *.cpp file for more information + * about coordinate systems we work with. + */ + QPointF mapToDewarpedSpace(const QPointF& img_pt) const; + + /** + * Transforms a point from dewarped normalized coordinates + * to warped image coordinates. See comments in the beginning + * of the *.cpp file for more information about coordinate + * systems we owork with. + */ + QPointF mapToWarpedSpace(const QPointF& crv_pt) const; + + private: + class CoupledPolylinesIterator; + + static HomographicTransform<2, double> calcPlnToImgHomography(const std::vector& img_directrix1, + const std::vector& img_directrix2); + + static double calcPlnStraightLineY(const std::vector& img_directrix1, + const std::vector& img_directrix2, + HomographicTransform<2, double> pln2img, + HomographicTransform<2, double> img2pln); + + static HomographicTransform<2, double> fourPoint2DHomography( + const boost::array, 4>& pairs); + + static HomographicTransform<1, double> threePoint1DHomography( + const boost::array, 3>& pairs); + + void initArcLengthMapper(const std::vector& img_directrix1, const std::vector& img_directrix2); + + HomographicTransform<2, double> m_pln2img; + HomographicTransform<2, double> m_img2pln; + double m_depthPerception; + double m_plnStraightLineY; + double m_directrixArcLength; + ArcLengthMapper m_arcLengthMapper; + PolylineIntersector m_imgDirectrix1Intersector; + PolylineIntersector m_imgDirectrix2Intersector; }; } // namespace dewarping #endif // ifndef DEWARPING_CYLINDRICAL_SURFACE_DEWARPER_H_ diff --git a/dewarping/DetectVertContentBounds.cpp b/dewarping/DetectVertContentBounds.cpp index 08ea5220b..8b9514414 100644 --- a/dewarping/DetectVertContentBounds.cpp +++ b/dewarping/DetectVertContentBounds.cpp @@ -17,462 +17,449 @@ */ #include "DetectVertContentBounds.h" -#include "DebugImages.h" -#include "VecNT.h" -#include "imageproc/BinaryImage.h" -#include "imageproc/Constants.h" #include #include #include -#include #include +#include +#include "DebugImages.h" +#include "VecNT.h" +#include "imageproc/BinaryImage.h" +#include "imageproc/Constants.h" using namespace imageproc; namespace dewarping { namespace { struct VertRange { - int top; - int bottom; + int top; + int bottom; - VertRange() : top(-1), bottom(-1) { - } + VertRange() : top(-1), bottom(-1) {} - VertRange(int t, int b) : top(t), bottom(b) { - } + VertRange(int t, int b) : top(t), bottom(b) {} - bool isValid() const { - return top != -1; - } + bool isValid() const { return top != -1; } }; struct Segment { - QLine line; - Vec2d unitVec; - int vertDist; + QLine line; + Vec2d unitVec; + int vertDist; - bool distToVertLine(int vert_line_x) const { - return (bool) std::min(std::abs(line.p1().x() - vert_line_x), std::abs(line.p2().x() - vert_line_x)); - } + bool distToVertLine(int vert_line_x) const { + return (bool) std::min(std::abs(line.p1().x() - vert_line_x), std::abs(line.p2().x() - vert_line_x)); + } - Segment(const QLine& line, const Vec2d& vec, int dist) : line(line), unitVec(vec), vertDist(dist) { - } + Segment(const QLine& line, const Vec2d& vec, int dist) : line(line), unitVec(vec), vertDist(dist) {} }; struct RansacModel { - std::vector segments; - int totalVertDist; // Sum of individual Segment::vertDist + std::vector segments; + int totalVertDist; // Sum of individual Segment::vertDist - RansacModel() : totalVertDist(0) { - } + RansacModel() : totalVertDist(0) {} - void add(const Segment& seg) { - segments.push_back(seg); - totalVertDist += seg.vertDist; - } + void add(const Segment& seg) { + segments.push_back(seg); + totalVertDist += seg.vertDist; + } - bool betterThan(const RansacModel& other) const { - return totalVertDist > other.totalVertDist; - } + bool betterThan(const RansacModel& other) const { return totalVertDist > other.totalVertDist; } - void swap(RansacModel& other) { - segments.swap(other.segments); - std::swap(totalVertDist, other.totalVertDist); - } + void swap(RansacModel& other) { + segments.swap(other.segments); + std::swap(totalVertDist, other.totalVertDist); + } }; class RansacAlgo { -public: - explicit RansacAlgo(const std::vector& segments); + public: + explicit RansacAlgo(const std::vector& segments); - void buildAndAssessModel(const Segment& seed_segment); + void buildAndAssessModel(const Segment& seed_segment); - RansacModel& bestModel() { - return m_bestModel; - } + RansacModel& bestModel() { return m_bestModel; } - const RansacModel& bestModel() const { - return m_bestModel; - } + const RansacModel& bestModel() const { return m_bestModel; } -private: - const std::vector& m_rSegments; - RansacModel m_bestModel; - double m_cosThreshold; + private: + const std::vector& m_segments; + RansacModel m_bestModel; + double m_cosThreshold; }; class SequentialColumnProcessor { -public: - enum LeftOrRight { LEFT, RIGHT }; + public: + enum LeftOrRight { LEFT, RIGHT }; - SequentialColumnProcessor(const QSize& page_size, LeftOrRight left_or_right); + SequentialColumnProcessor(const QSize& page_size, LeftOrRight left_or_right); - void process(int x, const VertRange& range); + void process(int x, const VertRange& range); - QLineF approximateWithLine(std::vector* dbg_segments = nullptr) const; + QLineF approximateWithLine(std::vector* dbg_segments = nullptr) const; - QImage visualizeEnvelope(const QImage& background); + QImage visualizeEnvelope(const QImage& background); -private: - bool topMidBottomConcave(QPoint top, QPoint mid, QPoint bottom) const; + private: + bool topMidBottomConcave(QPoint top, QPoint mid, QPoint bottom) const; - static int crossZ(QPoint v1, QPoint v2); + static int crossZ(QPoint v1, QPoint v2); - bool segmentIsTooLong(QPoint p1, QPoint p2) const; + bool segmentIsTooLong(QPoint p1, QPoint p2) const; - QLineF interpolateSegments(const std::vector& segments) const; + QLineF interpolateSegments(const std::vector& segments) const; - // Top and bottom points on the leftmost or the rightmost line. - QPoint m_leadingTop; - QPoint m_leadingBottom; - std::deque m_path; // Top to bottom. - int m_maxSegmentSqLen; - int m_leftMinusOneRightOne; - LeftOrRight m_leftOrRight; + // Top and bottom points on the leftmost or the rightmost line. + QPoint m_leadingTop; + QPoint m_leadingBottom; + std::deque m_path; // Top to bottom. + int m_maxSegmentSqLen; + int m_leftMinusOneRightOne; + LeftOrRight m_leftOrRight; }; RansacAlgo::RansacAlgo(const std::vector& segments) - : m_rSegments(segments), m_cosThreshold(std::cos(4.0 * constants::DEG2RAD)) { -} + : m_segments(segments), m_cosThreshold(std::cos(4.0 * constants::DEG2RAD)) {} void RansacAlgo::buildAndAssessModel(const Segment& seed_segment) { - RansacModel cur_model; - cur_model.add(seed_segment); - - for (const Segment& seg : m_rSegments) { - const double cos = seg.unitVec.dot(seed_segment.unitVec); - if (cos > m_cosThreshold) { - cur_model.add(seg); - } - } + RansacModel cur_model; + cur_model.add(seed_segment); - if (cur_model.betterThan(m_bestModel)) { - cur_model.swap(m_bestModel); + for (const Segment& seg : m_segments) { + const double cos = seg.unitVec.dot(seed_segment.unitVec); + if (cos > m_cosThreshold) { + cur_model.add(seg); } + } + + if (cur_model.betterThan(m_bestModel)) { + cur_model.swap(m_bestModel); + } } SequentialColumnProcessor::SequentialColumnProcessor(const QSize& page_size, LeftOrRight left_or_right) - : m_leftMinusOneRightOne(left_or_right == LEFT ? -1 : 1), m_leftOrRight(left_or_right) { - const int w = page_size.width(); - const int h = page_size.height(); - m_maxSegmentSqLen = (w * w + h * h) / 3; + : m_leftMinusOneRightOne(left_or_right == LEFT ? -1 : 1), m_leftOrRight(left_or_right) { + const int w = page_size.width(); + const int h = page_size.height(); + m_maxSegmentSqLen = (w * w + h * h) / 3; } void SequentialColumnProcessor::process(int x, const VertRange& range) { - if (!range.isValid()) { - return; + if (!range.isValid()) { + return; + } + + if (m_path.empty()) { + m_leadingTop = QPoint(x, range.top); + m_leadingBottom = QPoint(x, range.bottom); + m_path.push_front(m_leadingTop); + + if (range.top != range.bottom) { // We don't want zero length segments in m_path. + m_path.push_back(m_leadingBottom); } - if (m_path.empty()) { - m_leadingTop = QPoint(x, range.top); - m_leadingBottom = QPoint(x, range.bottom); - m_path.push_front(m_leadingTop); + return; + } - if (range.top != range.bottom) { // We don't want zero length segments in m_path. - m_path.push_back(m_leadingBottom); - } + if (range.top < m_path.front().y()) { + // Growing towards the top. + const QPoint top(x, range.top); + // Now we decide if we need to trim the path before + // adding a new element to it to preserve convexity. + const size_t size = m_path.size(); + size_t mid_idx = 0; + size_t bottom_idx = 1; - return; + for (; bottom_idx < size; ++mid_idx, ++bottom_idx) { + if (!topMidBottomConcave(top, m_path[mid_idx], m_path[bottom_idx])) { + break; + } } - if (range.top < m_path.front().y()) { - // Growing towards the top. - const QPoint top(x, range.top); - // Now we decide if we need to trim the path before - // adding a new element to it to preserve convexity. - const size_t size = m_path.size(); - size_t mid_idx = 0; - size_t bottom_idx = 1; - - for (; bottom_idx < size; ++mid_idx, ++bottom_idx) { - if (!topMidBottomConcave(top, m_path[mid_idx], m_path[bottom_idx])) { - break; - } - } - - // We avoid trimming the path too much. This helps cases like a heading - // wider than the rest of the text. - if (!segmentIsTooLong(top, m_path[mid_idx])) { - m_path.erase(m_path.begin(), m_path.begin() + mid_idx); - } - - m_path.push_front(top); + // We avoid trimming the path too much. This helps cases like a heading + // wider than the rest of the text. + if (!segmentIsTooLong(top, m_path[mid_idx])) { + m_path.erase(m_path.begin(), m_path.begin() + mid_idx); } - if (range.bottom > m_path.back().y()) { - // Growing towards the bottom. - const QPoint bottom(x, range.bottom); - - // Now we decide if we need to trim the path before - // adding a new element to it to preserve convexity. - auto mid_idx = static_cast(m_path.size() - 1); - int top_idx = mid_idx - 1; - - for (; top_idx >= 0; --top_idx, --mid_idx) { - if (!topMidBottomConcave(m_path[top_idx], m_path[mid_idx], bottom)) { - break; - } - } - // We avoid trimming the path too much. This helps cases like a heading - // wider than the rest of the text. - if (!segmentIsTooLong(bottom, m_path[mid_idx])) { - m_path.erase(m_path.begin() + (mid_idx + 1), m_path.end()); - } - - m_path.push_back(bottom); + m_path.push_front(top); + } + + if (range.bottom > m_path.back().y()) { + // Growing towards the bottom. + const QPoint bottom(x, range.bottom); + + // Now we decide if we need to trim the path before + // adding a new element to it to preserve convexity. + auto mid_idx = static_cast(m_path.size() - 1); + int top_idx = mid_idx - 1; + + for (; top_idx >= 0; --top_idx, --mid_idx) { + if (!topMidBottomConcave(m_path[top_idx], m_path[mid_idx], bottom)) { + break; + } } + // We avoid trimming the path too much. This helps cases like a heading + // wider than the rest of the text. + if (!segmentIsTooLong(bottom, m_path[mid_idx])) { + m_path.erase(m_path.begin() + (mid_idx + 1), m_path.end()); + } + + m_path.push_back(bottom); + } } // SequentialColumnProcessor::process bool SequentialColumnProcessor::topMidBottomConcave(QPoint top, QPoint mid, QPoint bottom) const { - const int cross_z = crossZ(mid - top, bottom - mid); + const int cross_z = crossZ(mid - top, bottom - mid); - return cross_z * m_leftMinusOneRightOne < 0; + return cross_z * m_leftMinusOneRightOne < 0; } int SequentialColumnProcessor::crossZ(QPoint v1, QPoint v2) { - return v1.x() * v2.y() - v2.x() * v1.y(); + return v1.x() * v2.y() - v2.x() * v1.y(); } bool SequentialColumnProcessor::segmentIsTooLong(const QPoint p1, const QPoint p2) const { - const QPoint v(p2 - p1); - const int sqlen = v.x() * v.x() + v.y() * v.y(); + const QPoint v(p2 - p1); + const int sqlen = v.x() * v.x() + v.y() * v.y(); - return sqlen > m_maxSegmentSqLen; + return sqlen > m_maxSegmentSqLen; } QLineF SequentialColumnProcessor::approximateWithLine(std::vector* dbg_segments) const { - using namespace boost::lambda; - - const size_t num_points = m_path.size(); - - std::vector segments; - segments.reserve(num_points); - // Collect line segments from m_path and convert them to unit vectors. - for (size_t i = 1; i < num_points; ++i) { - const QPoint pt1(m_path[i - 1]); - const QPoint pt2(m_path[i]); - assert(pt2.y() > pt1.y()); - - Vec2d vec(pt2 - pt1); - if (std::fabs(vec[0]) > std::fabs(vec[1])) { - // We don't want segments that are more horizontal than vertical. - continue; - } - - vec /= std::sqrt(vec.squaredNorm()); - segments.emplace_back(QLine(pt1, pt2), vec, pt2.y() - pt1.y()); - } - - - // Run RANSAC on the segments. - - RansacAlgo ransac(segments); - qsrand(0); // Repeatablity is important. - - // We want to make sure we do pick a few segments closest - // to the edge, so let's sort segments appropriately - // and manually feed the best ones to RANSAC. - const size_t num_best_segments = std::min(6, segments.size()); - std::partial_sort(segments.begin(), segments.begin() + num_best_segments, segments.end(), - bind(&Segment::distToVertLine, _1, m_leadingTop.x()) - < bind(&Segment::distToVertLine, _2, m_leadingTop.x())); - for (size_t i = 0; i < num_best_segments; ++i) { - ransac.buildAndAssessModel(segments[i]); - } - // Continue with random samples. - const int ransac_iterations = segments.empty() ? 0 : 200; - for (int i = 0; i < ransac_iterations; ++i) { - ransac.buildAndAssessModel(segments[qrand() % segments.size()]); - } - - if (ransac.bestModel().segments.empty()) { - return QLineF(m_leadingTop, m_leadingTop + QPointF(0, 1)); - } - - const QLineF line(interpolateSegments(ransac.bestModel().segments)); - - if (dbg_segments) { - // Has to be the last thing we do with best model. - dbg_segments->swap(ransac.bestModel().segments); - } - - return line; + using namespace boost::lambda; + + const size_t num_points = m_path.size(); + + std::vector segments; + segments.reserve(num_points); + // Collect line segments from m_path and convert them to unit vectors. + for (size_t i = 1; i < num_points; ++i) { + const QPoint pt1(m_path[i - 1]); + const QPoint pt2(m_path[i]); + assert(pt2.y() > pt1.y()); + + Vec2d vec(pt2 - pt1); + if (std::fabs(vec[0]) > std::fabs(vec[1])) { + // We don't want segments that are more horizontal than vertical. + continue; + } + + vec /= std::sqrt(vec.squaredNorm()); + segments.emplace_back(QLine(pt1, pt2), vec, pt2.y() - pt1.y()); + } + + + // Run RANSAC on the segments. + + RansacAlgo ransac(segments); + qsrand(0); // Repeatablity is important. + + // We want to make sure we do pick a few segments closest + // to the edge, so let's sort segments appropriately + // and manually feed the best ones to RANSAC. + const size_t num_best_segments = std::min(6, segments.size()); + std::partial_sort( + segments.begin(), segments.begin() + num_best_segments, segments.end(), + bind(&Segment::distToVertLine, _1, m_leadingTop.x()) < bind(&Segment::distToVertLine, _2, m_leadingTop.x())); + for (size_t i = 0; i < num_best_segments; ++i) { + ransac.buildAndAssessModel(segments[i]); + } + // Continue with random samples. + const int ransac_iterations = segments.empty() ? 0 : 200; + for (int i = 0; i < ransac_iterations; ++i) { + ransac.buildAndAssessModel(segments[qrand() % segments.size()]); + } + + if (ransac.bestModel().segments.empty()) { + return QLineF(m_leadingTop, m_leadingTop + QPointF(0, 1)); + } + + const QLineF line(interpolateSegments(ransac.bestModel().segments)); + + if (dbg_segments) { + // Has to be the last thing we do with best model. + dbg_segments->swap(ransac.bestModel().segments); + } + + return line; } // SequentialColumnProcessor::approximateWithLine QLineF SequentialColumnProcessor::interpolateSegments(const std::vector& segments) const { - assert(!segments.empty()); - - // First, interpolate the angle of segments. - Vec2d accum_vec; - double accum_weight = 0; - - for (const Segment& seg : segments) { - const double weight = std::sqrt(double(seg.vertDist)); - accum_vec += weight * seg.unitVec; - accum_weight += weight; - } - - assert(accum_weight != 0); - accum_vec /= accum_weight; - - QLineF line(m_path.front(), m_path.front() + accum_vec); - Vec2d normal(-accum_vec[1], accum_vec[0]); - if ((m_leftOrRight == RIGHT) != (normal[0] < 0)) { - normal = -normal; - } - // normal now points *inside* the image, towards the other bound. - // Now find the vertex in m_path through which our line should pass. - for (const QPoint& pt : m_path) { - if (normal.dot(pt - line.p1()) < 0) { - line.setP1(pt); - line.setP2(line.p1() + accum_vec); - } - } - - return line; + assert(!segments.empty()); + + // First, interpolate the angle of segments. + Vec2d accum_vec; + double accum_weight = 0; + + for (const Segment& seg : segments) { + const double weight = std::sqrt(double(seg.vertDist)); + accum_vec += weight * seg.unitVec; + accum_weight += weight; + } + + assert(accum_weight != 0); + accum_vec /= accum_weight; + + QLineF line(m_path.front(), m_path.front() + accum_vec); + Vec2d normal(-accum_vec[1], accum_vec[0]); + if ((m_leftOrRight == RIGHT) != (normal[0] < 0)) { + normal = -normal; + } + // normal now points *inside* the image, towards the other bound. + // Now find the vertex in m_path through which our line should pass. + for (const QPoint& pt : m_path) { + if (normal.dot(pt - line.p1()) < 0) { + line.setP1(pt); + line.setP2(line.p1() + accum_vec); + } + } + + return line; } QImage SequentialColumnProcessor::visualizeEnvelope(const QImage& background) { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - - QPen pen(QColor(0xff, 0, 0, 180)); - pen.setWidthF(3.0); - painter.setPen(pen); - - if (!m_path.empty()) { - const std::vector polyline(m_path.begin(), m_path.end()); - painter.drawPolyline(&polyline[0], static_cast(polyline.size())); - } - - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(Qt::blue)); - painter.setOpacity(0.7); - QRectF rect(0, 0, 9, 9); - - for (QPoint pt : m_path) { - rect.moveCenter(pt + QPointF(0.5, 0.5)); - painter.drawEllipse(rect); - } - - return canvas; + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + + QPen pen(QColor(0xff, 0, 0, 180)); + pen.setWidthF(3.0); + painter.setPen(pen); + + if (!m_path.empty()) { + const std::vector polyline(m_path.begin(), m_path.end()); + painter.drawPolyline(&polyline[0], static_cast(polyline.size())); + } + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(Qt::blue)); + painter.setOpacity(0.7); + QRectF rect(0, 0, 9, 9); + + for (QPoint pt : m_path) { + rect.moveCenter(pt + QPointF(0.5, 0.5)); + painter.drawEllipse(rect); + } + + return canvas; } QImage visualizeSegments(const QImage& background, const std::vector& segments) { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); - QPen pen(Qt::red); - pen.setWidthF(3.0); - painter.setPen(pen); - painter.setOpacity(0.7); + QPen pen(Qt::red); + pen.setWidthF(3.0); + painter.setPen(pen); + painter.setOpacity(0.7); - for (const Segment& seg : segments) { - painter.drawLine(seg.line); - } + for (const Segment& seg : segments) { + painter.drawLine(seg.line); + } - return canvas; + return canvas; } // For every column in the image, store the top-most and bottom-most black pixel. void calculateVertRanges(const imageproc::BinaryImage& image, std::vector& ranges) { - const int width = image.width(); - const int height = image.height(); - const uint32_t* image_data = image.data(); - const int image_stride = image.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - ranges.reserve(width); - - for (int x = 0; x < width; ++x) { - ranges.emplace_back(); - VertRange& range = ranges.back(); - - const uint32_t mask = msb >> (x & 31); - const uint32_t* p_word = image_data + (x >> 5); - - int top_y = 0; - for (; top_y < height; ++top_y, p_word += image_stride) { - if (*p_word & mask) { - range.top = top_y; - break; - } - } - - int bottom_y = height - 1; - p_word = image_data + bottom_y * image_stride + (x >> 5); - for (; bottom_y >= top_y; --bottom_y, p_word -= image_stride) { - if (*p_word & mask) { - range.bottom = bottom_y; - break; - } - } - } + const int width = image.width(); + const int height = image.height(); + const uint32_t* image_data = image.data(); + const int image_stride = image.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + ranges.reserve(width); + + for (int x = 0; x < width; ++x) { + ranges.emplace_back(); + VertRange& range = ranges.back(); + + const uint32_t mask = msb >> (x & 31); + const uint32_t* p_word = image_data + (x >> 5); + + int top_y = 0; + for (; top_y < height; ++top_y, p_word += image_stride) { + if (*p_word & mask) { + range.top = top_y; + break; + } + } + + int bottom_y = height - 1; + p_word = image_data + bottom_y * image_stride + (x >> 5); + for (; bottom_y >= top_y; --bottom_y, p_word -= image_stride) { + if (*p_word & mask) { + range.bottom = bottom_y; + break; + } + } + } } // calculateVertRanges QLineF extendLine(const QLineF& line, int height) { - QPointF top_intersection; - QPointF bottom_intersection; + QPointF top_intersection; + QPointF bottom_intersection; - const QLineF top_line(QPointF(0, 0), QPointF(1, 0)); - const QLineF bottom_line(QPointF(0, height), QPointF(1, height)); + const QLineF top_line(QPointF(0, 0), QPointF(1, 0)); + const QLineF bottom_line(QPointF(0, height), QPointF(1, height)); - line.intersect(top_line, &top_intersection); - line.intersect(bottom_line, &bottom_intersection); + line.intersect(top_line, &top_intersection); + line.intersect(bottom_line, &bottom_intersection); - return QLineF(top_intersection, bottom_intersection); + return QLineF(top_intersection, bottom_intersection); } } // namespace std::pair detectVertContentBounds(const imageproc::BinaryImage& image, DebugImages* dbg) { - const int width = image.width(); - const int height = image.height(); - - std::vector cols; - calculateVertRanges(image, cols); - - SequentialColumnProcessor left_processor(image.size(), SequentialColumnProcessor::LEFT); - for (int x = 0; x < width; ++x) { - left_processor.process(x, cols[x]); - } - - SequentialColumnProcessor right_processor(image.size(), SequentialColumnProcessor::RIGHT); - for (int x = width - 1; x >= 0; --x) { - right_processor.process(x, cols[x]); - } - - if (dbg) { - const QImage background(image.toQImage().convertToFormat(QImage::Format_RGB32)); - dbg->add(left_processor.visualizeEnvelope(background), "left_envelope"); - dbg->add(right_processor.visualizeEnvelope(background), "right_envelope"); - } - - std::pair bounds; - - std::vector segments; - std::vector* dbg_segments = dbg ? &segments : nullptr; - - QLineF left_line(left_processor.approximateWithLine(dbg_segments)); - left_line.translate(-1, 0); - bounds.first = extendLine(left_line, height); - if (dbg) { - dbg->add(visualizeSegments(image.toQImage(), *dbg_segments), "left_ransac_model"); - } - - QLineF right_line(right_processor.approximateWithLine(dbg_segments)); - right_line.translate(1, 0); - bounds.second = extendLine(right_line, height); - if (dbg) { - dbg->add(visualizeSegments(image.toQImage(), *dbg_segments), "right_ransac_model"); - } - - return bounds; + const int width = image.width(); + const int height = image.height(); + + std::vector cols; + calculateVertRanges(image, cols); + + SequentialColumnProcessor left_processor(image.size(), SequentialColumnProcessor::LEFT); + for (int x = 0; x < width; ++x) { + left_processor.process(x, cols[x]); + } + + SequentialColumnProcessor right_processor(image.size(), SequentialColumnProcessor::RIGHT); + for (int x = width - 1; x >= 0; --x) { + right_processor.process(x, cols[x]); + } + + if (dbg) { + const QImage background(image.toQImage().convertToFormat(QImage::Format_RGB32)); + dbg->add(left_processor.visualizeEnvelope(background), "left_envelope"); + dbg->add(right_processor.visualizeEnvelope(background), "right_envelope"); + } + + std::pair bounds; + + std::vector segments; + std::vector* dbg_segments = dbg ? &segments : nullptr; + + QLineF left_line(left_processor.approximateWithLine(dbg_segments)); + left_line.translate(-1, 0); + bounds.first = extendLine(left_line, height); + if (dbg) { + dbg->add(visualizeSegments(image.toQImage(), *dbg_segments), "left_ransac_model"); + } + + QLineF right_line(right_processor.approximateWithLine(dbg_segments)); + right_line.translate(1, 0); + bounds.second = extendLine(right_line, height); + if (dbg) { + dbg->add(visualizeSegments(image.toQImage(), *dbg_segments), "right_ransac_model"); + } + + return bounds; } // detectVertContentBounds } // namespace dewarping \ No newline at end of file diff --git a/dewarping/DewarpingPointMapper.cpp b/dewarping/DewarpingPointMapper.cpp index fb01766f0..6c3196a13 100644 --- a/dewarping/DewarpingPointMapper.cpp +++ b/dewarping/DewarpingPointMapper.cpp @@ -17,8 +17,8 @@ */ #include "DewarpingPointMapper.h" -#include "DistortionModel.h" #include +#include "DistortionModel.h" namespace dewarping { DewarpingPointMapper::DewarpingPointMapper(const DistortionModel& distortion_model, @@ -26,41 +26,41 @@ DewarpingPointMapper::DewarpingPointMapper(const DistortionModel& distortion_mod const QTransform& distortion_model_to_output, const QRect& output_content_rect, const QTransform& postTransform) - : m_dewarper(CylindricalSurfaceDewarper(distortion_model.topCurve().polyline(), - distortion_model.bottomCurve().polyline(), - depth_perception)), - m_postTransform(postTransform) { - // Model domain is a rectangle in output image coordinates that - // will be mapped to our curved quadrilateral. - const QRect model_domain( - distortion_model.modelDomain(m_dewarper, distortion_model_to_output, output_content_rect).toRect()); + : m_dewarper(CylindricalSurfaceDewarper(distortion_model.topCurve().polyline(), + distortion_model.bottomCurve().polyline(), + depth_perception)), + m_postTransform(postTransform) { + // Model domain is a rectangle in output image coordinates that + // will be mapped to our curved quadrilateral. + const QRect model_domain( + distortion_model.modelDomain(m_dewarper, distortion_model_to_output, output_content_rect).toRect()); - // Note: QRect::right() - QRect::left() will give you size() - 1 not size()! - // That's intended. + // Note: QRect::right() - QRect::left() will give you size() - 1 not size()! + // That's intended. - m_modelDomainLeft = model_domain.left(); - m_modelXScaleFromNormalized = model_domain.right() - model_domain.left(); - m_modelXScaleToNormalized = 1.0 / m_modelXScaleFromNormalized; + m_modelDomainLeft = model_domain.left(); + m_modelXScaleFromNormalized = model_domain.right() - model_domain.left(); + m_modelXScaleToNormalized = 1.0 / m_modelXScaleFromNormalized; - m_modelDomainTop = model_domain.top(); - m_modelYScaleFromNormalized = model_domain.bottom() - model_domain.top(); - m_modelYScaleToNormalized = 1.0 / m_modelYScaleFromNormalized; + m_modelDomainTop = model_domain.top(); + m_modelYScaleFromNormalized = model_domain.bottom() - model_domain.top(); + m_modelYScaleToNormalized = 1.0 / m_modelYScaleFromNormalized; } QPointF DewarpingPointMapper::mapToDewarpedSpace(const QPointF& warped_pt) const { - const QPointF crv_pt(m_dewarper.mapToDewarpedSpace(warped_pt)); - const double dewarped_x = crv_pt.x() * m_modelXScaleFromNormalized + m_modelDomainLeft; - const double dewarped_y = crv_pt.y() * m_modelYScaleFromNormalized + m_modelDomainTop; + const QPointF crv_pt(m_dewarper.mapToDewarpedSpace(warped_pt)); + const double dewarped_x = crv_pt.x() * m_modelXScaleFromNormalized + m_modelDomainLeft; + const double dewarped_y = crv_pt.y() * m_modelYScaleFromNormalized + m_modelDomainTop; - return m_postTransform.map(QPointF(dewarped_x, dewarped_y)); + return m_postTransform.map(QPointF(dewarped_x, dewarped_y)); } QPointF DewarpingPointMapper::mapToWarpedSpace(const QPointF& dewarped_pt) const { - QPointF dewarped_pt_m = m_postTransform.inverted().map(dewarped_pt); + QPointF dewarped_pt_m = m_postTransform.inverted().map(dewarped_pt); - const double crv_x = (dewarped_pt_m.x() - m_modelDomainLeft) * m_modelXScaleToNormalized; - const double crv_y = (dewarped_pt_m.y() - m_modelDomainTop) * m_modelYScaleToNormalized; + const double crv_x = (dewarped_pt_m.x() - m_modelDomainLeft) * m_modelXScaleToNormalized; + const double crv_y = (dewarped_pt_m.y() - m_modelDomainTop) * m_modelYScaleToNormalized; - return m_dewarper.mapToWarpedSpace(QPointF(crv_x, crv_y)); + return m_dewarper.mapToWarpedSpace(QPointF(crv_x, crv_y)); } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/DewarpingPointMapper.h b/dewarping/DewarpingPointMapper.h index d2feacbcd..6db4ccfc5 100644 --- a/dewarping/DewarpingPointMapper.h +++ b/dewarping/DewarpingPointMapper.h @@ -28,36 +28,36 @@ namespace dewarping { class DistortionModel; class DewarpingPointMapper { -public: - DewarpingPointMapper(const dewarping::DistortionModel& distortion_model, - double depth_perception, - const QTransform& distortion_model_to_output, - const QRect& output_content_rect, - const QTransform& postTransform = QTransform()); - - /** - * Similar to CylindricalSurfaceDewarper::mapToDewarpedSpace(), - * except it maps to dewarped image coordinates rather than - * to normalized dewarped coordinates. - */ - QPointF mapToDewarpedSpace(const QPointF& warped_pt) const; - - /** - * Similar to CylindricalSurfaceDewarper::mapToWarpedSpace(), - * except it maps from dewarped image coordinates rather than - * from normalized dewarped coordinates. - */ - QPointF mapToWarpedSpace(const QPointF& dewarped_pt) const; - -private: - CylindricalSurfaceDewarper m_dewarper; - double m_modelDomainLeft; - double m_modelDomainTop; - double m_modelXScaleFromNormalized; - double m_modelYScaleFromNormalized; - double m_modelXScaleToNormalized; - double m_modelYScaleToNormalized; - QTransform m_postTransform; + public: + DewarpingPointMapper(const dewarping::DistortionModel& distortion_model, + double depth_perception, + const QTransform& distortion_model_to_output, + const QRect& output_content_rect, + const QTransform& postTransform = QTransform()); + + /** + * Similar to CylindricalSurfaceDewarper::mapToDewarpedSpace(), + * except it maps to dewarped image coordinates rather than + * to normalized dewarped coordinates. + */ + QPointF mapToDewarpedSpace(const QPointF& warped_pt) const; + + /** + * Similar to CylindricalSurfaceDewarper::mapToWarpedSpace(), + * except it maps from dewarped image coordinates rather than + * from normalized dewarped coordinates. + */ + QPointF mapToWarpedSpace(const QPointF& dewarped_pt) const; + + private: + CylindricalSurfaceDewarper m_dewarper; + double m_modelDomainLeft; + double m_modelDomainTop; + double m_modelXScaleFromNormalized; + double m_modelYScaleFromNormalized; + double m_modelXScaleToNormalized; + double m_modelYScaleToNormalized; + QTransform m_postTransform; }; } // namespace dewarping #endif // ifndef DEWARPING_DEWARPING_POINT_MAPPER_H_ diff --git a/dewarping/DistortionModel.cpp b/dewarping/DistortionModel.cpp index 395a2fcf6..e6d2bf766 100644 --- a/dewarping/DistortionModel.cpp +++ b/dewarping/DistortionModel.cpp @@ -17,136 +17,135 @@ */ #include "DistortionModel.h" -#include "CylindricalSurfaceDewarper.h" +#include #include #include -#include +#include "CylindricalSurfaceDewarper.h" namespace dewarping { DistortionModel::DistortionModel() = default; DistortionModel::DistortionModel(const QDomElement& el) - : m_topCurve(el.namedItem("top-curve").toElement()), m_bottomCurve(el.namedItem("bottom-curve").toElement()) { -} + : m_topCurve(el.namedItem("top-curve").toElement()), m_bottomCurve(el.namedItem("bottom-curve").toElement()) {} QDomElement DistortionModel::toXml(QDomDocument& doc, const QString& name) const { - if (!isValid()) { - return QDomElement(); - } + if (!isValid()) { + return QDomElement(); + } - QDomElement el(doc.createElement(name)); - el.appendChild(m_topCurve.toXml(doc, "top-curve")); - el.appendChild(m_bottomCurve.toXml(doc, "bottom-curve")); + QDomElement el(doc.createElement(name)); + el.appendChild(m_topCurve.toXml(doc, "top-curve")); + el.appendChild(m_bottomCurve.toXml(doc, "bottom-curve")); - return el; + return el; } bool DistortionModel::isValid() const { - if (!m_topCurve.isValid() || !m_bottomCurve.isValid()) { - return false; - } + if (!m_topCurve.isValid() || !m_bottomCurve.isValid()) { + return false; + } - const Vec2d poly[4] = {m_topCurve.polyline().front(), m_topCurve.polyline().back(), m_bottomCurve.polyline().back(), - m_bottomCurve.polyline().front()}; + const Vec2d poly[4] = {m_topCurve.polyline().front(), m_topCurve.polyline().back(), m_bottomCurve.polyline().back(), + m_bottomCurve.polyline().front()}; - double min_dot = NumericTraits::max(); - double max_dot = NumericTraits::min(); + double min_dot = NumericTraits::max(); + double max_dot = NumericTraits::min(); - for (int i = 0; i < 4; ++i) { - const Vec2d cur(poly[i]); - const Vec2d prev(poly[(i + 3) & 3]); - const Vec2d next(poly[(i + 1) & 3]); + for (int i = 0; i < 4; ++i) { + const Vec2d cur(poly[i]); + const Vec2d prev(poly[(i + 3) & 3]); + const Vec2d next(poly[(i + 1) & 3]); - Vec2d prev_normal(cur - prev); - std::swap(prev_normal[0], prev_normal[1]); - prev_normal[0] = -prev_normal[0]; + Vec2d prev_normal(cur - prev); + std::swap(prev_normal[0], prev_normal[1]); + prev_normal[0] = -prev_normal[0]; - const double dot = prev_normal.dot(next - cur); - if (dot < min_dot) { - min_dot = dot; - } - if (dot > max_dot) { - max_dot = dot; - } + const double dot = prev_normal.dot(next - cur); + if (dot < min_dot) { + min_dot = dot; } - - if (min_dot * max_dot <= 0) { - // Not convex. - return false; + if (dot > max_dot) { + max_dot = dot; } + } - if ((std::fabs(min_dot) < 0.01) || (std::fabs(max_dot) < 0.01)) { - // Too close - possible problems with calculating homography. - return false; - } + if (min_dot * max_dot <= 0) { + // Not convex. + return false; + } - return true; + if ((std::fabs(min_dot) < 0.01) || (std::fabs(max_dot) < 0.01)) { + // Too close - possible problems with calculating homography. + return false; + } + + return true; } // DistortionModel::isValid bool DistortionModel::matches(const DistortionModel& other) const { - const bool this_valid = isValid(); - const bool other_valid = other.isValid(); - if (!this_valid && !other_valid) { - return true; - } else if (this_valid != other_valid) { - return false; - } + const bool this_valid = isValid(); + const bool other_valid = other.isValid(); + if (!this_valid && !other_valid) { + return true; + } else if (this_valid != other_valid) { + return false; + } - if (!m_topCurve.matches(other.m_topCurve)) { - return false; - } else if (!m_bottomCurve.matches(other.m_bottomCurve)) { - return false; - } + if (!m_topCurve.matches(other.m_topCurve)) { + return false; + } else if (!m_bottomCurve.matches(other.m_bottomCurve)) { + return false; + } - return true; + return true; } QRectF DistortionModel::modelDomain(const CylindricalSurfaceDewarper& dewarper, const QTransform& to_output, const QRectF& output_content_rect) const { - QRectF model_domain(boundingBox(to_output)); + QRectF model_domain(boundingBox(to_output)); - // We not only uncurl the lines, but also stretch them in curved areas. - // Because we don't want to reach out of the content box, we shrink - // the model domain vertically, rather than stretching it horizontally. - const double vert_scale = 1.0 / dewarper.directrixArcLength(); - // When scaling model_domain, we want the following point to remain where it is. - const QPointF scale_origin(output_content_rect.center()); + // We not only uncurl the lines, but also stretch them in curved areas. + // Because we don't want to reach out of the content box, we shrink + // the model domain vertically, rather than stretching it horizontally. + const double vert_scale = 1.0 / dewarper.directrixArcLength(); + // When scaling model_domain, we want the following point to remain where it is. + const QPointF scale_origin(output_content_rect.center()); - const double new_upper_part = (scale_origin.y() - model_domain.top()) * vert_scale; - const double new_height = model_domain.height() * vert_scale; - model_domain.setTop(scale_origin.y() - new_upper_part); - model_domain.setHeight(new_height); + const double new_upper_part = (scale_origin.y() - model_domain.top()) * vert_scale; + const double new_height = model_domain.height() * vert_scale; + model_domain.setTop(scale_origin.y() - new_upper_part); + model_domain.setHeight(new_height); - return model_domain; + return model_domain; } QRectF DistortionModel::boundingBox(const QTransform& transform) const { - double top = NumericTraits::max(); - double left = top; - double bottom = NumericTraits::min(); - double right = bottom; - - for (QPointF pt : m_topCurve.polyline()) { - pt = transform.map(pt); - left = std::min(left, pt.x()); - right = std::max(right, pt.x()); - top = std::min(top, pt.y()); - bottom = std::max(bottom, pt.y()); - } - - for (QPointF pt : m_bottomCurve.polyline()) { - pt = transform.map(pt); - left = std::min(left, pt.x()); - right = std::max(right, pt.x()); - top = std::min(top, pt.y()); - bottom = std::max(bottom, pt.y()); - } - - if ((top > bottom) || (left > right)) { - return QRectF(); - } else { - return QRectF(left, top, right - left, bottom - top); - } + double top = NumericTraits::max(); + double left = top; + double bottom = NumericTraits::min(); + double right = bottom; + + for (QPointF pt : m_topCurve.polyline()) { + pt = transform.map(pt); + left = std::min(left, pt.x()); + right = std::max(right, pt.x()); + top = std::min(top, pt.y()); + bottom = std::max(bottom, pt.y()); + } + + for (QPointF pt : m_bottomCurve.polyline()) { + pt = transform.map(pt); + left = std::min(left, pt.x()); + right = std::max(right, pt.x()); + top = std::min(top, pt.y()); + bottom = std::max(bottom, pt.y()); + } + + if ((top > bottom) || (left > right)) { + return QRectF(); + } else { + return QRectF(left, top, right - left, bottom - top); + } } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/DistortionModel.h b/dewarping/DistortionModel.h index ddf0e7fb2..f246fbd88 100644 --- a/dewarping/DistortionModel.h +++ b/dewarping/DistortionModel.h @@ -31,60 +31,52 @@ namespace dewarping { class CylindricalSurfaceDewarper; class DistortionModel { -public: - /** - * \brief Constructs a null distortion model. - */ - DistortionModel(); - - explicit DistortionModel(const QDomElement& el); - - QDomElement toXml(QDomDocument& doc, const QString& name) const; - - /** - * Returns true if the model is not null and in addition meets certain - * criteria, like curve endpoints forming a convex quadrilateral. - */ - bool isValid() const; - - void setTopCurve(const Curve& curve) { - m_topCurve = curve; - } - - void setBottomCurve(const Curve& curve) { - m_bottomCurve = curve; - } - - const Curve& topCurve() const { - return m_topCurve; - } - - const Curve& bottomCurve() const { - return m_bottomCurve; - } - - bool matches(const DistortionModel& other) const; - - /** - * Model domain is a rectangle in output image coordinates that - * will be mapped to our curved quadrilateral. - */ - QRectF modelDomain(const CylindricalSurfaceDewarper& dewarper, - const QTransform& to_output, - const QRectF& output_content_rect) const; - -private: - /** - * \return The bounding box of the shape formed by two curves - * and vertical segments connecting them. - * \param transform Transforms from the original image coordinates - * where curve points are defined, to the desired coordinate - * system, for example to output image coordinates. - */ - QRectF boundingBox(const QTransform& transform) const; - - Curve m_topCurve; - Curve m_bottomCurve; + public: + /** + * \brief Constructs a null distortion model. + */ + DistortionModel(); + + explicit DistortionModel(const QDomElement& el); + + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + /** + * Returns true if the model is not null and in addition meets certain + * criteria, like curve endpoints forming a convex quadrilateral. + */ + bool isValid() const; + + void setTopCurve(const Curve& curve) { m_topCurve = curve; } + + void setBottomCurve(const Curve& curve) { m_bottomCurve = curve; } + + const Curve& topCurve() const { return m_topCurve; } + + const Curve& bottomCurve() const { return m_bottomCurve; } + + bool matches(const DistortionModel& other) const; + + /** + * Model domain is a rectangle in output image coordinates that + * will be mapped to our curved quadrilateral. + */ + QRectF modelDomain(const CylindricalSurfaceDewarper& dewarper, + const QTransform& to_output, + const QRectF& output_content_rect) const; + + private: + /** + * \return The bounding box of the shape formed by two curves + * and vertical segments connecting them. + * \param transform Transforms from the original image coordinates + * where curve points are defined, to the desired coordinate + * system, for example to output image coordinates. + */ + QRectF boundingBox(const QTransform& transform) const; + + Curve m_topCurve; + Curve m_bottomCurve; }; } // namespace dewarping #endif // ifndef DEWARPING_DISTORTION_MODEL_H_ diff --git a/dewarping/DistortionModelBuilder.cpp b/dewarping/DistortionModelBuilder.cpp index 32fcbe2e5..76bd6701d 100644 --- a/dewarping/DistortionModelBuilder.cpp +++ b/dewarping/DistortionModelBuilder.cpp @@ -17,239 +17,226 @@ */ #include "DistortionModelBuilder.h" -#include "DistortionModel.h" +#include +#include +#include +#include #include "CylindricalSurfaceDewarper.h" +#include "DebugImages.h" +#include "DistortionModel.h" #include "LineBoundedByRect.h" -#include "ToLineProjector.h" #include "SidesOfLine.h" -#include "DebugImages.h" +#include "ToLineProjector.h" +#include "spfit/ConstraintSet.h" #include "spfit/FrenetFrame.h" -#include "spfit/SqDistApproximant.h" +#include "spfit/LinearForceBalancer.h" #include "spfit/PolylineModelShape.h" #include "spfit/SplineFitter.h" -#include "spfit/LinearForceBalancer.h" -#include "spfit/ConstraintSet.h" -#include -#include -#include -#include +#include "spfit/SqDistApproximant.h" using namespace imageproc; namespace dewarping { struct DistortionModelBuilder::TracedCurve { - std::vector trimmedPolyline; // Both are left to right. - std::vector extendedPolyline; // - XSpline extendedSpline; - double order; // Lesser values correspond to upper curves. - - TracedCurve(const std::vector& trimmed_polyline, const XSpline& extended_spline, double ord) - : trimmedPolyline(trimmed_polyline), - extendedPolyline(extended_spline.toPolyline()), - extendedSpline(extended_spline), - order(ord) { - } - - bool operator<(const TracedCurve& rhs) const { - return order < rhs.order; - } + std::vector trimmedPolyline; // Both are left to right. + std::vector extendedPolyline; // + XSpline extendedSpline; + double order; // Lesser values correspond to upper curves. + + TracedCurve(const std::vector& trimmed_polyline, const XSpline& extended_spline, double ord) + : trimmedPolyline(trimmed_polyline), + extendedPolyline(extended_spline.toPolyline()), + extendedSpline(extended_spline), + order(ord) {} + + bool operator<(const TracedCurve& rhs) const { return order < rhs.order; } }; struct DistortionModelBuilder::RansacModel { - const TracedCurve* topCurve; - const TracedCurve* bottomCurve; - double totalError; + const TracedCurve* topCurve; + const TracedCurve* bottomCurve; + double totalError; - RansacModel() : topCurve(nullptr), bottomCurve(nullptr), totalError(NumericTraits::max()) { - } + RansacModel() : topCurve(nullptr), bottomCurve(nullptr), totalError(NumericTraits::max()) {} - bool isValid() const { - return topCurve && bottomCurve; - } + bool isValid() const { return topCurve && bottomCurve; } }; class DistortionModelBuilder::RansacAlgo { -public: - explicit RansacAlgo(const std::vector& all_curves) : m_rAllCurves(all_curves) { - } + public: + explicit RansacAlgo(const std::vector& all_curves) : m_allCurves(all_curves) {} - void buildAndAssessModel(const TracedCurve* top_curve, const TracedCurve* bottom_curve); + void buildAndAssessModel(const TracedCurve* top_curve, const TracedCurve* bottom_curve); - RansacModel& bestModel() { - return m_bestModel; - } + RansacModel& bestModel() { return m_bestModel; } - const RansacModel& bestModel() const { - return m_bestModel; - } + const RansacModel& bestModel() const { return m_bestModel; } -private: - double calcReferenceHeight(const CylindricalSurfaceDewarper& dewarper, const QPointF& loc); + private: + double calcReferenceHeight(const CylindricalSurfaceDewarper& dewarper, const QPointF& loc); - RansacModel m_bestModel; - const std::vector& m_rAllCurves; + RansacModel m_bestModel; + const std::vector& m_allCurves; }; class DistortionModelBuilder::BadCurve : public std::exception { -public: - const char* what() const throw() override { - return "Bad curve"; - } + public: + const char* what() const noexcept override { return "Bad curve"; } }; DistortionModelBuilder::DistortionModelBuilder(const Vec2d& down_direction) - : m_downDirection(down_direction), m_rightDirection(down_direction[1], -down_direction[0]) { - assert(down_direction.squaredNorm() > 0); + : m_downDirection(down_direction), m_rightDirection(down_direction[1], -down_direction[0]) { + assert(down_direction.squaredNorm() > 0); } void DistortionModelBuilder::setVerticalBounds(const QLineF& bound1, const QLineF& bound2) { - m_bound1 = bound1; - m_bound2 = bound2; + m_bound1 = bound1; + m_bound2 = bound2; } std::pair DistortionModelBuilder::verticalBounds() const { - return std::pair(m_bound1, m_bound2); + return std::pair(m_bound1, m_bound2); } void DistortionModelBuilder::addHorizontalCurve(const std::vector& polyline) { - if (polyline.size() < 2) { - return; - } - - if (Vec2d(polyline.back() - polyline.front()).dot(m_rightDirection) > 0) { - m_ltrPolylines.push_back(polyline); - } else { - m_ltrPolylines.emplace_back(polyline.rbegin(), polyline.rend()); - } + if (polyline.size() < 2) { + return; + } + + if (Vec2d(polyline.back() - polyline.front()).dot(m_rightDirection) > 0) { + m_ltrPolylines.push_back(polyline); + } else { + m_ltrPolylines.emplace_back(polyline.rbegin(), polyline.rend()); + } } void DistortionModelBuilder::transform(const QTransform& xform) { - assert(xform.isAffine()); + assert(xform.isAffine()); - const QLineF down_line(xform.map(QLineF(QPointF(0, 0), m_downDirection))); - const QLineF right_line(xform.map(QLineF(QPointF(0, 0), m_rightDirection))); + const QLineF down_line(xform.map(QLineF(QPointF(0, 0), m_downDirection))); + const QLineF right_line(xform.map(QLineF(QPointF(0, 0), m_rightDirection))); - m_downDirection = down_line.p2() - down_line.p1(); - m_rightDirection = right_line.p2() - right_line.p1(); - m_bound1 = xform.map(m_bound1); - m_bound2 = xform.map(m_bound2); + m_downDirection = down_line.p2() - down_line.p1(); + m_rightDirection = right_line.p2() - right_line.p1(); + m_bound1 = xform.map(m_bound1); + m_bound2 = xform.map(m_bound2); - for (std::vector& polyline : m_ltrPolylines) { - for (QPointF& pt : polyline) { - pt = xform.map(pt); - } + for (std::vector& polyline : m_ltrPolylines) { + for (QPointF& pt : polyline) { + pt = xform.map(pt); } + } } DistortionModel DistortionModelBuilder::tryBuildModel(DebugImages* dbg, const QImage* dbg_background) const { - auto num_curves = static_cast(m_ltrPolylines.size()); - - if ((num_curves < 2) || (m_bound1.p1() == m_bound1.p2()) || (m_bound2.p1() == m_bound2.p2())) { - return DistortionModel(); - } - - std::vector ordered_curves; - ordered_curves.reserve(num_curves); - - for (const std::vector& polyline : m_ltrPolylines) { - try { - ordered_curves.push_back(polylineToCurve(polyline)); - } catch (const BadCurve&) { - // Just skip it. - } - } - num_curves = static_cast(ordered_curves.size()); - if (num_curves == 0) { - return DistortionModel(); - } - // if (num_curves < 2) { - // return DistortionModel(); - // } - std::sort(ordered_curves.begin(), ordered_curves.end()); - - // Select the best pair using RANSAC. - RansacAlgo ransac(ordered_curves); - - // First let's try to combine each of the 3 top-most lines - // with each of the 3 bottom-most ones. - for (int i = 0; i < std::min(3, num_curves); ++i) { - for (int j = std::max(0, num_curves - 3); j < num_curves; ++j) { - if (i < j) { - ransac.buildAndAssessModel(&ordered_curves[i], &ordered_curves[j]); - } - } - } - // Continue by throwing in some random pairs of lines. - qsrand(0); // Repeatablity is important. - int random_pairs_remaining = 10; - while (random_pairs_remaining-- > 0) { - int i = qrand() % num_curves; - int j = qrand() % num_curves; - if (i > j) { - std::swap(i, j); - } - if (i < j) { - ransac.buildAndAssessModel(&ordered_curves[i], &ordered_curves[j]); - } - } - - if (dbg && dbg_background) { - dbg->add(visualizeTrimmedPolylines(*dbg_background, ordered_curves), "trimmed_polylines"); - dbg->add(visualizeModel(*dbg_background, ordered_curves, ransac.bestModel()), "distortion_model"); - } - - DistortionModel model; - if (ransac.bestModel().isValid()) { - model.setTopCurve(Curve(ransac.bestModel().topCurve->extendedPolyline)); - model.setBottomCurve(Curve(ransac.bestModel().bottomCurve->extendedPolyline)); - } - - return model; + auto num_curves = static_cast(m_ltrPolylines.size()); + + if ((num_curves < 2) || (m_bound1.p1() == m_bound1.p2()) || (m_bound2.p1() == m_bound2.p2())) { + return DistortionModel(); + } + + std::vector ordered_curves; + ordered_curves.reserve(num_curves); + + for (const std::vector& polyline : m_ltrPolylines) { + try { + ordered_curves.push_back(polylineToCurve(polyline)); + } catch (const BadCurve&) { + // Just skip it. + } + } + num_curves = static_cast(ordered_curves.size()); + if (num_curves == 0) { + return DistortionModel(); + } + // if (num_curves < 2) { + // return DistortionModel(); + // } + std::sort(ordered_curves.begin(), ordered_curves.end()); + + // Select the best pair using RANSAC. + RansacAlgo ransac(ordered_curves); + + // First let's try to combine each of the 3 top-most lines + // with each of the 3 bottom-most ones. + for (int i = 0; i < std::min(3, num_curves); ++i) { + for (int j = std::max(0, num_curves - 3); j < num_curves; ++j) { + if (i < j) { + ransac.buildAndAssessModel(&ordered_curves[i], &ordered_curves[j]); + } + } + } + // Continue by throwing in some random pairs of lines. + qsrand(0); // Repeatablity is important. + int random_pairs_remaining = 10; + while (random_pairs_remaining-- > 0) { + int i = qrand() % num_curves; + int j = qrand() % num_curves; + if (i > j) { + std::swap(i, j); + } + if (i < j) { + ransac.buildAndAssessModel(&ordered_curves[i], &ordered_curves[j]); + } + } + + if (dbg && dbg_background) { + dbg->add(visualizeTrimmedPolylines(*dbg_background, ordered_curves), "trimmed_polylines"); + dbg->add(visualizeModel(*dbg_background, ordered_curves, ransac.bestModel()), "distortion_model"); + } + + DistortionModel model; + if (ransac.bestModel().isValid()) { + model.setTopCurve(Curve(ransac.bestModel().topCurve->extendedPolyline)); + model.setBottomCurve(Curve(ransac.bestModel().bottomCurve->extendedPolyline)); + } + + return model; } // DistortionModelBuilder::tryBuildModel DistortionModelBuilder::TracedCurve DistortionModelBuilder::polylineToCurve( - const std::vector& polyline) const { - const std::pair bounds(frontBackBounds(polyline)); + const std::vector& polyline) const { + const std::pair bounds(frontBackBounds(polyline)); - // Trim the polyline if necessary. - const std::vector trimmed_polyline(maybeTrimPolyline(polyline, bounds)); + // Trim the polyline if necessary. + const std::vector trimmed_polyline(maybeTrimPolyline(polyline, bounds)); - const Vec2d centroid(this->centroid(polyline)); + const Vec2d centroid(this->centroid(polyline)); - // Fit the polyline to a spline, extending it to bounds at the same time. - const XSpline extended_spline(fitExtendedSpline(trimmed_polyline, centroid, bounds)); + // Fit the polyline to a spline, extending it to bounds at the same time. + const XSpline extended_spline(fitExtendedSpline(trimmed_polyline, centroid, bounds)); - const double order = centroid.dot(m_downDirection); + const double order = centroid.dot(m_downDirection); - return TracedCurve(trimmed_polyline, extended_spline, order); + return TracedCurve(trimmed_polyline, extended_spline, order); } Vec2d DistortionModelBuilder::centroid(const std::vector& polyline) { - const auto num_points = static_cast(polyline.size()); - if (num_points == 0) { - return Vec2d(); - } else if (num_points == 1) { - return Vec2d(polyline.front()); - } - - Vec2d accum(0, 0); - double total_weight = 0; - - for (int i = 1; i < num_points; ++i) { - const QLineF segment(polyline[i - 1], polyline[i]); - const Vec2d center(0.5 * (segment.p1() + segment.p2())); - const double weight = segment.length(); - accum += center * weight; - total_weight += weight; - } - - if (total_weight < 1e-06) { - return Vec2d(polyline.front()); - } else { - return accum / total_weight; - } + const auto num_points = static_cast(polyline.size()); + if (num_points == 0) { + return Vec2d(); + } else if (num_points == 1) { + return Vec2d(polyline.front()); + } + + Vec2d accum(0, 0); + double total_weight = 0; + + for (int i = 1; i < num_points; ++i) { + const QLineF segment(polyline[i - 1], polyline[i]); + const Vec2d center(0.5 * (segment.p1() + segment.p2())); + const double weight = segment.length(); + accum += center * weight; + total_weight += weight; + } + + if (total_weight < 1e-06) { + return Vec2d(polyline.front()); + } else { + return accum / total_weight; + } } /** @@ -259,254 +246,253 @@ Vec2d DistortionModelBuilder::centroid(const std::vector& polyline) { * respectively. */ std::pair DistortionModelBuilder::frontBackBounds(const std::vector& polyline) const { - assert(!polyline.empty()); + assert(!polyline.empty()); - const ToLineProjector proj1(m_bound1); - const ToLineProjector proj2(m_bound2); - if (proj1.projectionDist(polyline.front()) + proj2.projectionDist(polyline.back()) - < proj1.projectionDist(polyline.back()) + proj2.projectionDist(polyline.front())) { - return std::pair(m_bound1, m_bound2); - } else { - return std::pair(m_bound2, m_bound1); - } + const ToLineProjector proj1(m_bound1); + const ToLineProjector proj2(m_bound2); + if (proj1.projectionDist(polyline.front()) + proj2.projectionDist(polyline.back()) + < proj1.projectionDist(polyline.back()) + proj2.projectionDist(polyline.front())) { + return std::pair(m_bound1, m_bound2); + } else { + return std::pair(m_bound2, m_bound1); + } } std::vector DistortionModelBuilder::maybeTrimPolyline(const std::vector& polyline, const std::pair& bounds) { - std::deque trimmed_polyline(polyline.begin(), polyline.end()); - maybeTrimFront(trimmed_polyline, bounds.first); - maybeTrimBack(trimmed_polyline, bounds.second); + std::deque trimmed_polyline(polyline.begin(), polyline.end()); + maybeTrimFront(trimmed_polyline, bounds.first); + maybeTrimBack(trimmed_polyline, bounds.second); - return std::vector(trimmed_polyline.begin(), trimmed_polyline.end()); + return std::vector(trimmed_polyline.begin(), trimmed_polyline.end()); } bool DistortionModelBuilder::maybeTrimFront(std::deque& polyline, const QLineF& bound) { - if (sidesOfLine(bound, polyline.front(), polyline.back()) >= 0) { - // Doesn't need trimming. - return false; - } + if (sidesOfLine(bound, polyline.front(), polyline.back()) >= 0) { + // Doesn't need trimming. + return false; + } - while (polyline.size() > 2 && sidesOfLine(bound, polyline.front(), polyline[1]) > 0) { - polyline.pop_front(); - } + while (polyline.size() > 2 && sidesOfLine(bound, polyline.front(), polyline[1]) > 0) { + polyline.pop_front(); + } - intersectFront(polyline, bound); + intersectFront(polyline, bound); - return true; + return true; } bool DistortionModelBuilder::maybeTrimBack(std::deque& polyline, const QLineF& bound) { - if (sidesOfLine(bound, polyline.front(), polyline.back()) >= 0) { - // Doesn't need trimming. - return false; - } + if (sidesOfLine(bound, polyline.front(), polyline.back()) >= 0) { + // Doesn't need trimming. + return false; + } - while (polyline.size() > 2 && sidesOfLine(bound, polyline[polyline.size() - 2], polyline.back()) > 0) { - polyline.pop_back(); - } + while (polyline.size() > 2 && sidesOfLine(bound, polyline[polyline.size() - 2], polyline.back()) > 0) { + polyline.pop_back(); + } - intersectBack(polyline, bound); + intersectBack(polyline, bound); - return true; + return true; } void DistortionModelBuilder::intersectFront(std::deque& polyline, const QLineF& bound) { - assert(polyline.size() >= 2); + assert(polyline.size() >= 2); - const QLineF front_segment(polyline.front(), polyline[1]); - QPointF intersection; - if (bound.intersect(front_segment, &intersection) != QLineF::NoIntersection) { - polyline.front() = intersection; - } + const QLineF front_segment(polyline.front(), polyline[1]); + QPointF intersection; + if (bound.intersect(front_segment, &intersection) != QLineF::NoIntersection) { + polyline.front() = intersection; + } } void DistortionModelBuilder::intersectBack(std::deque& polyline, const QLineF& bound) { - assert(polyline.size() >= 2); + assert(polyline.size() >= 2); - const QLineF back_segment(polyline[polyline.size() - 2], polyline.back()); - QPointF intersection; - if (bound.intersect(back_segment, &intersection) != QLineF::NoIntersection) { - polyline.back() = intersection; - } + const QLineF back_segment(polyline[polyline.size() - 2], polyline.back()); + QPointF intersection; + if (bound.intersect(back_segment, &intersection) != QLineF::NoIntersection) { + polyline.back() = intersection; + } } XSpline DistortionModelBuilder::fitExtendedSpline(const std::vector& polyline, const Vec2d& centroid, const std::pair& bounds) { - using namespace spfit; - - const QLineF chord(polyline.front(), polyline.back()); - XSpline spline; - const int initial_spline_points = 5; - spline.appendControlPoint(chord.pointAt(0), 1); - for (int i = 1; i < initial_spline_points - 1; ++i) { - const double fraction = i / (initial_spline_points - 1.0); - spline.appendControlPoint(chord.pointAt(fraction), 1); - } - spline.appendControlPoint(chord.pointAt(1), 1); - - // initialSplinePositioning(spline, 0, chord.p1(), 1, chord.p2()); - - class ModelShape : public PolylineModelShape { - public: - explicit ModelShape(const std::vector& polyline) : PolylineModelShape(polyline) { - } - - protected: - SqDistApproximant calcApproximant(const QPointF& pt, - FittableSpline::SampleFlags sample_flags, - Flags polyline_flags, - const FrenetFrame& frenet_frame, - double signed_curvature) const override { - if (polyline_flags & (POLYLINE_FRONT | POLYLINE_BACK)) { - if (sample_flags & FittableSpline::JUNCTION_SAMPLE) { - return SqDistApproximant::pointDistance(frenet_frame.origin()); - } else { - return SqDistApproximant(); - } - } else { - return SqDistApproximant::curveDistance(pt, frenet_frame, signed_curvature); - } + using namespace spfit; + + const QLineF chord(polyline.front(), polyline.back()); + XSpline spline; + const int initial_spline_points = 5; + spline.appendControlPoint(chord.pointAt(0), 1); + for (int i = 1; i < initial_spline_points - 1; ++i) { + const double fraction = i / (initial_spline_points - 1.0); + spline.appendControlPoint(chord.pointAt(fraction), 1); + } + spline.appendControlPoint(chord.pointAt(1), 1); + + // initialSplinePositioning(spline, 0, chord.p1(), 1, chord.p2()); + + class ModelShape : public PolylineModelShape { + public: + explicit ModelShape(const std::vector& polyline) : PolylineModelShape(polyline) {} + + protected: + SqDistApproximant calcApproximant(const QPointF& pt, + FittableSpline::SampleFlags sample_flags, + Flags polyline_flags, + const FrenetFrame& frenet_frame, + double signed_curvature) const override { + if (polyline_flags & (POLYLINE_FRONT | POLYLINE_BACK)) { + if (sample_flags & FittableSpline::JUNCTION_SAMPLE) { + return SqDistApproximant::pointDistance(frenet_frame.origin()); + } else { + return SqDistApproximant(); } - }; - - - const ModelShape model_shape(polyline); - SplineFitter fitter(&spline); + } else { + return SqDistApproximant::curveDistance(pt, frenet_frame, signed_curvature); + } + } + }; - FittableSpline::SamplingParams sampling_params; - sampling_params.maxDistBetweenSamples = 10; - fitter.setSamplingParams(sampling_params); - int iterations_remaining = 20; - LinearForceBalancer balancer(0.8); - balancer.setTargetRatio(0.1); - balancer.setIterationsToTarget(iterations_remaining - 1); + const ModelShape model_shape(polyline); + SplineFitter fitter(&spline); - // Initial fitting: just uniform distribution of junction points on a spline. - { - ConstraintSet constraints(&spline); - constraints.constrainSplinePoint(0, bounds.first); - constraints.constrainSplinePoint(1, bounds.second); - for (int i = 0; i < initial_spline_points; ++i) { - constraints.constrainSplinePoint(spline.controlPointIndexToT(i), chord); - } - fitter.setConstraints(constraints); - fitter.addInternalForce(spline.junctionPointsAttractionForce()); + FittableSpline::SamplingParams sampling_params; + sampling_params.maxDistBetweenSamples = 10; + fitter.setSamplingParams(sampling_params); - // We don't have any external forces, so we can choose any non-zero - // weight for internal force. - fitter.optimize(1); - assert(!Curve::splineHasLoops(spline)); - } + int iterations_remaining = 20; + LinearForceBalancer balancer(0.8); + balancer.setTargetRatio(0.1); + balancer.setIterationsToTarget(iterations_remaining - 1); + // Initial fitting: just uniform distribution of junction points on a spline. + { ConstraintSet constraints(&spline); constraints.constrainSplinePoint(0, bounds.first); constraints.constrainSplinePoint(1, bounds.second); + for (int i = 0; i < initial_spline_points; ++i) { + constraints.constrainSplinePoint(spline.controlPointIndexToT(i), chord); + } fitter.setConstraints(constraints); + fitter.addInternalForce(spline.junctionPointsAttractionForce()); - for (int iteration = 0; iterations_remaining > 0; ++iteration, --iterations_remaining, balancer.nextIteration()) { - fitter.addAttractionForces(model_shape); - fitter.addInternalForce(spline.controlPointsAttractionForce()); - - double internal_force_weight = balancer.calcInternalForceWeight(fitter.internalForce(), fitter.externalForce()); - const OptimizationResult res(fitter.optimize(internal_force_weight)); - if (Curve::splineHasLoops(spline)) { - if (iteration == 0) { - // Having a loop on the first iteration is not good at all. - throw BadCurve(); - } else { - fitter.undoLastStep(); - break; - } - } + // We don't have any external forces, so we can choose any non-zero + // weight for internal force. + fitter.optimize(1); + assert(!Curve::splineHasLoops(spline)); + } - if (res.improvementPercentage() < 0.5) { - break; - } + ConstraintSet constraints(&spline); + constraints.constrainSplinePoint(0, bounds.first); + constraints.constrainSplinePoint(1, bounds.second); + fitter.setConstraints(constraints); + + for (int iteration = 0; iterations_remaining > 0; ++iteration, --iterations_remaining, balancer.nextIteration()) { + fitter.addAttractionForces(model_shape); + fitter.addInternalForce(spline.controlPointsAttractionForce()); + + double internal_force_weight = balancer.calcInternalForceWeight(fitter.internalForce(), fitter.externalForce()); + const OptimizationResult res(fitter.optimize(internal_force_weight)); + if (Curve::splineHasLoops(spline)) { + if (iteration == 0) { + // Having a loop on the first iteration is not good at all. + throw BadCurve(); + } else { + fitter.undoLastStep(); + break; + } + } + + if (res.improvementPercentage() < 0.5) { + break; } + } - return spline; + return spline; } // DistortionModelBuilder::fitExtendedSpline /*============================== RansacAlgo ============================*/ void DistortionModelBuilder::RansacAlgo::buildAndAssessModel(const TracedCurve* top_curve, const TracedCurve* bottom_curve) try { - DistortionModel model; - model.setTopCurve(Curve(top_curve->extendedPolyline)); - model.setBottomCurve(Curve(bottom_curve->extendedPolyline)); - if (!model.isValid()) { - return; - } - - const double depth_perception = 2.0; // Doesn't matter much here. - const CylindricalSurfaceDewarper dewarper(top_curve->extendedPolyline, bottom_curve->extendedPolyline, - depth_perception); - - double error = 0; - for (const TracedCurve& curve : m_rAllCurves) { - const size_t polyline_size = curve.trimmedPolyline.size(); - const double r_reference_height = 1.0 / 1.0; // calcReferenceHeight(dewarper, curve.centroid); - - // We are going to approximate the dewarped polyline by a straight line - // using linear least-squares: At*A*x = At*B -> x = (At*A)-1 * At*B - std::vector At; - At.reserve(polyline_size * 2); - std::vector B; - B.reserve(polyline_size); - - for (const QPointF& warped_pt : curve.trimmedPolyline) { - // TODO: add another signature with hint for efficiency. - const QPointF dewarped_pt(dewarper.mapToDewarpedSpace(warped_pt)); - - // ax + b = y <-> x * a + 1 * b = y - At.push_back(dewarped_pt.x()); - At.push_back(1); - B.push_back(dewarped_pt.y()); - } - - DynamicMatrixCalc mc; - - // A = Att - boost::scoped_array A(new double[polyline_size * 2]); - mc(&At[0], 2, (int) polyline_size).transWrite(&A[0]); - - try { - boost::scoped_array errvec(new double[polyline_size]); - double ab[2]; // As in "y = ax + b". - - // errvec = B - A * (At*A)-1 * At * B - // ab = (At*A)-1 * At * B - (mc(&B[0], (int) polyline_size, 1) - - mc(&A[0], (int) polyline_size, 2) - * ((mc(&At[0], 2, (int) polyline_size) * mc(&A[0], (int) polyline_size, 2)).inv() - * (mc(&At[0], 2, (int) polyline_size) * mc(&B[0], (int) polyline_size, 1))) - .write(ab)) - .write(&errvec[0]); - - double sum_abs_err = 0; - for (size_t i = 0; i < polyline_size; ++i) { - sum_abs_err += std::fabs(errvec[i]) * r_reference_height; - } - // Penalty for not being straight. - error += sum_abs_err / polyline_size; - - // TODO: penalty for not being horizontal. - } catch (const std::runtime_error&) { - // Strictly vertical line? - error += 1000; - } - } - - if (error < m_bestModel.totalError) { - m_bestModel.topCurve = top_curve; - m_bestModel.bottomCurve = bottom_curve; - m_bestModel.totalError = error; - } + DistortionModel model; + model.setTopCurve(Curve(top_curve->extendedPolyline)); + model.setBottomCurve(Curve(bottom_curve->extendedPolyline)); + if (!model.isValid()) { + return; + } + + const double depth_perception = 2.0; // Doesn't matter much here. + const CylindricalSurfaceDewarper dewarper(top_curve->extendedPolyline, bottom_curve->extendedPolyline, + depth_perception); + + double error = 0; + for (const TracedCurve& curve : m_allCurves) { + const size_t polyline_size = curve.trimmedPolyline.size(); + const double r_reference_height = 1.0 / 1.0; // calcReferenceHeight(dewarper, curve.centroid); + + // We are going to approximate the dewarped polyline by a straight line + // using linear least-squares: At*A*x = At*B -> x = (At*A)-1 * At*B + std::vector At; + At.reserve(polyline_size * 2); + std::vector B; + B.reserve(polyline_size); + + for (const QPointF& warped_pt : curve.trimmedPolyline) { + // TODO: add another signature with hint for efficiency. + const QPointF dewarped_pt(dewarper.mapToDewarpedSpace(warped_pt)); + + // ax + b = y <-> x * a + 1 * b = y + At.push_back(dewarped_pt.x()); + At.push_back(1); + B.push_back(dewarped_pt.y()); + } + + DynamicMatrixCalc mc; + + // A = Att + boost::scoped_array A(new double[polyline_size * 2]); + mc(&At[0], 2, (int) polyline_size).transWrite(&A[0]); + + try { + boost::scoped_array errvec(new double[polyline_size]); + double ab[2]; // As in "y = ax + b". + + // errvec = B - A * (At*A)-1 * At * B + // ab = (At*A)-1 * At * B + (mc(&B[0], (int) polyline_size, 1) + - mc(&A[0], (int) polyline_size, 2) + * ((mc(&At[0], 2, (int) polyline_size) * mc(&A[0], (int) polyline_size, 2)).inv() + * (mc(&At[0], 2, (int) polyline_size) * mc(&B[0], (int) polyline_size, 1))) + .write(ab)) + .write(&errvec[0]); + + double sum_abs_err = 0; + for (size_t i = 0; i < polyline_size; ++i) { + sum_abs_err += std::fabs(errvec[i]) * r_reference_height; + } + // Penalty for not being straight. + error += sum_abs_err / polyline_size; + + // TODO: penalty for not being horizontal. + } catch (const std::runtime_error&) { + // Strictly vertical line? + error += 1000; + } + } + + if (error < m_bestModel.totalError) { + m_bestModel.topCurve = top_curve; + m_bestModel.bottomCurve = bottom_curve; + m_bestModel.totalError = error; + } } // DistortionModelBuilder::RansacAlgo::buildAndAssessModel catch (const std::runtime_error&) { - // Probably CylindricalSurfaceDewarper didn't like something. + // Probably CylindricalSurfaceDewarper didn't like something. } #if 0 @@ -524,153 +510,153 @@ catch (const std::runtime_error&) { QImage DistortionModelBuilder::visualizeTrimmedPolylines(const QImage& background, const std::vector& curves) const { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - - const int width = background.width(); - const int height = background.height(); - const double stroke_width = std::sqrt(double(width * width + height * height)) / 500; - - // Extend / trim bounds. - QLineF bound1(m_bound1); - QLineF bound2(m_bound2); - lineBoundedByRect(bound1, background.rect()); - lineBoundedByRect(bound2, background.rect()); - - // Draw bounds. - QPen pen(QColor(0, 0, 255, 180)); - pen.setWidthF(stroke_width); - painter.setPen(pen); - painter.drawLine(bound1); - painter.drawLine(bound2); - - for (const TracedCurve& curve : curves) { - if (!curve.trimmedPolyline.empty()) { - painter.drawPolyline(&curve.trimmedPolyline[0], static_cast(curve.trimmedPolyline.size())); - } - } - - // Draw polyline knots. - QBrush knot_brush(Qt::magenta); - painter.setBrush(knot_brush); - painter.setPen(Qt::NoPen); - for (const TracedCurve& curve : curves) { - QRectF rect(0, 0, stroke_width, stroke_width); - for (const QPointF& knot : curve.trimmedPolyline) { - rect.moveCenter(knot); - painter.drawEllipse(rect); - } - } - - return canvas; + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + + const int width = background.width(); + const int height = background.height(); + const double stroke_width = std::sqrt(double(width * width + height * height)) / 500; + + // Extend / trim bounds. + QLineF bound1(m_bound1); + QLineF bound2(m_bound2); + lineBoundedByRect(bound1, background.rect()); + lineBoundedByRect(bound2, background.rect()); + + // Draw bounds. + QPen pen(QColor(0, 0, 255, 180)); + pen.setWidthF(stroke_width); + painter.setPen(pen); + painter.drawLine(bound1); + painter.drawLine(bound2); + + for (const TracedCurve& curve : curves) { + if (!curve.trimmedPolyline.empty()) { + painter.drawPolyline(&curve.trimmedPolyline[0], static_cast(curve.trimmedPolyline.size())); + } + } + + // Draw polyline knots. + QBrush knot_brush(Qt::magenta); + painter.setBrush(knot_brush); + painter.setPen(Qt::NoPen); + for (const TracedCurve& curve : curves) { + QRectF rect(0, 0, stroke_width, stroke_width); + for (const QPointF& knot : curve.trimmedPolyline) { + rect.moveCenter(knot); + painter.drawEllipse(rect); + } + } + + return canvas; } // DistortionModelBuilder::visualizeTrimmedPolylines QImage DistortionModelBuilder::visualizeModel(const QImage& background, const std::vector& curves, const RansacModel& model) const { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - - const int width = background.width(); - const int height = background.height(); - const double stroke_width = std::sqrt(double(width * width + height * height)) / 500; - - // Extend / trim bounds. - QLineF bound1(m_bound1); - QLineF bound2(m_bound2); - lineBoundedByRect(bound1, background.rect()); - lineBoundedByRect(bound2, background.rect()); - // Draw bounds. - QPen bounds_pen(QColor(0, 0, 255, 180)); - bounds_pen.setWidthF(stroke_width); - painter.setPen(bounds_pen); - painter.drawLine(bound1); - painter.drawLine(bound2); - - QPen active_curve_pen(QColor(0x45, 0xff, 0x53, 180)); - active_curve_pen.setWidthF(stroke_width); - - QPen inactive_curve_pen(QColor(0, 0, 255, 140)); - inactive_curve_pen.setWidthF(stroke_width); - - QPen reverse_segments_pen(QColor(0xff, 0x28, 0x05, 140)); - reverse_segments_pen.setWidthF(stroke_width); - - QBrush control_point_brush(QColor(0xff, 0x00, 0x00, 255)); - - QBrush junction_point_brush(QColor(0xff, 0x00, 0xff, 255)); - - for (const TracedCurve& curve : curves) { - if (curve.extendedPolyline.empty()) { - continue; - } - if ((&curve == model.topCurve) || (&curve == model.bottomCurve)) { - painter.setPen(active_curve_pen); - } else { - painter.setPen(inactive_curve_pen); - } + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); - const size_t size = curve.extendedPolyline.size(); - painter.drawPolyline(&curve.extendedPolyline[0], static_cast(curve.extendedPolyline.size())); - - const Vec2d main_direction(curve.extendedPolyline.back() - curve.extendedPolyline.front()); - std::list> reverse_segments; - - for (size_t i = 1; i < size; ++i) { - const Vec2d dir(curve.extendedPolyline[i] - curve.extendedPolyline[i - 1]); - if (dir.dot(main_direction) >= 0) { - continue; - } - - // We've got a reverse segment. - if (!reverse_segments.empty() && (reverse_segments.back().back() == int(i) - 1)) { - // Continue the previous sequence. - reverse_segments.back().push_back(static_cast(i)); - } else { - // Start a new sequence. - reverse_segments.emplace_back(); - std::vector& sequence = reverse_segments.back(); - sequence.push_back(static_cast(i - 1)); - sequence.push_back(static_cast(i)); - } - } + const int width = background.width(); + const int height = background.height(); + const double stroke_width = std::sqrt(double(width * width + height * height)) / 500; - QVector polyline; - - if (!reverse_segments.empty()) { - painter.setPen(reverse_segments_pen); - for (const std::vector& sequence : reverse_segments) { - assert(!sequence.empty()); - polyline.clear(); - for (int idx : sequence) { - polyline << curve.extendedPolyline[idx]; - } - painter.drawPolyline(polyline); - } - } + // Extend / trim bounds. + QLineF bound1(m_bound1); + QLineF bound2(m_bound2); + lineBoundedByRect(bound1, background.rect()); + lineBoundedByRect(bound2, background.rect()); + // Draw bounds. + QPen bounds_pen(QColor(0, 0, 255, 180)); + bounds_pen.setWidthF(stroke_width); + painter.setPen(bounds_pen); + painter.drawLine(bound1); + painter.drawLine(bound2); - const int num_control_points = curve.extendedSpline.numControlPoints(); - QRectF rect(0, 0, stroke_width, stroke_width); + QPen active_curve_pen(QColor(0x45, 0xff, 0x53, 180)); + active_curve_pen.setWidthF(stroke_width); - // Draw junction points. - painter.setPen(Qt::NoPen); - painter.setBrush(junction_point_brush); - for (int i = 0; i < num_control_points; ++i) { - const double t = curve.extendedSpline.controlPointIndexToT(i); - rect.moveCenter(curve.extendedSpline.pointAt(t)); - painter.drawEllipse(rect); - } - // Draw control points. - painter.setPen(Qt::NoPen); - painter.setBrush(control_point_brush); - for (int i = 0; i < num_control_points; ++i) { - rect.moveCenter(curve.extendedSpline.controlPointPosition(i)); - painter.drawEllipse(rect); + QPen inactive_curve_pen(QColor(0, 0, 255, 140)); + inactive_curve_pen.setWidthF(stroke_width); + + QPen reverse_segments_pen(QColor(0xff, 0x28, 0x05, 140)); + reverse_segments_pen.setWidthF(stroke_width); + + QBrush control_point_brush(QColor(0xff, 0x00, 0x00, 255)); + + QBrush junction_point_brush(QColor(0xff, 0x00, 0xff, 255)); + + for (const TracedCurve& curve : curves) { + if (curve.extendedPolyline.empty()) { + continue; + } + if ((&curve == model.topCurve) || (&curve == model.bottomCurve)) { + painter.setPen(active_curve_pen); + } else { + painter.setPen(inactive_curve_pen); + } + + const size_t size = curve.extendedPolyline.size(); + painter.drawPolyline(&curve.extendedPolyline[0], static_cast(curve.extendedPolyline.size())); + + const Vec2d main_direction(curve.extendedPolyline.back() - curve.extendedPolyline.front()); + std::list> reverse_segments; + + for (size_t i = 1; i < size; ++i) { + const Vec2d dir(curve.extendedPolyline[i] - curve.extendedPolyline[i - 1]); + if (dir.dot(main_direction) >= 0) { + continue; + } + + // We've got a reverse segment. + if (!reverse_segments.empty() && (reverse_segments.back().back() == int(i) - 1)) { + // Continue the previous sequence. + reverse_segments.back().push_back(static_cast(i)); + } else { + // Start a new sequence. + reverse_segments.emplace_back(); + std::vector& sequence = reverse_segments.back(); + sequence.push_back(static_cast(i - 1)); + sequence.push_back(static_cast(i)); + } + } + + QVector polyline; + + if (!reverse_segments.empty()) { + painter.setPen(reverse_segments_pen); + for (const std::vector& sequence : reverse_segments) { + assert(!sequence.empty()); + polyline.clear(); + for (int idx : sequence) { + polyline << curve.extendedPolyline[idx]; } + painter.drawPolyline(polyline); + } + } + + const int num_control_points = curve.extendedSpline.numControlPoints(); + QRectF rect(0, 0, stroke_width, stroke_width); + + // Draw junction points. + painter.setPen(Qt::NoPen); + painter.setBrush(junction_point_brush); + for (int i = 0; i < num_control_points; ++i) { + const double t = curve.extendedSpline.controlPointIndexToT(i); + rect.moveCenter(curve.extendedSpline.pointAt(t)); + painter.drawEllipse(rect); + } + // Draw control points. + painter.setPen(Qt::NoPen); + painter.setBrush(control_point_brush); + for (int i = 0; i < num_control_points; ++i) { + rect.moveCenter(curve.extendedSpline.controlPointPosition(i)); + painter.drawEllipse(rect); } + } - return canvas; + return canvas; } // DistortionModelBuilder::visualizeModel } // namespace dewarping \ No newline at end of file diff --git a/dewarping/DistortionModelBuilder.h b/dewarping/DistortionModelBuilder.h index f0a00c790..b8c2f15e0 100644 --- a/dewarping/DistortionModelBuilder.h +++ b/dewarping/DistortionModelBuilder.h @@ -19,13 +19,13 @@ #ifndef DEWARPING_DISTORTION_MODEL_BUILDER_H_ #define DEWARPING_DISTORTION_MODEL_BUILDER_H_ -#include "VecNT.h" -#include -#include #include -#include +#include +#include #include #include +#include +#include "VecNT.h" class QImage; class DebugImages; @@ -35,96 +35,96 @@ namespace dewarping { class DistortionModel; class DistortionModelBuilder { - // Member-wise copying is OK. -public: - /** - * \brief Constructor. - * - * \param down_direction A vector pointing approximately downwards in terms of content. - * The vector can't be zero-length. - */ - explicit DistortionModelBuilder(const Vec2d& down_direction); - - /** - * \brief Set the vertical content boundaries. - * - * Note that we are setting lines, not line segments, so endpoint - * positions along the line don't really matter. It also doesn't - * matter which one is the left bound and which one is the right one. - */ - void setVerticalBounds(const QLineF& bound1, const QLineF& bound2); - - /** - * \brief Returns the current vertical bounds. - * - * It's not specified which one is the left and which one is the right bound. - */ - std::pair verticalBounds() const; - - /** - * \brief Add a curve that's meant to become straight and horizontal after dewarping. - * - * The curve doesn't have to touch or intersect the vertical bounds, although - * long curves are preferable. The minimum number of curves to build a distortion - * model is 2, although that doesn't guarantee successful model construction. - * The more apart the curves are, the better. - */ - void addHorizontalCurve(const std::vector& polyline); - - /** - * \brief Applies an affine transformation to the internal representation. - */ - void transform(const QTransform& xform); - - /** - * \brief Tries to build a distortion model based on information provided so far. - * - * \return A DistortionModel that may be invalid. - * \see DistortionModel::isValid() - */ - DistortionModel tryBuildModel(DebugImages* dbg = nullptr, const QImage* dbg_background = nullptr) const; - -private: - struct TracedCurve; - struct RansacModel; - - class RansacAlgo; - class BadCurve; - - TracedCurve polylineToCurve(const std::vector& polyline) const; - - static Vec2d centroid(const std::vector& polyline); - - std::pair frontBackBounds(const std::vector& polyline) const; - - static std::vector maybeTrimPolyline(const std::vector& polyline, - const std::pair& bounds); - - static bool maybeTrimFront(std::deque& polyline, const QLineF& bound); - - static bool maybeTrimBack(std::deque& polyline, const QLineF& bound); - - static void intersectFront(std::deque& polyline, const QLineF& bound); - - static void intersectBack(std::deque& polyline, const QLineF& bound); - - static XSpline fitExtendedSpline(const std::vector& polyline, - const Vec2d& centroid, - const std::pair& bounds); - - QImage visualizeTrimmedPolylines(const QImage& background, const std::vector& curves) const; - - QImage visualizeModel(const QImage& background, - const std::vector& curves, - const RansacModel& model) const; - - Vec2d m_downDirection; - Vec2d m_rightDirection; - QLineF m_bound1; // It's not specified which one is left - QLineF m_bound2; // and which one is right. - - /** These go left to right in terms of content. */ - std::deque> m_ltrPolylines; + // Member-wise copying is OK. + public: + /** + * \brief Constructor. + * + * \param down_direction A vector pointing approximately downwards in terms of content. + * The vector can't be zero-length. + */ + explicit DistortionModelBuilder(const Vec2d& down_direction); + + /** + * \brief Set the vertical content boundaries. + * + * Note that we are setting lines, not line segments, so endpoint + * positions along the line don't really matter. It also doesn't + * matter which one is the left bound and which one is the right one. + */ + void setVerticalBounds(const QLineF& bound1, const QLineF& bound2); + + /** + * \brief Returns the current vertical bounds. + * + * It's not specified which one is the left and which one is the right bound. + */ + std::pair verticalBounds() const; + + /** + * \brief Add a curve that's meant to become straight and horizontal after dewarping. + * + * The curve doesn't have to touch or intersect the vertical bounds, although + * long curves are preferable. The minimum number of curves to build a distortion + * model is 2, although that doesn't guarantee successful model construction. + * The more apart the curves are, the better. + */ + void addHorizontalCurve(const std::vector& polyline); + + /** + * \brief Applies an affine transformation to the internal representation. + */ + void transform(const QTransform& xform); + + /** + * \brief Tries to build a distortion model based on information provided so far. + * + * \return A DistortionModel that may be invalid. + * \see DistortionModel::isValid() + */ + DistortionModel tryBuildModel(DebugImages* dbg = nullptr, const QImage* dbg_background = nullptr) const; + + private: + struct TracedCurve; + struct RansacModel; + + class RansacAlgo; + class BadCurve; + + TracedCurve polylineToCurve(const std::vector& polyline) const; + + static Vec2d centroid(const std::vector& polyline); + + std::pair frontBackBounds(const std::vector& polyline) const; + + static std::vector maybeTrimPolyline(const std::vector& polyline, + const std::pair& bounds); + + static bool maybeTrimFront(std::deque& polyline, const QLineF& bound); + + static bool maybeTrimBack(std::deque& polyline, const QLineF& bound); + + static void intersectFront(std::deque& polyline, const QLineF& bound); + + static void intersectBack(std::deque& polyline, const QLineF& bound); + + static XSpline fitExtendedSpline(const std::vector& polyline, + const Vec2d& centroid, + const std::pair& bounds); + + QImage visualizeTrimmedPolylines(const QImage& background, const std::vector& curves) const; + + QImage visualizeModel(const QImage& background, + const std::vector& curves, + const RansacModel& model) const; + + Vec2d m_downDirection; + Vec2d m_rightDirection; + QLineF m_bound1; // It's not specified which one is left + QLineF m_bound2; // and which one is right. + + /** These go left to right in terms of content. */ + std::deque> m_ltrPolylines; }; } // namespace dewarping #endif // ifndef DEWARPING_DISTORTION_MODEL_BUILDER_H_ diff --git a/dewarping/RasterDewarper.cpp b/dewarping/RasterDewarper.cpp index 23e2b10fc..dd2b604c2 100644 --- a/dewarping/RasterDewarper.cpp +++ b/dewarping/RasterDewarper.cpp @@ -17,11 +17,11 @@ */ #include "RasterDewarper.h" +#include +#include #include "CylindricalSurfaceDewarper.h" #include "imageproc/ColorMixer.h" #include "imageproc/GrayImage.h" -#include -#include #define INTERP_NONE 0 #define INTERP_BILLINEAR 1 @@ -33,7 +33,7 @@ using namespace imageproc; namespace dewarping { namespace { #if INTERPOLATION_METHOD == INTERP_NONE -template +template void dewarpGeneric(const PixelType* const src_data, const QSize src_size, const int src_stride, @@ -43,43 +43,43 @@ void dewarpGeneric(const PixelType* const src_data, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const PixelType bg_color) { - const int src_width = src_size.width(); - const int src_height = src_size.height(); - const int dst_width = dst_size.width(); - const int dst_height = dst_size.height(); - - CylindricalSurfaceDewarper::State state; - - const double model_domain_left = model_domain.left(); - const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); - - const float model_domain_top = model_domain.top(); - const float model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); - - for (int dst_x = 0; dst_x < dst_width; ++dst_x) { - const double model_x = (dst_x - model_domain_left) * model_x_scale; - const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); - - const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); - const Vec2f origin(generatrix.imgLine.p1()); - const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); - for (int dst_y = 0; dst_y < dst_height; ++dst_y) { - const float model_y = (float(dst_y) - model_domain_top) * model_y_scale; - const Vec2f src_pt(origin + vec * homog(model_y)); - const int src_x = qRound(src_pt[0]); - const int src_y = qRound(src_pt[1]); - if ((src_x < 0) || (src_x >= src_width) || (src_y < 0) || (src_y >= src_height)) { - dst_data[dst_y * dst_stride + dst_x] = bg_color; - continue; - } - - dst_data[dst_y * dst_stride + dst_x] = src_data[src_y * src_stride + src_x]; - } + const int src_width = src_size.width(); + const int src_height = src_size.height(); + const int dst_width = dst_size.width(); + const int dst_height = dst_size.height(); + + CylindricalSurfaceDewarper::State state; + + const double model_domain_left = model_domain.left(); + const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); + + const float model_domain_top = model_domain.top(); + const float model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); + + for (int dst_x = 0; dst_x < dst_width; ++dst_x) { + const double model_x = (dst_x - model_domain_left) * model_x_scale; + const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); + + const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); + const Vec2f origin(generatrix.imgLine.p1()); + const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); + for (int dst_y = 0; dst_y < dst_height; ++dst_y) { + const float model_y = (float(dst_y) - model_domain_top) * model_y_scale; + const Vec2f src_pt(origin + vec * homog(model_y)); + const int src_x = qRound(src_pt[0]); + const int src_y = qRound(src_pt[1]); + if ((src_x < 0) || (src_x >= src_width) || (src_y < 0) || (src_y >= src_height)) { + dst_data[dst_y * dst_stride + dst_x] = bg_color; + continue; + } + + dst_data[dst_y * dst_stride + dst_x] = src_data[src_y * src_stride + src_x]; } + } } // dewarpGeneric #elif INTERPOLATION_METHOD == INTERP_BILLINEAR -template +template void dewarpGeneric(const PixelType* const src_data, const QSize src_size, const int src_stride, @@ -89,70 +89,70 @@ void dewarpGeneric(const PixelType* const src_data, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const PixelType bg_color) { - const int src_width = src_size.width(); - const int src_height = src_size.height(); - const int dst_width = dst_size.width(); - const int dst_height = dst_size.height(); - - CylindricalSurfaceDewarper::State state; - - const double model_domain_left = model_domain.left() - 0.5f; - const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); - - const float model_domain_top = model_domain.top() - 0.5f; - const float model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); - - for (int dst_x = 0; dst_x < dst_width; ++dst_x) { - const double model_x = (dst_x - model_domain_left) * model_x_scale; - const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); - - const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); - const Vec2f origin(generatrix.imgLine.p1()); - const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); - for (int dst_y = 0; dst_y < dst_height; ++dst_y) { - const float model_y = ((float) dst_y - model_domain_top) * model_y_scale; - const Vec2f src_pt(origin + vec * homog(model_y)); - - const int src_x0 = (int) std::floor(src_pt[0] - 0.5f); - const int src_y0 = (int) std::floor(src_pt[1] - 0.5f); - const int src_x1 = src_x0 + 1; - const int src_y1 = src_y0 + 1; - const float x = src_pt[0] - src_x0; - const float y = src_pt[1] - src_y0; - - PixelType tl_color = bg_color; - if ((src_x0 >= 0) && (src_x0 < src_width) && (src_y0 >= 0) && (src_y0 < src_height)) { - tl_color = src_data[src_y0 * src_stride + src_x0]; - } - - PixelType tr_color = bg_color; - if ((src_x1 >= 0) && (src_x1 < src_width) && (src_y0 >= 0) && (src_y0 < src_height)) { - tr_color = src_data[src_y0 * src_stride + src_x1]; - } - - PixelType bl_color = bg_color; - if ((src_x0 >= 0) && (src_x0 < src_width) && (src_y1 >= 0) && (src_y1 < src_height)) { - bl_color = src_data[src_y1 * src_stride + src_x0]; - } - - PixelType br_color = bg_color; - if ((src_x1 >= 0) && (src_x1 < src_width) && (src_y1 >= 0) && (src_y1 < src_height)) { - br_color = src_data[src_y1 * src_stride + src_x1]; - } - - ColorMixer mixer; - mixer.add(tl_color, (1.5f - y) * (1.5f - x)); - mixer.add(tr_color, (1.5f - y) * (x - 0.5f)); - mixer.add(bl_color, (y - 0.5f) * (1.5f - x)); - mixer.add(br_color, (y - 0.5f) * (x - 0.5f)); - dst_data[dst_y * dst_stride + dst_x] = mixer.mix(1.0f); - } + const int src_width = src_size.width(); + const int src_height = src_size.height(); + const int dst_width = dst_size.width(); + const int dst_height = dst_size.height(); + + CylindricalSurfaceDewarper::State state; + + const double model_domain_left = model_domain.left() - 0.5f; + const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); + + const float model_domain_top = model_domain.top() - 0.5f; + const float model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); + + for (int dst_x = 0; dst_x < dst_width; ++dst_x) { + const double model_x = (dst_x - model_domain_left) * model_x_scale; + const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); + + const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); + const Vec2f origin(generatrix.imgLine.p1()); + const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); + for (int dst_y = 0; dst_y < dst_height; ++dst_y) { + const float model_y = ((float) dst_y - model_domain_top) * model_y_scale; + const Vec2f src_pt(origin + vec * homog(model_y)); + + const int src_x0 = (int) std::floor(src_pt[0] - 0.5f); + const int src_y0 = (int) std::floor(src_pt[1] - 0.5f); + const int src_x1 = src_x0 + 1; + const int src_y1 = src_y0 + 1; + const float x = src_pt[0] - src_x0; + const float y = src_pt[1] - src_y0; + + PixelType tl_color = bg_color; + if ((src_x0 >= 0) && (src_x0 < src_width) && (src_y0 >= 0) && (src_y0 < src_height)) { + tl_color = src_data[src_y0 * src_stride + src_x0]; + } + + PixelType tr_color = bg_color; + if ((src_x1 >= 0) && (src_x1 < src_width) && (src_y0 >= 0) && (src_y0 < src_height)) { + tr_color = src_data[src_y0 * src_stride + src_x1]; + } + + PixelType bl_color = bg_color; + if ((src_x0 >= 0) && (src_x0 < src_width) && (src_y1 >= 0) && (src_y1 < src_height)) { + bl_color = src_data[src_y1 * src_stride + src_x0]; + } + + PixelType br_color = bg_color; + if ((src_x1 >= 0) && (src_x1 < src_width) && (src_y1 >= 0) && (src_y1 < src_height)) { + br_color = src_data[src_y1 * src_stride + src_x1]; + } + + ColorMixer mixer; + mixer.add(tl_color, (1.5f - y) * (1.5f - x)); + mixer.add(tr_color, (1.5f - y) * (x - 0.5f)); + mixer.add(bl_color, (y - 0.5f) * (1.5f - x)); + mixer.add(br_color, (y - 0.5f) * (x - 0.5f)); + dst_data[dst_y * dst_stride + dst_x] = mixer.mix(1.0f); } + } } // dewarpGeneric #elif INTERPOLATION_METHOD == INTERP_AREA_MAPPING -template +template void areaMapGeneratrix(const PixelType* const src_data, const QSize src_size, const int src_stride, @@ -162,252 +162,252 @@ void areaMapGeneratrix(const PixelType* const src_data, const PixelType bg_color, const std::vector& prev_grid_column, const std::vector& next_grid_column) { - const int sw = src_size.width(); - const int sh = src_size.height(); - const int dst_height = dst_size.height(); + const int sw = src_size.width(); + const int sh = src_size.height(); + const int dst_height = dst_size.height(); + + const Vec2f* src_left_points = &prev_grid_column[0]; + const Vec2f* src_right_points = &next_grid_column[0]; + + Vec2f f_src32_quad[4]; + + for (int dst_y = 0; dst_y < dst_height; ++dst_y) { + // Take a mid-point of each edge, pre-multiply by 32, + // write the result to f_src32_quad. 16 comes from 32*0.5 + f_src32_quad[0] = 16.0f * (src_left_points[0] + src_right_points[0]); + f_src32_quad[1] = 16.0f * (src_right_points[0] + src_right_points[1]); + f_src32_quad[2] = 16.0f * (src_right_points[1] + src_left_points[1]); + f_src32_quad[3] = 16.0f * (src_left_points[0] + src_left_points[1]); + ++src_left_points; + ++src_right_points; + + // Calculate the bounding box of src_quad. + + float f_src32_left = f_src32_quad[0][0]; + float f_src32_top = f_src32_quad[0][1]; + float f_src32_right = f_src32_left; + float f_src32_bottom = f_src32_top; + + for (int i = 1; i < 4; ++i) { + const Vec2f pt(f_src32_quad[i]); + if (pt[0] < f_src32_left) { + f_src32_left = pt[0]; + } else if (pt[0] > f_src32_right) { + f_src32_right = pt[0]; + } + if (pt[1] < f_src32_top) { + f_src32_top = pt[1]; + } else if (pt[1] > f_src32_bottom) { + f_src32_bottom = pt[1]; + } + } - const Vec2f* src_left_points = &prev_grid_column[0]; - const Vec2f* src_right_points = &next_grid_column[0]; + if ((f_src32_top < -32.0f * 10000.0f) || (f_src32_left < -32.0f * 10000.0f) + || (f_src32_bottom > 32.0f * (float(sh) + 10000.f)) || (f_src32_right > 32.0f * (float(sw) + 10000.f))) { + // This helps to prevent integer overflows. + *p_dst = bg_color; + p_dst += dst_stride; + continue; + } - Vec2f f_src32_quad[4]; + // Note: the code below is more or less the same as in transformGeneric() + // in imageproc/Transform.cpp + + // Note that without using std::floor() and std::ceil() + // we can't guarantee that src_bottom >= src_top + // and src_right >= src_left. + auto src32_left = (int) std::floor(f_src32_left); + auto src32_right = (int) std::ceil(f_src32_right); + auto src32_top = (int) std::floor(f_src32_top); + auto src32_bottom = (int) std::ceil(f_src32_bottom); + int src_left = src32_left >> 5; + int src_right = (src32_right - 1) >> 5; // inclusive + int src_top = src32_top >> 5; + int src_bottom = (src32_bottom - 1) >> 5; // inclusive + assert(src_bottom >= src_top); + assert(src_right >= src_left); + + if ((src_bottom < 0) || (src_right < 0) || (src_left >= sw) || (src_top >= sh)) { + // Completely outside of src image. + *p_dst = bg_color; + p_dst += dst_stride; + continue; + } - for (int dst_y = 0; dst_y < dst_height; ++dst_y) { - // Take a mid-point of each edge, pre-multiply by 32, - // write the result to f_src32_quad. 16 comes from 32*0.5 - f_src32_quad[0] = 16.0f * (src_left_points[0] + src_right_points[0]); - f_src32_quad[1] = 16.0f * (src_right_points[0] + src_right_points[1]); - f_src32_quad[2] = 16.0f * (src_right_points[1] + src_left_points[1]); - f_src32_quad[3] = 16.0f * (src_left_points[0] + src_left_points[1]); - ++src_left_points; - ++src_right_points; - - // Calculate the bounding box of src_quad. - - float f_src32_left = f_src32_quad[0][0]; - float f_src32_top = f_src32_quad[0][1]; - float f_src32_right = f_src32_left; - float f_src32_bottom = f_src32_top; - - for (int i = 1; i < 4; ++i) { - const Vec2f pt(f_src32_quad[i]); - if (pt[0] < f_src32_left) { - f_src32_left = pt[0]; - } else if (pt[0] > f_src32_right) { - f_src32_right = pt[0]; - } - if (pt[1] < f_src32_top) { - f_src32_top = pt[1]; - } else if (pt[1] > f_src32_bottom) { - f_src32_bottom = pt[1]; - } - } + /* + * Note that (intval / 32) is not the same as (intval >> 5). + * The former rounds towards zero, while the latter rounds towards + * negative infinity. + * Likewise, (intval % 32) is not the same as (intval & 31). + * The following expression: + * top_fraction = 32 - (src32_top & 31); + * works correctly with both positive and negative src32_top. + */ + + unsigned background_area = 0; + + if (src_top < 0) { + const unsigned top_fraction = 32 - (src32_top & 31); + const unsigned hor_fraction = src32_right - src32_left; + background_area += top_fraction * hor_fraction; + const unsigned full_pixels_ver = -1 - src_top; + background_area += hor_fraction * (full_pixels_ver << 5); + src_top = 0; + src32_top = 0; + } + if (src_bottom >= sh) { + const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); + const unsigned hor_fraction = src32_right - src32_left; + background_area += bottom_fraction * hor_fraction; + const unsigned full_pixels_ver = src_bottom - sh; + background_area += hor_fraction * (full_pixels_ver << 5); + src_bottom = sh - 1; // inclusive + src32_bottom = sh << 5; // exclusive + } + if (src_left < 0) { + const unsigned left_fraction = 32 - (src32_left & 31); + const unsigned vert_fraction = src32_bottom - src32_top; + background_area += left_fraction * vert_fraction; + const unsigned full_pixels_hor = -1 - src_left; + background_area += vert_fraction * (full_pixels_hor << 5); + src_left = 0; + src32_left = 0; + } + if (src_right >= sw) { + const unsigned right_fraction = src32_right - (src_right << 5); + const unsigned vert_fraction = src32_bottom - src32_top; + background_area += right_fraction * vert_fraction; + const unsigned full_pixels_hor = src_right - sw; + background_area += vert_fraction * (full_pixels_hor << 5); + src_right = sw - 1; // inclusive + src32_right = sw << 5; // exclusive + } + assert(src_bottom >= src_top); + assert(src_right >= src_left); + + ColorMixer mixer; + // if (weak_background) { + // background_area = 0; + // } else { + mixer.add(bg_color, background_area); + // } + + const unsigned left_fraction = 32 - (src32_left & 31); + const unsigned top_fraction = 32 - (src32_top & 31); + const unsigned right_fraction = src32_right - (src_right << 5); + const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); + + assert(left_fraction + right_fraction + (src_right - src_left - 1) * 32 + == static_cast(src32_right - src32_left)); + assert(top_fraction + bottom_fraction + (src_bottom - src_top - 1) * 32 + == static_cast(src32_bottom - src32_top)); + + const unsigned src_area = (src32_bottom - src32_top) * (src32_right - src32_left); + if (src_area == 0) { + *p_dst = bg_color; + p_dst += dst_stride; + continue; + } - if ((f_src32_top < -32.0f * 10000.0f) || (f_src32_left < -32.0f * 10000.0f) - || (f_src32_bottom > 32.0f * (float(sh) + 10000.f)) || (f_src32_right > 32.0f * (float(sw) + 10000.f))) { - // This helps to prevent integer overflows. - *p_dst = bg_color; - p_dst += dst_stride; - continue; + const PixelType* src_line = &src_data[src_top * src_stride]; + + if (src_top == src_bottom) { + if (src_left == src_right) { + // dst pixel maps to a single src pixel + const PixelType c = src_line[src_left]; + if (background_area == 0) { + // common case optimization + *p_dst = c; + p_dst += dst_stride; + continue; } - - // Note: the code below is more or less the same as in transformGeneric() - // in imageproc/Transform.cpp - - // Note that without using std::floor() and std::ceil() - // we can't guarantee that src_bottom >= src_top - // and src_right >= src_left. - auto src32_left = (int) std::floor(f_src32_left); - auto src32_right = (int) std::ceil(f_src32_right); - auto src32_top = (int) std::floor(f_src32_top); - auto src32_bottom = (int) std::ceil(f_src32_bottom); - int src_left = src32_left >> 5; - int src_right = (src32_right - 1) >> 5; // inclusive - int src_top = src32_top >> 5; - int src_bottom = (src32_bottom - 1) >> 5; // inclusive - assert(src_bottom >= src_top); - assert(src_right >= src_left); - - if ((src_bottom < 0) || (src_right < 0) || (src_left >= sw) || (src_top >= sh)) { - // Completely outside of src image. - *p_dst = bg_color; - p_dst += dst_stride; - continue; + mixer.add(c, src_area); + } else { + // dst pixel maps to a horizontal line of src pixels + const unsigned vert_fraction = src32_bottom - src32_top; + const unsigned left_area = vert_fraction * left_fraction; + const unsigned middle_area = vert_fraction << 5; + const unsigned right_area = vert_fraction * right_fraction; + + mixer.add(src_line[src_left], left_area); + + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], middle_area); } - /* - * Note that (intval / 32) is not the same as (intval >> 5). - * The former rounds towards zero, while the latter rounds towards - * negative infinity. - * Likewise, (intval % 32) is not the same as (intval & 31). - * The following expression: - * top_fraction = 32 - (src32_top & 31); - * works correctly with both positive and negative src32_top. - */ - - unsigned background_area = 0; - - if (src_top < 0) { - const unsigned top_fraction = 32 - (src32_top & 31); - const unsigned hor_fraction = src32_right - src32_left; - background_area += top_fraction * hor_fraction; - const unsigned full_pixels_ver = -1 - src_top; - background_area += hor_fraction * (full_pixels_ver << 5); - src_top = 0; - src32_top = 0; - } - if (src_bottom >= sh) { - const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); - const unsigned hor_fraction = src32_right - src32_left; - background_area += bottom_fraction * hor_fraction; - const unsigned full_pixels_ver = src_bottom - sh; - background_area += hor_fraction * (full_pixels_ver << 5); - src_bottom = sh - 1; // inclusive - src32_bottom = sh << 5; // exclusive - } - if (src_left < 0) { - const unsigned left_fraction = 32 - (src32_left & 31); - const unsigned vert_fraction = src32_bottom - src32_top; - background_area += left_fraction * vert_fraction; - const unsigned full_pixels_hor = -1 - src_left; - background_area += vert_fraction * (full_pixels_hor << 5); - src_left = 0; - src32_left = 0; - } - if (src_right >= sw) { - const unsigned right_fraction = src32_right - (src_right << 5); - const unsigned vert_fraction = src32_bottom - src32_top; - background_area += right_fraction * vert_fraction; - const unsigned full_pixels_hor = src_right - sw; - background_area += vert_fraction * (full_pixels_hor << 5); - src_right = sw - 1; // inclusive - src32_right = sw << 5; // exclusive - } - assert(src_bottom >= src_top); - assert(src_right >= src_left); - - ColorMixer mixer; - // if (weak_background) { - // background_area = 0; - // } else { - mixer.add(bg_color, background_area); - // } - - const unsigned left_fraction = 32 - (src32_left & 31); - const unsigned top_fraction = 32 - (src32_top & 31); - const unsigned right_fraction = src32_right - (src_right << 5); - const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); - - assert(left_fraction + right_fraction + (src_right - src_left - 1) * 32 - == static_cast(src32_right - src32_left)); - assert(top_fraction + bottom_fraction + (src_bottom - src_top - 1) * 32 - == static_cast(src32_bottom - src32_top)); - - const unsigned src_area = (src32_bottom - src32_top) * (src32_right - src32_left); - if (src_area == 0) { - *p_dst = bg_color; - p_dst += dst_stride; - continue; - } + mixer.add(src_line[src_right], right_area); + } + } else if (src_left == src_right) { + // dst pixel maps to a vertical line of src pixels + const unsigned hor_fraction = src32_right - src32_left; + const unsigned top_area = hor_fraction * top_fraction; + const unsigned middle_area = hor_fraction << 5; + const unsigned bottom_area = hor_fraction * bottom_fraction; - const PixelType* src_line = &src_data[src_top * src_stride]; - - if (src_top == src_bottom) { - if (src_left == src_right) { - // dst pixel maps to a single src pixel - const PixelType c = src_line[src_left]; - if (background_area == 0) { - // common case optimization - *p_dst = c; - p_dst += dst_stride; - continue; - } - mixer.add(c, src_area); - } else { - // dst pixel maps to a horizontal line of src pixels - const unsigned vert_fraction = src32_bottom - src32_top; - const unsigned left_area = vert_fraction * left_fraction; - const unsigned middle_area = vert_fraction << 5; - const unsigned right_area = vert_fraction * right_fraction; - - mixer.add(src_line[src_left], left_area); - - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], middle_area); - } - - mixer.add(src_line[src_right], right_area); - } - } else if (src_left == src_right) { - // dst pixel maps to a vertical line of src pixels - const unsigned hor_fraction = src32_right - src32_left; - const unsigned top_area = hor_fraction * top_fraction; - const unsigned middle_area = hor_fraction << 5; - const unsigned bottom_area = hor_fraction * bottom_fraction; - - src_line += src_left; - mixer.add(*src_line, top_area); - - src_line += src_stride; - - for (int sy = src_top + 1; sy < src_bottom; ++sy) { - mixer.add(*src_line, middle_area); - src_line += src_stride; - } - - mixer.add(*src_line, bottom_area); - } else { - // dst pixel maps to a block of src pixels - const unsigned top_area = top_fraction << 5; - const unsigned bottom_area = bottom_fraction << 5; - const unsigned left_area = left_fraction << 5; - const unsigned right_area = right_fraction << 5; - const unsigned topleft_area = top_fraction * left_fraction; - const unsigned topright_area = top_fraction * right_fraction; - const unsigned bottomleft_area = bottom_fraction * left_fraction; - const unsigned bottomright_area = bottom_fraction * right_fraction; - - // process the top-left corner - mixer.add(src_line[src_left], topleft_area); - - // process the top line (without corners) - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], top_area); - } - - // process the top-right corner - mixer.add(src_line[src_right], topright_area); - - src_line += src_stride; - // process middle lines - for (int sy = src_top + 1; sy < src_bottom; ++sy) { - mixer.add(src_line[src_left], left_area); - - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], 32 * 32); - } - - mixer.add(src_line[src_right], right_area); - - src_line += src_stride; - } - - // process bottom-left corner - mixer.add(src_line[src_left], bottomleft_area); - - // process the bottom line (without corners) - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], bottom_area); - } - // process the bottom-right corner - mixer.add(src_line[src_right], bottomright_area); + src_line += src_left; + mixer.add(*src_line, top_area); + + src_line += src_stride; + + for (int sy = src_top + 1; sy < src_bottom; ++sy) { + mixer.add(*src_line, middle_area); + src_line += src_stride; + } + + mixer.add(*src_line, bottom_area); + } else { + // dst pixel maps to a block of src pixels + const unsigned top_area = top_fraction << 5; + const unsigned bottom_area = bottom_fraction << 5; + const unsigned left_area = left_fraction << 5; + const unsigned right_area = right_fraction << 5; + const unsigned topleft_area = top_fraction * left_fraction; + const unsigned topright_area = top_fraction * right_fraction; + const unsigned bottomleft_area = bottom_fraction * left_fraction; + const unsigned bottomright_area = bottom_fraction * right_fraction; + + // process the top-left corner + mixer.add(src_line[src_left], topleft_area); + + // process the top line (without corners) + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], top_area); + } + + // process the top-right corner + mixer.add(src_line[src_right], topright_area); + + src_line += src_stride; + // process middle lines + for (int sy = src_top + 1; sy < src_bottom; ++sy) { + mixer.add(src_line[src_left], left_area); + + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], 32 * 32); } - *p_dst = mixer.mix(src_area + background_area); - p_dst += dst_stride; + mixer.add(src_line[src_right], right_area); + + src_line += src_stride; + } + + // process bottom-left corner + mixer.add(src_line[src_left], bottomleft_area); + + // process the bottom line (without corners) + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], bottom_area); + } + // process the bottom-right corner + mixer.add(src_line[src_right], bottomright_area); } + + *p_dst = mixer.mix(src_area + background_area); + p_dst += dst_stride; + } } // areaMapGeneratrix -template +template void dewarpGeneric(const PixelType* const src_data, const QSize src_size, const int src_stride, @@ -417,41 +417,41 @@ void dewarpGeneric(const PixelType* const src_data, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const PixelType bg_color) { - const int src_width = src_size.width(); - const int src_height = src_size.height(); - const int dst_width = dst_size.width(); - const int dst_height = dst_size.height(); + const int src_width = src_size.width(); + const int src_height = src_size.height(); + const int dst_width = dst_size.width(); + const int dst_height = dst_size.height(); - CylindricalSurfaceDewarper::State state; + CylindricalSurfaceDewarper::State state; - const double model_domain_left = model_domain.left(); - const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); + const double model_domain_left = model_domain.left(); + const double model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); - const auto model_domain_top = static_cast(model_domain.top()); - const auto model_y_scale = static_cast(1.0 / (model_domain.bottom() - model_domain.top())); + const auto model_domain_top = static_cast(model_domain.top()); + const auto model_y_scale = static_cast(1.0 / (model_domain.bottom() - model_domain.top())); - std::vector prev_grid_column(dst_height + 1); - std::vector next_grid_column(dst_height + 1); + std::vector prev_grid_column(dst_height + 1); + std::vector next_grid_column(dst_height + 1); - for (int dst_x = 0; dst_x <= dst_width; ++dst_x) { - const double model_x = (dst_x - model_domain_left) * model_x_scale; - const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); - - const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); - const Vec2f origin(generatrix.imgLine.p1()); - const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); - for (int dst_y = 0; dst_y <= dst_height; ++dst_y) { - const float model_y = (float(dst_y) - model_domain_top) * model_y_scale; - next_grid_column[dst_y] = origin + vec * homog(model_y); - } + for (int dst_x = 0; dst_x <= dst_width; ++dst_x) { + const double model_x = (dst_x - model_domain_left) * model_x_scale; + const CylindricalSurfaceDewarper::Generatrix generatrix(distortion_model.mapGeneratrix(model_x, state)); - if (dst_x != 0) { - areaMapGeneratrix(src_data, src_size, src_stride, dst_data + dst_x - 1, dst_size, - dst_stride, bg_color, prev_grid_column, next_grid_column); - } + const HomographicTransform<1, float> homog(generatrix.pln2img.mat()); + const Vec2f origin(generatrix.imgLine.p1()); + const Vec2f vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); + for (int dst_y = 0; dst_y <= dst_height; ++dst_y) { + const float model_y = (float(dst_y) - model_domain_top) * model_y_scale; + next_grid_column[dst_y] = origin + vec * homog(model_y); + } - prev_grid_column.swap(next_grid_column); + if (dst_x != 0) { + areaMapGeneratrix(src_data, src_size, src_stride, dst_data + dst_x - 1, dst_size, + dst_stride, bg_color, prev_grid_column, next_grid_column); } + + prev_grid_column.swap(next_grid_column); + } } // dewarpGeneric #endif // INTERPOLATION_METHOD #if INTERPOLATION_METHOD == INTERP_BILLINEAR @@ -465,14 +465,13 @@ QImage dewarpGrayscale(const QImage& src, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const QColor& bg_color) { - GrayImage dst(dst_size); - const auto bg_sample = static_cast(qGray(bg_color.rgb())); - dst.fill(bg_sample); - dewarpGeneric, uint8_t>(src.bits(), src.size(), src.bytesPerLine(), dst.data(), - dst_size, dst.stride(), distortion_model, model_domain, - bg_sample); - - return dst.toQImage(); + GrayImage dst(dst_size); + const auto bg_sample = static_cast(qGray(bg_color.rgb())); + dst.fill(bg_sample); + dewarpGeneric, uint8_t>(src.bits(), src.size(), src.bytesPerLine(), dst.data(), dst_size, + dst.stride(), distortion_model, model_domain, bg_sample); + + return dst.toQImage(); } QImage dewarpRgb(const QImage& src, @@ -480,13 +479,13 @@ QImage dewarpRgb(const QImage& src, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const QColor& bg_color) { - QImage dst(dst_size, QImage::Format_RGB32); - dst.fill(bg_color.rgb()); - dewarpGeneric, uint32_t>( - (const uint32_t*) src.bits(), src.size(), src.bytesPerLine() / 4, (uint32_t*) dst.bits(), dst_size, - dst.bytesPerLine() / 4, distortion_model, model_domain, bg_color.rgb()); + QImage dst(dst_size, QImage::Format_RGB32); + dst.fill(bg_color.rgb()); + dewarpGeneric, uint32_t>((const uint32_t*) src.bits(), src.size(), src.bytesPerLine() / 4, + (uint32_t*) dst.bits(), dst_size, dst.bytesPerLine() / 4, + distortion_model, model_domain, bg_color.rgb()); - return dst; + return dst; } QImage dewarpArgb(const QImage& src, @@ -494,13 +493,13 @@ QImage dewarpArgb(const QImage& src, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const QColor& bg_color) { - QImage dst(dst_size, QImage::Format_ARGB32); - dst.fill(bg_color.rgba()); - dewarpGeneric, uint32_t>( - (const uint32_t*) src.bits(), src.size(), src.bytesPerLine() / 4, (uint32_t*) dst.bits(), dst_size, - dst.bytesPerLine() / 4, distortion_model, model_domain, bg_color.rgba()); + QImage dst(dst_size, QImage::Format_ARGB32); + dst.fill(bg_color.rgba()); + dewarpGeneric, uint32_t>( + (const uint32_t*) src.bits(), src.size(), src.bytesPerLine() / 4, (uint32_t*) dst.bits(), dst_size, + dst.bytesPerLine() / 4, distortion_model, model_domain, bg_color.rgba()); - return dst; + return dst; } } // namespace @@ -509,39 +508,38 @@ QImage RasterDewarper::dewarp(const QImage& src, const CylindricalSurfaceDewarper& distortion_model, const QRectF& model_domain, const QColor& bg_color) { - if (model_domain.isEmpty()) { - throw std::invalid_argument("RasterDewarper: model_domain is empty."); - } - - switch (src.format()) { - case QImage::Format_Invalid: - return QImage(); - case QImage::Format_RGB32: - return dewarpRgb(src, dst_size, distortion_model, model_domain, bg_color); - case QImage::Format_ARGB32: - return dewarpArgb(src, dst_size, distortion_model, model_domain, bg_color); - case QImage::Format_Indexed8: - if (src.isGrayscale()) { - return dewarpGrayscale(src, dst_size, distortion_model, model_domain, bg_color); - } else if (src.allGray()) { - // Only shades of gray but non-standard palette. - return dewarpGrayscale(GrayImage(src).toQImage(), dst_size, distortion_model, model_domain, bg_color); - } - break; - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - if (src.allGray()) { - return dewarpGrayscale(GrayImage(src).toQImage(), dst_size, distortion_model, model_domain, bg_color); - } - break; - default:; - } - // Generic case: convert to either RGB32 or ARGB32. - if (src.hasAlphaChannel()) { - return dewarpArgb(src.convertToFormat(QImage::Format_ARGB32), dst_size, distortion_model, model_domain, - bg_color); - } else { - return dewarpRgb(src.convertToFormat(QImage::Format_RGB32), dst_size, distortion_model, model_domain, bg_color); - } + if (model_domain.isEmpty()) { + throw std::invalid_argument("RasterDewarper: model_domain is empty."); + } + + switch (src.format()) { + case QImage::Format_Invalid: + return QImage(); + case QImage::Format_RGB32: + return dewarpRgb(src, dst_size, distortion_model, model_domain, bg_color); + case QImage::Format_ARGB32: + return dewarpArgb(src, dst_size, distortion_model, model_domain, bg_color); + case QImage::Format_Indexed8: + if (src.isGrayscale()) { + return dewarpGrayscale(src, dst_size, distortion_model, model_domain, bg_color); + } else if (src.allGray()) { + // Only shades of gray but non-standard palette. + return dewarpGrayscale(GrayImage(src).toQImage(), dst_size, distortion_model, model_domain, bg_color); + } + break; + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + if (src.allGray()) { + return dewarpGrayscale(GrayImage(src).toQImage(), dst_size, distortion_model, model_domain, bg_color); + } + break; + default:; + } + // Generic case: convert to either RGB32 or ARGB32. + if (src.hasAlphaChannel()) { + return dewarpArgb(src.convertToFormat(QImage::Format_ARGB32), dst_size, distortion_model, model_domain, bg_color); + } else { + return dewarpRgb(src.convertToFormat(QImage::Format_RGB32), dst_size, distortion_model, model_domain, bg_color); + } } // RasterDewarper::dewarp } // namespace dewarping \ No newline at end of file diff --git a/dewarping/RasterDewarper.h b/dewarping/RasterDewarper.h index f7aab033a..9beb0b8d9 100644 --- a/dewarping/RasterDewarper.h +++ b/dewarping/RasterDewarper.h @@ -28,12 +28,12 @@ namespace dewarping { class CylindricalSurfaceDewarper; class RasterDewarper { -public: - static QImage dewarp(const QImage& src, - const QSize& dst_size, - const CylindricalSurfaceDewarper& distortion_model, - const QRectF& model_domain, - const QColor& background_color); + public: + static QImage dewarp(const QImage& src, + const QSize& dst_size, + const CylindricalSurfaceDewarper& distortion_model, + const QRectF& model_domain, + const QColor& background_color); }; } // namespace dewarping #endif diff --git a/dewarping/TextLineRefiner.cpp b/dewarping/TextLineRefiner.cpp index b297c7a4a..0bc18d521 100644 --- a/dewarping/TextLineRefiner.cpp +++ b/dewarping/TextLineRefiner.cpp @@ -17,434 +17,424 @@ */ #include "TextLineRefiner.h" -#include "NumericTraits.h" -#include "DebugImages.h" -#include "imageproc/GaussBlur.h" -#include "imageproc/Sobel.h" +#include +#include #include -#include #include -#include -#include +#include #include +#include "DebugImages.h" +#include "NumericTraits.h" +#include "imageproc/GaussBlur.h" +#include "imageproc/Sobel.h" using namespace imageproc; namespace dewarping { class TextLineRefiner::SnakeLength { -public: - explicit SnakeLength(const Snake& snake); + public: + explicit SnakeLength(const Snake& snake); - float totalLength() const { - return m_totalLength; - } + float totalLength() const { return m_totalLength; } - float avgSegmentLength() const { - return m_avgSegmentLength; - } + float avgSegmentLength() const { return m_avgSegmentLength; } - float arcLengthAt(size_t node_idx) const { - return m_integralLength[node_idx]; - } + float arcLengthAt(size_t node_idx) const { return m_integralLength[node_idx]; } - float arcLengthFractionAt(size_t node_idx) const { - return m_integralLength[node_idx] * m_rTotalLength; - } + float arcLengthFractionAt(size_t node_idx) const { return m_integralLength[node_idx] * m_reciprocalTotalLength; } - float lengthFromTo(size_t from_node_idx, size_t to_node_idx) const { - return m_integralLength[to_node_idx] - m_integralLength[from_node_idx]; - } + float lengthFromTo(size_t from_node_idx, size_t to_node_idx) const { + return m_integralLength[to_node_idx] - m_integralLength[from_node_idx]; + } -private: - std::vector m_integralLength; - float m_totalLength; - float m_rTotalLength; // Reciprocal of the above. - float m_avgSegmentLength; + private: + std::vector m_integralLength; + float m_totalLength; + float m_reciprocalTotalLength; + float m_avgSegmentLength; }; struct TextLineRefiner::FrenetFrame { - Vec2f unitTangent; - Vec2f unitDownNormal; + Vec2f unitTangent; + Vec2f unitDownNormal; }; class TextLineRefiner::Optimizer { -public: - Optimizer(const Snake& snake, const Vec2f& unit_down_vec, float factor); + public: + Optimizer(const Snake& snake, const Vec2f& unit_down_vec, float factor); - bool thicknessAdjustment(Snake& snake, const Grid& gradient); + bool thicknessAdjustment(Snake& snake, const Grid& gradient); - bool tangentMovement(Snake& snake, const Grid& gradient); + bool tangentMovement(Snake& snake, const Grid& gradient); - bool normalMovement(Snake& snake, const Grid& gradient); + bool normalMovement(Snake& snake, const Grid& gradient); -private: - static float calcExternalEnergy(const Grid& gradient, const SnakeNode& node, Vec2f down_normal); + private: + static float calcExternalEnergy(const Grid& gradient, const SnakeNode& node, Vec2f down_normal); - static float calcElasticityEnergy(const SnakeNode& node1, const SnakeNode& node2, float avg_dist); + static float calcElasticityEnergy(const SnakeNode& node1, const SnakeNode& node2, float avg_dist); - static float calcBendingEnergy(const SnakeNode& node, const SnakeNode& prev_node, const SnakeNode& prev_prev_node); + static float calcBendingEnergy(const SnakeNode& node, const SnakeNode& prev_node, const SnakeNode& prev_prev_node); - static const float m_elasticityWeight; - static const float m_bendingWeight; - static const float m_topExternalWeight; - static const float m_bottomExternalWeight; - const float m_factor; - SnakeLength m_snakeLength; - std::vector m_frenetFrames; + static const float m_elasticityWeight; + static const float m_bendingWeight; + static const float m_topExternalWeight; + static const float m_bottomExternalWeight; + const float m_factor; + SnakeLength m_snakeLength; + std::vector m_frenetFrames; }; TextLineRefiner::TextLineRefiner(const GrayImage& image, const Dpi& dpi, const Vec2f& unit_down_vector) - : m_image(image), m_dpi(dpi), m_unitDownVec(unit_down_vector) { -} + : m_image(image), m_dpi(dpi), m_unitDownVec(unit_down_vector) {} void TextLineRefiner::refine(std::list>& polylines, const int iterations, DebugImages* dbg) const { - if (polylines.empty()) { - return; - } - - std::vector snakes; - snakes.reserve(polylines.size()); - - // Convert from polylines to snakes. - for (const std::vector& polyline : polylines) { - snakes.push_back(makeSnake(polyline, iterations)); - } - - if (dbg) { - dbg->add(visualizeSnakes(snakes), "initial_snakes"); - } - - Grid gradient(m_image.width(), m_image.height(), /*padding=*/0); - - // Start with a rather strong blur. - float h_sigma = (4.0f / 200.f) * m_dpi.horizontal(); - float v_sigma = (4.0f / 200.f) * m_dpi.vertical(); - calcBlurredGradient(gradient, h_sigma, v_sigma); - - for (Snake& snake : snakes) { - evolveSnake(snake, gradient, ON_CONVERGENCE_STOP); - } - if (dbg) { - dbg->add(visualizeSnakes(snakes, &gradient), "evolved_snakes1"); - } - - // Less blurring this time. - h_sigma *= 0.5f; - v_sigma *= 0.5f; - calcBlurredGradient(gradient, h_sigma, v_sigma); - - for (Snake& snake : snakes) { - evolveSnake(snake, gradient, ON_CONVERGENCE_GO_FINER); - } - if (dbg) { - dbg->add(visualizeSnakes(snakes, &gradient), "evolved_snakes2"); - } - - // Convert from snakes back to polylines. - int i = -1; - for (std::vector& polyline : polylines) { - ++i; - const Snake& snake = snakes[i]; - polyline.clear(); - for (const SnakeNode& node : snake.nodes) { - polyline.push_back(node.center); - } - } + if (polylines.empty()) { + return; + } + + std::vector snakes; + snakes.reserve(polylines.size()); + + // Convert from polylines to snakes. + for (const std::vector& polyline : polylines) { + snakes.push_back(makeSnake(polyline, iterations)); + } + + if (dbg) { + dbg->add(visualizeSnakes(snakes), "initial_snakes"); + } + + Grid gradient(m_image.width(), m_image.height(), /*padding=*/0); + + // Start with a rather strong blur. + float h_sigma = (4.0f / 200.f) * m_dpi.horizontal(); + float v_sigma = (4.0f / 200.f) * m_dpi.vertical(); + calcBlurredGradient(gradient, h_sigma, v_sigma); + + for (Snake& snake : snakes) { + evolveSnake(snake, gradient, ON_CONVERGENCE_STOP); + } + if (dbg) { + dbg->add(visualizeSnakes(snakes, &gradient), "evolved_snakes1"); + } + + // Less blurring this time. + h_sigma *= 0.5f; + v_sigma *= 0.5f; + calcBlurredGradient(gradient, h_sigma, v_sigma); + + for (Snake& snake : snakes) { + evolveSnake(snake, gradient, ON_CONVERGENCE_GO_FINER); + } + if (dbg) { + dbg->add(visualizeSnakes(snakes, &gradient), "evolved_snakes2"); + } + + // Convert from snakes back to polylines. + int i = -1; + for (std::vector& polyline : polylines) { + ++i; + const Snake& snake = snakes[i]; + polyline.clear(); + for (const SnakeNode& node : snake.nodes) { + polyline.push_back(node.center); + } + } } // TextLineRefiner::refine void TextLineRefiner::calcBlurredGradient(Grid& gradient, float h_sigma, float v_sigma) const { - using namespace boost::lambda; - - const float downscale = 1.0f / (255.0f * 8.0f); - Grid vert_grad(m_image.width(), m_image.height(), /*padding=*/0); - horizontalSobel(m_image.width(), m_image.height(), m_image.data(), m_image.stride(), _1 * downscale, - gradient.data(), gradient.stride(), _1 = _2, _1, gradient.data(), gradient.stride(), - _1 = _2); - verticalSobel(m_image.width(), m_image.height(), m_image.data(), m_image.stride(), _1 * downscale, - vert_grad.data(), vert_grad.stride(), _1 = _2, _1, gradient.data(), gradient.stride(), - _1 = _1 * m_unitDownVec[0] + _2 * m_unitDownVec[1]); - Grid().swap(vert_grad); // Save memory. - gaussBlurGeneric(m_image.size(), h_sigma, v_sigma, gradient.data(), gradient.stride(), _1, gradient.data(), - gradient.stride(), _1 = _2); + using namespace boost::lambda; + + const float downscale = 1.0f / (255.0f * 8.0f); + Grid vert_grad(m_image.width(), m_image.height(), /*padding=*/0); + horizontalSobel(m_image.width(), m_image.height(), m_image.data(), m_image.stride(), _1 * downscale, + gradient.data(), gradient.stride(), _1 = _2, _1, gradient.data(), gradient.stride(), _1 = _2); + verticalSobel(m_image.width(), m_image.height(), m_image.data(), m_image.stride(), _1 * downscale, + vert_grad.data(), vert_grad.stride(), _1 = _2, _1, gradient.data(), gradient.stride(), + _1 = _1 * m_unitDownVec[0] + _2 * m_unitDownVec[1]); + Grid().swap(vert_grad); // Save memory. + gaussBlurGeneric(m_image.size(), h_sigma, v_sigma, gradient.data(), gradient.stride(), _1, gradient.data(), + gradient.stride(), _1 = _2); } float TextLineRefiner::externalEnergyAt(const Grid& gradient, const Vec2f& pos, float penalty_if_outside) { - const auto x_base = static_cast(std::floor(pos[0])); - const auto y_base = static_cast(std::floor(pos[1])); - const auto x_base_i = (int) x_base; - const auto y_base_i = (int) y_base; + const auto x_base = static_cast(std::floor(pos[0])); + const auto y_base = static_cast(std::floor(pos[1])); + const auto x_base_i = (int) x_base; + const auto y_base_i = (int) y_base; - if ((x_base_i < 0) || (y_base_i < 0) || (x_base_i + 1 >= gradient.width()) || (y_base_i + 1 >= gradient.height())) { - return penalty_if_outside; - } + if ((x_base_i < 0) || (y_base_i < 0) || (x_base_i + 1 >= gradient.width()) || (y_base_i + 1 >= gradient.height())) { + return penalty_if_outside; + } - const float x = pos[0] - x_base; - const float y = pos[1] - y_base; - const float x1 = 1.0f - x; - const float y1 = 1.0f - y; + const float x = pos[0] - x_base; + const float y = pos[1] - y_base; + const float x1 = 1.0f - x; + const float y1 = 1.0f - y; - const int stride = gradient.stride(); - const float* base = gradient.data() + y_base_i * stride + x_base_i; + const int stride = gradient.stride(); + const float* base = gradient.data() + y_base_i * stride + x_base_i; - return base[0] * x1 * y1 + base[1] * x * y1 + base[stride] * x1 * y + base[stride + 1] * x * y; + return base[0] * x1 * y1 + base[1] * x * y1 + base[stride] * x1 * y + base[stride + 1] * x * y; } TextLineRefiner::Snake TextLineRefiner::makeSnake(const std::vector& polyline, const int iterations) { - float total_length = 0; - - const size_t polyline_size = polyline.size(); - for (size_t i = 1; i < polyline_size; ++i) { - total_length += std::sqrt(Vec2f(polyline[i] - polyline[i - 1]).squaredNorm()); - } - - const auto points_in_snake = static_cast(total_length / 20); - Snake snake; - snake.iterationsRemaining = iterations; - - int points_inserted = 0; - float base_t = 0; - float next_insert_t = 0; - for (size_t i = 1; i < polyline_size; ++i) { - const Vec2f base(polyline[i - 1]); - const Vec2f vec((polyline[i] - base)); - const auto next_t = static_cast(base_t + std::sqrt(vec.squaredNorm())); - - while (next_t >= next_insert_t) { - const float fraction = (next_insert_t - base_t) / (next_t - base_t); - SnakeNode node; - node.center = base + fraction * vec; - node.ribHalfLength = 4; - snake.nodes.push_back(node); - ++points_inserted; - next_insert_t = total_length * points_inserted / (points_in_snake - 1); - } - - base_t = next_t; - } - - return snake; + float total_length = 0; + + const size_t polyline_size = polyline.size(); + for (size_t i = 1; i < polyline_size; ++i) { + total_length += std::sqrt(Vec2f(polyline[i] - polyline[i - 1]).squaredNorm()); + } + + const auto points_in_snake = static_cast(total_length / 20); + Snake snake; + snake.iterationsRemaining = iterations; + + int points_inserted = 0; + float base_t = 0; + float next_insert_t = 0; + for (size_t i = 1; i < polyline_size; ++i) { + const Vec2f base(polyline[i - 1]); + const Vec2f vec((polyline[i] - base)); + const auto next_t = static_cast(base_t + std::sqrt(vec.squaredNorm())); + + while (next_t >= next_insert_t) { + const float fraction = (next_insert_t - base_t) / (next_t - base_t); + SnakeNode node; + node.center = base + fraction * vec; + node.ribHalfLength = 4; + snake.nodes.push_back(node); + ++points_inserted; + next_insert_t = total_length * points_inserted / (points_in_snake - 1); + } + + base_t = next_t; + } + + return snake; } // TextLineRefiner::makeSnake void TextLineRefiner::calcFrenetFrames(std::vector& frenet_frames, const Snake& snake, const SnakeLength& snake_length, const Vec2f& unit_down_vec) { - const size_t num_nodes = snake.nodes.size(); - frenet_frames.resize(num_nodes); - - if (num_nodes == 0) { - return; - } else if (num_nodes == 1) { - frenet_frames[0].unitTangent = Vec2f(); - frenet_frames[0].unitDownNormal = Vec2f(); - - return; - } - - // First segment. - Vec2f first_segment(snake.nodes[1].center - snake.nodes[0].center); - const float first_segment_len = snake_length.arcLengthAt(1); - if (first_segment_len > std::numeric_limits::epsilon()) { - first_segment /= first_segment_len; - frenet_frames.front().unitTangent = first_segment; - } - // Segments between first and last, exclusive. - Vec2f prev_segment(first_segment); - for (size_t i = 1; i < num_nodes - 1; ++i) { - Vec2f next_segment(snake.nodes[i + 1].center - snake.nodes[i].center); - const float next_segment_len = snake_length.lengthFromTo(i, i + 1); - if (next_segment_len > std::numeric_limits::epsilon()) { - next_segment /= next_segment_len; - } - - Vec2f tangent_vec(0.5 * (prev_segment + next_segment)); - const auto len = static_cast(std::sqrt(tangent_vec.squaredNorm())); - if (len > std::numeric_limits::epsilon()) { - tangent_vec /= len; - } - frenet_frames[i].unitTangent = tangent_vec; - - prev_segment = next_segment; - } - - // Last segments. - Vec2f last_segment(snake.nodes[num_nodes - 1].center - snake.nodes[num_nodes - 2].center); - const float last_segment_len = snake_length.lengthFromTo(num_nodes - 2, num_nodes - 1); - if (last_segment_len > std::numeric_limits::epsilon()) { - last_segment /= last_segment_len; - frenet_frames.back().unitTangent = last_segment; - } - - // Calculate normals and make sure they point down. - for (FrenetFrame& frame : frenet_frames) { - frame.unitDownNormal = Vec2f(frame.unitTangent[1], -frame.unitTangent[0]); - if (frame.unitDownNormal.dot(unit_down_vec) < 0) { - frame.unitDownNormal = -frame.unitDownNormal; - } - } + const size_t num_nodes = snake.nodes.size(); + frenet_frames.resize(num_nodes); + + if (num_nodes == 0) { + return; + } else if (num_nodes == 1) { + frenet_frames[0].unitTangent = Vec2f(); + frenet_frames[0].unitDownNormal = Vec2f(); + + return; + } + + // First segment. + Vec2f first_segment(snake.nodes[1].center - snake.nodes[0].center); + const float first_segment_len = snake_length.arcLengthAt(1); + if (first_segment_len > std::numeric_limits::epsilon()) { + first_segment /= first_segment_len; + frenet_frames.front().unitTangent = first_segment; + } + // Segments between first and last, exclusive. + Vec2f prev_segment(first_segment); + for (size_t i = 1; i < num_nodes - 1; ++i) { + Vec2f next_segment(snake.nodes[i + 1].center - snake.nodes[i].center); + const float next_segment_len = snake_length.lengthFromTo(i, i + 1); + if (next_segment_len > std::numeric_limits::epsilon()) { + next_segment /= next_segment_len; + } + + Vec2f tangent_vec(0.5 * (prev_segment + next_segment)); + const auto len = static_cast(std::sqrt(tangent_vec.squaredNorm())); + if (len > std::numeric_limits::epsilon()) { + tangent_vec /= len; + } + frenet_frames[i].unitTangent = tangent_vec; + + prev_segment = next_segment; + } + + // Last segments. + Vec2f last_segment(snake.nodes[num_nodes - 1].center - snake.nodes[num_nodes - 2].center); + const float last_segment_len = snake_length.lengthFromTo(num_nodes - 2, num_nodes - 1); + if (last_segment_len > std::numeric_limits::epsilon()) { + last_segment /= last_segment_len; + frenet_frames.back().unitTangent = last_segment; + } + + // Calculate normals and make sure they point down. + for (FrenetFrame& frame : frenet_frames) { + frame.unitDownNormal = Vec2f(frame.unitTangent[1], -frame.unitTangent[0]); + if (frame.unitDownNormal.dot(unit_down_vec) < 0) { + frame.unitDownNormal = -frame.unitDownNormal; + } + } } // TextLineRefiner::calcFrenetFrames void TextLineRefiner::evolveSnake(Snake& snake, const Grid& gradient, const OnConvergence on_convergence) const { - float factor = 1.0f; - - while (snake.iterationsRemaining > 0) { - --snake.iterationsRemaining; - - Optimizer optimizer(snake, m_unitDownVec, factor); - bool changed = false; - changed |= optimizer.thicknessAdjustment(snake, gradient); - changed |= optimizer.tangentMovement(snake, gradient); - changed |= optimizer.normalMovement(snake, gradient); - - if (!changed) { - // qDebug() << "Converged. Iterations remaining = " << snake.iterationsRemaining; - if (on_convergence == ON_CONVERGENCE_STOP) { - break; - } else { - factor *= 0.5f; - } - } - } + float factor = 1.0f; + + while (snake.iterationsRemaining > 0) { + --snake.iterationsRemaining; + + Optimizer optimizer(snake, m_unitDownVec, factor); + bool changed = false; + changed |= optimizer.thicknessAdjustment(snake, gradient); + changed |= optimizer.tangentMovement(snake, gradient); + changed |= optimizer.normalMovement(snake, gradient); + + if (!changed) { + // qDebug() << "Converged. Iterations remaining = " << snake.iterationsRemaining; + if (on_convergence == ON_CONVERGENCE_STOP) { + break; + } else { + factor *= 0.5f; + } + } + } } QImage TextLineRefiner::visualizeGradient(const Grid& gradient) const { - const int width = gradient.width(); - const int height = gradient.height(); - const int gradient_stride = gradient.stride(); - // First let's find the maximum and minimum values. - float min_value = NumericTraits::max(); - float max_value = NumericTraits::min(); - - const float* gradient_line = gradient.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = gradient_line[x]; - if (value < min_value) { - min_value = value; - } else if (value > max_value) { - max_value = value; - } - } - gradient_line += gradient_stride; - } - - float scale = std::max(max_value, -min_value); - if (scale > std::numeric_limits::epsilon()) { - scale = 255.0f / scale; - } - - QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); - auto* overlay_line = (uint32_t*) overlay.bits(); - const int overlay_stride = overlay.bytesPerLine() / 4; - - gradient_line = gradient.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = gradient_line[x] * scale; - const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); - if (value > 0) { - // Red for positive gradients which indicate bottom edges. - overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); - } else { - overlay_line[x] = qRgba(0, 0, magnitude, magnitude); - } - } - gradient_line += gradient_stride; - overlay_line += overlay_stride; - } - - QImage canvas(m_image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.drawImage(0, 0, overlay); - - return canvas; + const int width = gradient.width(); + const int height = gradient.height(); + const int gradient_stride = gradient.stride(); + // First let's find the maximum and minimum values. + float min_value = NumericTraits::max(); + float max_value = NumericTraits::min(); + + const float* gradient_line = gradient.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = gradient_line[x]; + if (value < min_value) { + min_value = value; + } else if (value > max_value) { + max_value = value; + } + } + gradient_line += gradient_stride; + } + + float scale = std::max(max_value, -min_value); + if (scale > std::numeric_limits::epsilon()) { + scale = 255.0f / scale; + } + + QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); + auto* overlay_line = (uint32_t*) overlay.bits(); + const int overlay_stride = overlay.bytesPerLine() / 4; + + gradient_line = gradient.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = gradient_line[x] * scale; + const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); + if (value > 0) { + // Red for positive gradients which indicate bottom edges. + overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); + } else { + overlay_line[x] = qRgba(0, 0, magnitude, magnitude); + } + } + gradient_line += gradient_stride; + overlay_line += overlay_stride; + } + + QImage canvas(m_image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.drawImage(0, 0, overlay); + + return canvas; } // TextLineRefiner::visualizeGradient QImage TextLineRefiner::visualizeSnakes(const std::vector& snakes, const Grid* gradient) const { - QImage canvas; - if (gradient) { - canvas = visualizeGradient(*gradient); - } else { - canvas = m_image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); - } + QImage canvas; + if (gradient) { + canvas = visualizeGradient(*gradient); + } else { + canvas = m_image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + } - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); - QPen top_pen(QColor(0, 0, 255)); - top_pen.setWidthF(1.5); + QPen top_pen(QColor(0, 0, 255)); + top_pen.setWidthF(1.5); - QPen bottom_pen(QColor(255, 0, 0)); - bottom_pen.setWidthF(1.5); + QPen bottom_pen(QColor(255, 0, 0)); + bottom_pen.setWidthF(1.5); - QPen middle_pen(QColor(255, 0, 255)); - middle_pen.setWidth(static_cast(1.5)); + QPen middle_pen(QColor(255, 0, 255)); + middle_pen.setWidth(static_cast(1.5)); - QBrush knot_brush(QColor(255, 255, 0, 180)); - painter.setBrush(knot_brush); + QBrush knot_brush(QColor(255, 255, 0, 180)); + painter.setBrush(knot_brush); - QRectF knot_rect(0, 0, 7, 7); - std::vector frenet_frames; + QRectF knot_rect(0, 0, 7, 7); + std::vector frenet_frames; - for (const Snake& snake : snakes) { - const SnakeLength snake_length(snake); - calcFrenetFrames(frenet_frames, snake, snake_length, m_unitDownVec); - QVector top_polyline; - QVector middle_polyline; - QVector bottom_polyline; + for (const Snake& snake : snakes) { + const SnakeLength snake_length(snake); + calcFrenetFrames(frenet_frames, snake, snake_length, m_unitDownVec); + QVector top_polyline; + QVector middle_polyline; + QVector bottom_polyline; - const size_t num_nodes = snake.nodes.size(); - for (size_t i = 0; i < num_nodes; ++i) { - const QPointF mid(snake.nodes[i].center + QPointF(0.5, 0.5)); - const QPointF top(mid - snake.nodes[i].ribHalfLength * frenet_frames[i].unitDownNormal); - const QPointF bottom(mid + snake.nodes[i].ribHalfLength * frenet_frames[i].unitDownNormal); - top_polyline << top; - middle_polyline << mid; - bottom_polyline << bottom; - } + const size_t num_nodes = snake.nodes.size(); + for (size_t i = 0; i < num_nodes; ++i) { + const QPointF mid(snake.nodes[i].center + QPointF(0.5, 0.5)); + const QPointF top(mid - snake.nodes[i].ribHalfLength * frenet_frames[i].unitDownNormal); + const QPointF bottom(mid + snake.nodes[i].ribHalfLength * frenet_frames[i].unitDownNormal); + top_polyline << top; + middle_polyline << mid; + bottom_polyline << bottom; + } - // Draw polylines. - painter.setPen(top_pen); - painter.drawPolyline(top_polyline); + // Draw polylines. + painter.setPen(top_pen); + painter.drawPolyline(top_polyline); - painter.setPen(bottom_pen); - painter.drawPolyline(bottom_polyline); + painter.setPen(bottom_pen); + painter.drawPolyline(bottom_polyline); - painter.setPen(middle_pen); - painter.drawPolyline(middle_polyline); + painter.setPen(middle_pen); + painter.drawPolyline(middle_polyline); - // Draw knots. - painter.setPen(Qt::NoPen); - for (const QPointF& pt : middle_polyline) { - knot_rect.moveCenter(pt); - painter.drawEllipse(knot_rect); - } + // Draw knots. + painter.setPen(Qt::NoPen); + for (const QPointF& pt : middle_polyline) { + knot_rect.moveCenter(pt); + painter.drawEllipse(knot_rect); } + } - return canvas; + return canvas; } // TextLineRefiner::visualizeSnakes /*============================ SnakeLength =============================*/ TextLineRefiner::SnakeLength::SnakeLength(const Snake& snake) - : m_integralLength(snake.nodes.size()), m_totalLength(), m_rTotalLength(), m_avgSegmentLength() { - const size_t num_nodes = snake.nodes.size(); - float arc_length_accum = 0; - for (size_t i = 1; i < num_nodes; ++i) { - const Vec2f vec(snake.nodes[i].center - snake.nodes[i - 1].center); - arc_length_accum += std::sqrt(vec.squaredNorm()); - m_integralLength[i] = arc_length_accum; - } - m_totalLength = arc_length_accum; - if (m_totalLength > std::numeric_limits::epsilon()) { - m_rTotalLength = 1.0f / m_totalLength; - } - if (num_nodes > 1) { - m_avgSegmentLength = m_totalLength / (num_nodes - 1); - } + : m_integralLength(snake.nodes.size()), m_totalLength(), m_reciprocalTotalLength(), m_avgSegmentLength() { + const size_t num_nodes = snake.nodes.size(); + float arc_length_accum = 0; + for (size_t i = 1; i < num_nodes; ++i) { + const Vec2f vec(snake.nodes[i].center - snake.nodes[i - 1].center); + arc_length_accum += std::sqrt(vec.squaredNorm()); + m_integralLength[i] = arc_length_accum; + } + m_totalLength = arc_length_accum; + if (m_totalLength > std::numeric_limits::epsilon()) { + m_reciprocalTotalLength = 1.0f / m_totalLength; + } + if (num_nodes > 1) { + m_avgSegmentLength = m_totalLength / (num_nodes - 1); + } } /*=========================== Optimizer =============================*/ @@ -455,310 +445,310 @@ const float TextLineRefiner::Optimizer::m_topExternalWeight = 1.0f; const float TextLineRefiner::Optimizer::m_bottomExternalWeight = 1.0f; TextLineRefiner::Optimizer::Optimizer(const Snake& snake, const Vec2f& unit_down_vec, float factor) - : m_factor(factor), m_snakeLength(snake) { - calcFrenetFrames(m_frenetFrames, snake, m_snakeLength, unit_down_vec); + : m_factor(factor), m_snakeLength(snake) { + calcFrenetFrames(m_frenetFrames, snake, m_snakeLength, unit_down_vec); } bool TextLineRefiner::Optimizer::thicknessAdjustment(Snake& snake, const Grid& gradient) { - const size_t num_nodes = snake.nodes.size(); + const size_t num_nodes = snake.nodes.size(); - const float rib_adjustments[] = {0.0f * m_factor, 0.5f * m_factor, -0.5f * m_factor}; - enum { NUM_RIB_ADJUSTMENTS = sizeof(rib_adjustments) / sizeof(rib_adjustments[0]) }; - - int best_i = 0; - int best_j = 0; - float best_cost = NumericTraits::max(); - for (int i = 0; i < NUM_RIB_ADJUSTMENTS; ++i) { - const float head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[i]; - if (head_rib <= std::numeric_limits::epsilon()) { - continue; - } + const float rib_adjustments[] = {0.0f * m_factor, 0.5f * m_factor, -0.5f * m_factor}; + enum { NUM_RIB_ADJUSTMENTS = sizeof(rib_adjustments) / sizeof(rib_adjustments[0]) }; - for (int j = 0; j < NUM_RIB_ADJUSTMENTS; ++j) { - const float tail_rib = snake.nodes.back().ribHalfLength + rib_adjustments[j]; - if (tail_rib <= std::numeric_limits::epsilon()) { - continue; - } - - float cost = 0; - for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { - const float t = m_snakeLength.arcLengthFractionAt(node_idx); - const float rib = head_rib + t * (tail_rib - head_rib); - const Vec2f down_normal(m_frenetFrames[node_idx].unitDownNormal); - - SnakeNode node(snake.nodes[node_idx]); - node.ribHalfLength = rib; - cost += calcExternalEnergy(gradient, node, down_normal); - } - if (cost < best_cost) { - best_cost = cost; - best_i = i; - best_j = j; - } - } - } - const float head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[best_i]; - const float tail_rib = snake.nodes.back().ribHalfLength + rib_adjustments[best_j]; - for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { - const float t = m_snakeLength.arcLengthFractionAt(node_idx); - snake.nodes[node_idx].ribHalfLength = head_rib + t * (tail_rib - head_rib); - // Note that we need to recalculate inner ribs even if outer ribs - // didn't change, as movement of ribs in tangent direction affects - // interpolation. + int best_i = 0; + int best_j = 0; + float best_cost = NumericTraits::max(); + for (int i = 0; i < NUM_RIB_ADJUSTMENTS; ++i) { + const float head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[i]; + if (head_rib <= std::numeric_limits::epsilon()) { + continue; } - return rib_adjustments[best_i] != 0 || rib_adjustments[best_j] != 0; -} // TextLineRefiner::Optimizer::thicknessAdjustment - -bool TextLineRefiner::Optimizer::tangentMovement(Snake& snake, const Grid& gradient) { - const size_t num_nodes = snake.nodes.size(); - if (num_nodes < 3) { - return false; - } + for (int j = 0; j < NUM_RIB_ADJUSTMENTS; ++j) { + const float tail_rib = snake.nodes.back().ribHalfLength + rib_adjustments[j]; + if (tail_rib <= std::numeric_limits::epsilon()) { + continue; + } - const float tangent_movements[] = {0.0f * m_factor, 1.0f * m_factor, -1.0f * m_factor}; - enum { NUM_TANGENT_MOVEMENTS = sizeof(tangent_movements) / sizeof(tangent_movements[0]) }; - - std::vector paths; - std::vector new_paths; - std::vector step_storage; - // Note that we don't move the first and the last node in tangent direction. - paths.push_back(static_cast(step_storage.size())); - step_storage.emplace_back(); - step_storage.back().prevStepIdx = ~uint32_t(0); - step_storage.back().node = snake.nodes.front(); - step_storage.back().pathCost = 0; - - for (size_t node_idx = 1; node_idx < num_nodes - 1; ++node_idx) { - const Vec2f initial_pos(snake.nodes[node_idx].center); - const float rib = snake.nodes[node_idx].ribHalfLength; - const Vec2f unit_tangent(m_frenetFrames[node_idx].unitTangent); + float cost = 0; + for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { + const float t = m_snakeLength.arcLengthFractionAt(node_idx); + const float rib = head_rib + t * (tail_rib - head_rib); const Vec2f down_normal(m_frenetFrames[node_idx].unitDownNormal); - for (float tangent_movement : tangent_movements) { - Step step; - step.prevStepIdx = ~uint32_t(0); - step.node.center = initial_pos + tangent_movement * unit_tangent; - step.node.ribHalfLength = rib; - step.pathCost = NumericTraits::max(); - - float base_cost = calcExternalEnergy(gradient, step.node, down_normal); - - if (node_idx == num_nodes - 2) { - // Take into account the distance to the last node as well. - base_cost += calcElasticityEnergy(step.node, snake.nodes.back(), m_snakeLength.avgSegmentLength()); - } - - // Now find the best step for the previous node to combine with. - for (uint32_t prev_step_idx : paths) { - const Step& prev_step = step_storage[prev_step_idx]; - const float cost = base_cost + prev_step.pathCost - + calcElasticityEnergy(step.node, prev_step.node, m_snakeLength.avgSegmentLength()); - - if (cost < step.pathCost) { - step.pathCost = cost; - step.prevStepIdx = prev_step_idx; - } - } - assert(step.prevStepIdx != ~uint32_t(0)); - new_paths.push_back(static_cast(step_storage.size())); - step_storage.push_back(step); - } - assert(!new_paths.empty()); - paths.swap(new_paths); - new_paths.clear(); - } + SnakeNode node(snake.nodes[node_idx]); + node.ribHalfLength = rib; + cost += calcExternalEnergy(gradient, node, down_normal); + } + if (cost < best_cost) { + best_cost = cost; + best_i = i; + best_j = j; + } + } + } + const float head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[best_i]; + const float tail_rib = snake.nodes.back().ribHalfLength + rib_adjustments[best_j]; + for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { + const float t = m_snakeLength.arcLengthFractionAt(node_idx); + snake.nodes[node_idx].ribHalfLength = head_rib + t * (tail_rib - head_rib); + // Note that we need to recalculate inner ribs even if outer ribs + // didn't change, as movement of ribs in tangent direction affects + // interpolation. + } + + return rib_adjustments[best_i] != 0 || rib_adjustments[best_j] != 0; +} // TextLineRefiner::Optimizer::thicknessAdjustment - // Find the best overall path. - uint32_t best_path_idx = ~uint32_t(0); - float best_cost = NumericTraits::max(); - for (uint32_t last_step_idx : paths) { - const Step& step = step_storage[last_step_idx]; - if (step.pathCost < best_cost) { - best_cost = step.pathCost; - best_path_idx = last_step_idx; +bool TextLineRefiner::Optimizer::tangentMovement(Snake& snake, const Grid& gradient) { + const size_t num_nodes = snake.nodes.size(); + if (num_nodes < 3) { + return false; + } + + const float tangent_movements[] = {0.0f * m_factor, 1.0f * m_factor, -1.0f * m_factor}; + enum { NUM_TANGENT_MOVEMENTS = sizeof(tangent_movements) / sizeof(tangent_movements[0]) }; + + std::vector paths; + std::vector new_paths; + std::vector step_storage; + // Note that we don't move the first and the last node in tangent direction. + paths.push_back(static_cast(step_storage.size())); + step_storage.emplace_back(); + step_storage.back().prevStepIdx = ~uint32_t(0); + step_storage.back().node = snake.nodes.front(); + step_storage.back().pathCost = 0; + + for (size_t node_idx = 1; node_idx < num_nodes - 1; ++node_idx) { + const Vec2f initial_pos(snake.nodes[node_idx].center); + const float rib = snake.nodes[node_idx].ribHalfLength; + const Vec2f unit_tangent(m_frenetFrames[node_idx].unitTangent); + const Vec2f down_normal(m_frenetFrames[node_idx].unitDownNormal); + + for (float tangent_movement : tangent_movements) { + Step step; + step.prevStepIdx = ~uint32_t(0); + step.node.center = initial_pos + tangent_movement * unit_tangent; + step.node.ribHalfLength = rib; + step.pathCost = NumericTraits::max(); + + float base_cost = calcExternalEnergy(gradient, step.node, down_normal); + + if (node_idx == num_nodes - 2) { + // Take into account the distance to the last node as well. + base_cost += calcElasticityEnergy(step.node, snake.nodes.back(), m_snakeLength.avgSegmentLength()); + } + + // Now find the best step for the previous node to combine with. + for (uint32_t prev_step_idx : paths) { + const Step& prev_step = step_storage[prev_step_idx]; + const float cost = base_cost + prev_step.pathCost + + calcElasticityEnergy(step.node, prev_step.node, m_snakeLength.avgSegmentLength()); + + if (cost < step.pathCost) { + step.pathCost = cost; + step.prevStepIdx = prev_step_idx; } - } - // Having found the best path, convert it back to a snake. - float max_sqdist = 0; - uint32_t step_idx = best_path_idx; - for (auto node_idx = static_cast(num_nodes - 2); node_idx > 0; --node_idx) { - assert(step_idx != ~uint32_t(0)); - const Step& step = step_storage[step_idx]; - SnakeNode& node = snake.nodes[node_idx]; - - const float sqdist = (node.center - step.node.center).squaredNorm(); - max_sqdist = std::max(max_sqdist, sqdist); - - node = step.node; - step_idx = step.prevStepIdx; - } - - return max_sqdist > std::numeric_limits::epsilon(); + } + assert(step.prevStepIdx != ~uint32_t(0)); + new_paths.push_back(static_cast(step_storage.size())); + step_storage.push_back(step); + } + assert(!new_paths.empty()); + paths.swap(new_paths); + new_paths.clear(); + } + + // Find the best overall path. + uint32_t best_path_idx = ~uint32_t(0); + float best_cost = NumericTraits::max(); + for (uint32_t last_step_idx : paths) { + const Step& step = step_storage[last_step_idx]; + if (step.pathCost < best_cost) { + best_cost = step.pathCost; + best_path_idx = last_step_idx; + } + } + // Having found the best path, convert it back to a snake. + float max_sqdist = 0; + uint32_t step_idx = best_path_idx; + for (auto node_idx = static_cast(num_nodes - 2); node_idx > 0; --node_idx) { + assert(step_idx != ~uint32_t(0)); + const Step& step = step_storage[step_idx]; + SnakeNode& node = snake.nodes[node_idx]; + + const float sqdist = (node.center - step.node.center).squaredNorm(); + max_sqdist = std::max(max_sqdist, sqdist); + + node = step.node; + step_idx = step.prevStepIdx; + } + + return max_sqdist > std::numeric_limits::epsilon(); } // TextLineRefiner::Optimizer::tangentMovement bool TextLineRefiner::Optimizer::normalMovement(Snake& snake, const Grid& gradient) { - const size_t num_nodes = snake.nodes.size(); - if (num_nodes < 3) { - return false; - } + const size_t num_nodes = snake.nodes.size(); + if (num_nodes < 3) { + return false; + } + + const float normal_movements[] = {0.0f * m_factor, 1.0f * m_factor, -1.0f * m_factor}; + enum { NUM_NORMAL_MOVEMENTS = sizeof(normal_movements) / sizeof(normal_movements[0]) }; + + std::vector paths; + std::vector new_paths; + std::vector step_storage; + // The first two nodes pose a problem for us. These nodes don't have two predecessors, + // and therefore we can't take bending into the account. We could take the followers + // instead of the ancestors, but then this follower is going to move itself, making + // our calculations less accurate. The proper solution is to provide not N but N*N + // paths to the 3rd node, each path corresponding to a combination of movement of + // the first and the second node. That's the approach we are taking here. + for (float normal_movement : normal_movements) { + const auto prev_step_idx = static_cast(step_storage.size()); + { + // Movements of the first node. + const Vec2f down_normal(m_frenetFrames[0].unitDownNormal); + Step step; + step.node.center = snake.nodes[0].center + normal_movement * down_normal; + step.node.ribHalfLength = snake.nodes[0].ribHalfLength; + step.prevStepIdx = ~uint32_t(0); + step.pathCost = calcExternalEnergy(gradient, step.node, down_normal); + + step_storage.push_back(step); + } + + for (float j : normal_movements) { + // Movements of the second node. + const Vec2f down_normal(m_frenetFrames[1].unitDownNormal); + + Step step; + step.node.center = snake.nodes[1].center + j * down_normal; + step.node.ribHalfLength = snake.nodes[1].ribHalfLength; + step.prevStepIdx = prev_step_idx; + step.pathCost = step_storage[prev_step_idx].pathCost + calcExternalEnergy(gradient, step.node, down_normal); + + paths.push_back(static_cast(step_storage.size())); + step_storage.push_back(step); + } + } + + for (size_t node_idx = 2; node_idx < num_nodes; ++node_idx) { + const SnakeNode& node = snake.nodes[node_idx]; + const Vec2f down_normal(m_frenetFrames[node_idx].unitDownNormal); - const float normal_movements[] = {0.0f * m_factor, 1.0f * m_factor, -1.0f * m_factor}; - enum { NUM_NORMAL_MOVEMENTS = sizeof(normal_movements) / sizeof(normal_movements[0]) }; - - std::vector paths; - std::vector new_paths; - std::vector step_storage; - // The first two nodes pose a problem for us. These nodes don't have two predecessors, - // and therefore we can't take bending into the account. We could take the followers - // instead of the ancestors, but then this follower is going to move itself, making - // our calculations less accurate. The proper solution is to provide not N but N*N - // paths to the 3rd node, each path corresponding to a combination of movement of - // the first and the second node. That's the approach we are taking here. for (float normal_movement : normal_movements) { - const auto prev_step_idx = static_cast(step_storage.size()); - { - // Movements of the first node. - const Vec2f down_normal(m_frenetFrames[0].unitDownNormal); - Step step; - step.node.center = snake.nodes[0].center + normal_movement * down_normal; - step.node.ribHalfLength = snake.nodes[0].ribHalfLength; - step.prevStepIdx = ~uint32_t(0); - step.pathCost = calcExternalEnergy(gradient, step.node, down_normal); - - step_storage.push_back(step); - } - - for (float j : normal_movements) { - // Movements of the second node. - const Vec2f down_normal(m_frenetFrames[1].unitDownNormal); + Step step; + step.prevStepIdx = ~uint32_t(0); + step.node.center = node.center + normal_movement * down_normal; + step.node.ribHalfLength = node.ribHalfLength; + step.pathCost = NumericTraits::max(); - Step step; - step.node.center = snake.nodes[1].center + j * down_normal; - step.node.ribHalfLength = snake.nodes[1].ribHalfLength; - step.prevStepIdx = prev_step_idx; - step.pathCost = step_storage[prev_step_idx].pathCost + calcExternalEnergy(gradient, step.node, down_normal); + const float base_cost = calcExternalEnergy(gradient, step.node, down_normal); - paths.push_back(static_cast(step_storage.size())); - step_storage.push_back(step); - } - } + // Now find the best step for the previous node to combine with. + for (uint32_t prev_step_idx : paths) { + const Step& prev_step = step_storage[prev_step_idx]; + const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; - for (size_t node_idx = 2; node_idx < num_nodes; ++node_idx) { - const SnakeNode& node = snake.nodes[node_idx]; - const Vec2f down_normal(m_frenetFrames[node_idx].unitDownNormal); - - for (float normal_movement : normal_movements) { - Step step; - step.prevStepIdx = ~uint32_t(0); - step.node.center = node.center + normal_movement * down_normal; - step.node.ribHalfLength = node.ribHalfLength; - step.pathCost = NumericTraits::max(); - - const float base_cost = calcExternalEnergy(gradient, step.node, down_normal); - - // Now find the best step for the previous node to combine with. - for (uint32_t prev_step_idx : paths) { - const Step& prev_step = step_storage[prev_step_idx]; - const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; - - const float cost = base_cost + prev_step.pathCost - + calcBendingEnergy(step.node, prev_step.node, prev_prev_step.node); - - if (cost < step.pathCost) { - step.pathCost = cost; - step.prevStepIdx = prev_step_idx; - } - } - assert(step.prevStepIdx != ~uint32_t(0)); - new_paths.push_back(static_cast(step_storage.size())); - step_storage.push_back(step); - } - assert(!new_paths.empty()); - paths.swap(new_paths); - new_paths.clear(); - } + const float cost + = base_cost + prev_step.pathCost + calcBendingEnergy(step.node, prev_step.node, prev_prev_step.node); - // Find the best overall path. - uint32_t best_path_idx = ~uint32_t(0); - float best_cost = NumericTraits::max(); - for (uint32_t last_step_idx : paths) { - const Step& step = step_storage[last_step_idx]; - if (step.pathCost < best_cost) { - best_cost = step.pathCost; - best_path_idx = last_step_idx; + if (cost < step.pathCost) { + step.pathCost = cost; + step.prevStepIdx = prev_step_idx; } - } - // Having found the best path, convert it back to a snake. - float max_sqdist = 0; - uint32_t step_idx = best_path_idx; - for (auto node_idx = static_cast(num_nodes - 1); node_idx >= 0; --node_idx) { - assert(step_idx != ~uint32_t(0)); - const Step& step = step_storage[step_idx]; - SnakeNode& node = snake.nodes[node_idx]; - - const float sqdist = (node.center - step.node.center).squaredNorm(); - max_sqdist = std::max(max_sqdist, sqdist); - - node = step.node; - step_idx = step.prevStepIdx; - } - - return max_sqdist > std::numeric_limits::epsilon(); + } + assert(step.prevStepIdx != ~uint32_t(0)); + new_paths.push_back(static_cast(step_storage.size())); + step_storage.push_back(step); + } + assert(!new_paths.empty()); + paths.swap(new_paths); + new_paths.clear(); + } + + // Find the best overall path. + uint32_t best_path_idx = ~uint32_t(0); + float best_cost = NumericTraits::max(); + for (uint32_t last_step_idx : paths) { + const Step& step = step_storage[last_step_idx]; + if (step.pathCost < best_cost) { + best_cost = step.pathCost; + best_path_idx = last_step_idx; + } + } + // Having found the best path, convert it back to a snake. + float max_sqdist = 0; + uint32_t step_idx = best_path_idx; + for (auto node_idx = static_cast(num_nodes - 1); node_idx >= 0; --node_idx) { + assert(step_idx != ~uint32_t(0)); + const Step& step = step_storage[step_idx]; + SnakeNode& node = snake.nodes[node_idx]; + + const float sqdist = (node.center - step.node.center).squaredNorm(); + max_sqdist = std::max(max_sqdist, sqdist); + + node = step.node; + step_idx = step.prevStepIdx; + } + + return max_sqdist > std::numeric_limits::epsilon(); } // TextLineRefiner::Optimizer::normalMovement float TextLineRefiner::Optimizer::calcExternalEnergy(const Grid& gradient, const SnakeNode& node, const Vec2f down_normal) { - const Vec2f top(node.center - node.ribHalfLength * down_normal); - const Vec2f bottom(node.center + node.ribHalfLength * down_normal); - - const float top_grad = externalEnergyAt(gradient, top, 0.0f); - const float bottom_grad = externalEnergyAt(gradient, bottom, 0.0f); - - // Surprisingly, it turns out it's a bad idea to penalize for the opposite - // sign in the gradient. Sometimes a snake's edge has to move over the - // "wrong" gradient ridge before it gets into a good position. - // Those std::min and std::max prevent such penalties. - const float top_energy = m_topExternalWeight * std::min(top_grad, 0.0f); - const float bottom_energy = m_bottomExternalWeight * std::max(bottom_grad, 0.0f); - - // Positive gradient indicates the bottom edge and vice versa. - // Note that negative energies are fine with us - the less the better. - return top_energy - bottom_energy; + const Vec2f top(node.center - node.ribHalfLength * down_normal); + const Vec2f bottom(node.center + node.ribHalfLength * down_normal); + + const float top_grad = externalEnergyAt(gradient, top, 0.0f); + const float bottom_grad = externalEnergyAt(gradient, bottom, 0.0f); + + // Surprisingly, it turns out it's a bad idea to penalize for the opposite + // sign in the gradient. Sometimes a snake's edge has to move over the + // "wrong" gradient ridge before it gets into a good position. + // Those std::min and std::max prevent such penalties. + const float top_energy = m_topExternalWeight * std::min(top_grad, 0.0f); + const float bottom_energy = m_bottomExternalWeight * std::max(bottom_grad, 0.0f); + + // Positive gradient indicates the bottom edge and vice versa. + // Note that negative energies are fine with us - the less the better. + return top_energy - bottom_energy; } float TextLineRefiner::Optimizer::calcElasticityEnergy(const SnakeNode& node1, const SnakeNode& node2, float avg_dist) { - const Vec2f vec(node1.center - node2.center); - const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); + const Vec2f vec(node1.center - node2.center); + const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); - if (vec_len < 1.0f) { - return 1000.0f; // Penalty for moving too close to another node. - } + if (vec_len < 1.0f) { + return 1000.0f; // Penalty for moving too close to another node. + } - const auto dist_diff = std::fabs(avg_dist - vec_len); + const auto dist_diff = std::fabs(avg_dist - vec_len); - return m_elasticityWeight * (dist_diff / avg_dist); + return m_elasticityWeight * (dist_diff / avg_dist); } float TextLineRefiner::Optimizer::calcBendingEnergy(const SnakeNode& node, const SnakeNode& prev_node, const SnakeNode& prev_prev_node) { - const Vec2f vec(node.center - prev_node.center); - const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); + const Vec2f vec(node.center - prev_node.center); + const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); - if (vec_len < 1.0f) { - return 1000.0f; // Penalty for moving too close to another node. - } + if (vec_len < 1.0f) { + return 1000.0f; // Penalty for moving too close to another node. + } - const Vec2f prev_vec(prev_node.center - prev_prev_node.center); - const auto prev_vec_len = static_cast(std::sqrt(prev_vec.squaredNorm())); - if (prev_vec_len < 1.0f) { - return 1000.0f; // Penalty for moving too close to another node. - } + const Vec2f prev_vec(prev_node.center - prev_prev_node.center); + const auto prev_vec_len = static_cast(std::sqrt(prev_vec.squaredNorm())); + if (prev_vec_len < 1.0f) { + return 1000.0f; // Penalty for moving too close to another node. + } - const Vec2f bend_vec(vec / vec_len - prev_vec / prev_vec_len); + const Vec2f bend_vec(vec / vec_len - prev_vec / prev_vec_len); - return m_bendingWeight * bend_vec.squaredNorm(); + return m_bendingWeight * bend_vec.squaredNorm(); } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/TextLineRefiner.h b/dewarping/TextLineRefiner.h index bf8dfbd98..baf685f5d 100644 --- a/dewarping/TextLineRefiner.h +++ b/dewarping/TextLineRefiner.h @@ -19,15 +19,15 @@ #ifndef DEWARPING_TEXT_LINE_REFINER_H_ #define DEWARPING_TEXT_LINE_REFINER_H_ +#include +#include +#include +#include +#include +#include "Dpi.h" #include "Grid.h" #include "VecNT.h" -#include "Dpi.h" #include "imageproc/GrayImage.h" -#include -#include -#include -#include -#include class Dpi; class DebugImages; @@ -35,59 +35,58 @@ class QImage; namespace dewarping { class TextLineRefiner { -public: - TextLineRefiner(const imageproc::GrayImage& image, const Dpi& dpi, const Vec2f& unit_down_vector); + public: + TextLineRefiner(const imageproc::GrayImage& image, const Dpi& dpi, const Vec2f& unit_down_vector); - void refine(std::list>& polylines, int iterations, DebugImages* dbg) const; + void refine(std::list>& polylines, int iterations, DebugImages* dbg) const; -private: - enum OnConvergence { ON_CONVERGENCE_STOP, ON_CONVERGENCE_GO_FINER }; + private: + enum OnConvergence { ON_CONVERGENCE_STOP, ON_CONVERGENCE_GO_FINER }; - class SnakeLength; + class SnakeLength; - struct FrenetFrame; + struct FrenetFrame; - class Optimizer; + class Optimizer; - struct SnakeNode { - Vec2f center; - float ribHalfLength{}; - }; + struct SnakeNode { + Vec2f center; + float ribHalfLength{}; + }; - struct Snake { - std::vector nodes; - int iterationsRemaining; + struct Snake { + std::vector nodes; + int iterationsRemaining; - Snake() : iterationsRemaining(0) { - } - }; + Snake() : iterationsRemaining(0) {} + }; - struct Step { - SnakeNode node; - uint32_t prevStepIdx{0}; - float pathCost{0}; - }; + struct Step { + SnakeNode node; + uint32_t prevStepIdx{0}; + float pathCost{0}; + }; - void calcBlurredGradient(Grid& gradient, float h_sigma, float v_sigma) const; + void calcBlurredGradient(Grid& gradient, float h_sigma, float v_sigma) const; - static float externalEnergyAt(const Grid& gradient, const Vec2f& pos, float penalty_if_outside); + static float externalEnergyAt(const Grid& gradient, const Vec2f& pos, float penalty_if_outside); - static Snake makeSnake(const std::vector& polyline, int iterations); + static Snake makeSnake(const std::vector& polyline, int iterations); - static void calcFrenetFrames(std::vector& frenet_frames, - const Snake& snake, - const SnakeLength& snake_length, - const Vec2f& unit_down_vec); + static void calcFrenetFrames(std::vector& frenet_frames, + const Snake& snake, + const SnakeLength& snake_length, + const Vec2f& unit_down_vec); - void evolveSnake(Snake& snake, const Grid& gradient, OnConvergence on_convergence) const; + void evolveSnake(Snake& snake, const Grid& gradient, OnConvergence on_convergence) const; - QImage visualizeGradient(const Grid& gradient) const; + QImage visualizeGradient(const Grid& gradient) const; - QImage visualizeSnakes(const std::vector& snakes, const Grid* gradient = nullptr) const; + QImage visualizeSnakes(const std::vector& snakes, const Grid* gradient = nullptr) const; - imageproc::GrayImage m_image; - Dpi m_dpi; - Vec2f m_unitDownVec; + imageproc::GrayImage m_image; + Dpi m_dpi; + Vec2f m_unitDownVec; }; } // namespace dewarping #endif // ifndef DEWARPING_TEXT_LINE_REFINER_H_ diff --git a/dewarping/TextLineTracer.cpp b/dewarping/TextLineTracer.cpp index 6e29bf269..36ed840ae 100644 --- a/dewarping/TextLineTracer.cpp +++ b/dewarping/TextLineTracer.cpp @@ -17,36 +17,36 @@ */ #include "TextLineTracer.h" -#include "TextLineRefiner.h" +#include +#include +#include +#include +#include +#include +#include "DebugImages.h" #include "DetectVertContentBounds.h" -#include "TowardsLineTracer.h" +#include "DistortionModel.h" +#include "DistortionModelBuilder.h" #include "GridLineTraverser.h" -#include "TaskStatus.h" -#include "DebugImages.h" +#include "LineBoundedByRect.h" #include "NumericTraits.h" +#include "TaskStatus.h" +#include "TextLineRefiner.h" #include "ToLineProjector.h" -#include "LineBoundedByRect.h" -#include "DistortionModelBuilder.h" -#include "DistortionModel.h" -#include "imageproc/BinaryImage.h" +#include "TowardsLineTracer.h" #include "imageproc/Binarize.h" -#include "imageproc/Grayscale.h" -#include "imageproc/Scale.h" +#include "imageproc/BinaryImage.h" #include "imageproc/Constants.h" #include "imageproc/GaussBlur.h" -#include "imageproc/Sobel.h" +#include "imageproc/Grayscale.h" +#include "imageproc/LocalMinMaxGeneric.h" #include "imageproc/Morphology.h" #include "imageproc/RasterOp.h" #include "imageproc/RasterOpGeneric.h" -#include "imageproc/SeedFill.h" -#include "imageproc/LocalMinMaxGeneric.h" #include "imageproc/SEDM.h" -#include -#include -#include -#include -#include -#include +#include "imageproc/Scale.h" +#include "imageproc/SeedFill.h" +#include "imageproc/Sobel.h" using namespace imageproc; @@ -57,495 +57,492 @@ void TextLineTracer::trace(const GrayImage& input, DistortionModelBuilder& output, const TaskStatus& status, DebugImages* dbg) { - GrayImage downscaled(downscale(input, dpi)); - if (dbg) { - dbg->add(downscaled, "downscaled"); - } - - const int downscaled_width = downscaled.width(); - const int downscaled_height = downscaled.height(); - - const double downscale_x_factor = double(downscaled_width) / input.width(); - const double downscale_y_factor = double(downscaled_height) / input.height(); - QTransform to_orig; - to_orig.scale(1.0 / downscale_x_factor, 1.0 / downscale_y_factor); - - const QRect downscaled_content_rect(to_orig.inverted().mapRect(content_rect)); - const Dpi downscaled_dpi(qRound(dpi.horizontal() * downscale_x_factor), - qRound(dpi.vertical() * downscale_y_factor)); - - BinaryImage binarized(binarizeWolf(downscaled, QSize(31, 31))); - if (dbg) { - dbg->add(binarized, "binarized"); - } - // detectVertContentBounds() is sensitive to clutter and speckles, so let's try to remove it. - sanitizeBinaryImage(binarized, downscaled_content_rect); - if (dbg) { - dbg->add(binarized, "sanitized"); - } - - std::pair vert_bounds(detectVertContentBounds(binarized, dbg)); - if (dbg) { - dbg->add(visualizeVerticalBounds(binarized.toQImage(), vert_bounds), "vert_bounds"); - } - - std::list> polylines; - extractTextLines(polylines, stretchGrayRange(downscaled), vert_bounds, dbg); - if (dbg) { - dbg->add(visualizePolylines(downscaled, polylines), "traced"); - } - - filterShortCurves(polylines, vert_bounds.first, vert_bounds.second); - filterOutOfBoundsCurves(polylines, vert_bounds.first, vert_bounds.second); - if (dbg) { - dbg->add(visualizePolylines(downscaled, polylines), "filtered1"); - } - - Vec2f unit_down_vector(calcAvgUnitVector(vert_bounds)); - unit_down_vector /= std::sqrt(unit_down_vector.squaredNorm()); - if (unit_down_vector[1] < 0) { - unit_down_vector = -unit_down_vector; - } - TextLineRefiner refiner(downscaled, Dpi(200, 200), unit_down_vector); - refiner.refine(polylines, /*iterations=*/100, dbg); - - filterEdgyCurves(polylines); - if (dbg) { - dbg->add(visualizePolylines(downscaled, polylines), "filtered2"); - } - - - // Transform back to original coordinates and output. - - vert_bounds.first = to_orig.map(vert_bounds.first); - vert_bounds.second = to_orig.map(vert_bounds.second); - output.setVerticalBounds(vert_bounds.first, vert_bounds.second); - - for (std::vector& polyline : polylines) { - for (QPointF& pt : polyline) { - pt = to_orig.map(pt); - } - output.addHorizontalCurve(polyline); - } + GrayImage downscaled(downscale(input, dpi)); + if (dbg) { + dbg->add(downscaled, "downscaled"); + } + + const int downscaled_width = downscaled.width(); + const int downscaled_height = downscaled.height(); + + const double downscale_x_factor = double(downscaled_width) / input.width(); + const double downscale_y_factor = double(downscaled_height) / input.height(); + QTransform to_orig; + to_orig.scale(1.0 / downscale_x_factor, 1.0 / downscale_y_factor); + + const QRect downscaled_content_rect(to_orig.inverted().mapRect(content_rect)); + const Dpi downscaled_dpi(qRound(dpi.horizontal() * downscale_x_factor), qRound(dpi.vertical() * downscale_y_factor)); + + BinaryImage binarized(binarizeWolf(downscaled, QSize(31, 31))); + if (dbg) { + dbg->add(binarized, "binarized"); + } + // detectVertContentBounds() is sensitive to clutter and speckles, so let's try to remove it. + sanitizeBinaryImage(binarized, downscaled_content_rect); + if (dbg) { + dbg->add(binarized, "sanitized"); + } + + std::pair vert_bounds(detectVertContentBounds(binarized, dbg)); + if (dbg) { + dbg->add(visualizeVerticalBounds(binarized.toQImage(), vert_bounds), "vert_bounds"); + } + + std::list> polylines; + extractTextLines(polylines, stretchGrayRange(downscaled), vert_bounds, dbg); + if (dbg) { + dbg->add(visualizePolylines(downscaled, polylines), "traced"); + } + + filterShortCurves(polylines, vert_bounds.first, vert_bounds.second); + filterOutOfBoundsCurves(polylines, vert_bounds.first, vert_bounds.second); + if (dbg) { + dbg->add(visualizePolylines(downscaled, polylines), "filtered1"); + } + + Vec2f unit_down_vector(calcAvgUnitVector(vert_bounds)); + unit_down_vector /= std::sqrt(unit_down_vector.squaredNorm()); + if (unit_down_vector[1] < 0) { + unit_down_vector = -unit_down_vector; + } + TextLineRefiner refiner(downscaled, Dpi(200, 200), unit_down_vector); + refiner.refine(polylines, /*iterations=*/100, dbg); + + filterEdgyCurves(polylines); + if (dbg) { + dbg->add(visualizePolylines(downscaled, polylines), "filtered2"); + } + + + // Transform back to original coordinates and output. + + vert_bounds.first = to_orig.map(vert_bounds.first); + vert_bounds.second = to_orig.map(vert_bounds.second); + output.setVerticalBounds(vert_bounds.first, vert_bounds.second); + + for (std::vector& polyline : polylines) { + for (QPointF& pt : polyline) { + pt = to_orig.map(pt); + } + output.addHorizontalCurve(polyline); + } } // TextLineTracer::trace GrayImage TextLineTracer::downscale(const GrayImage& input, const Dpi& dpi) { - // Downscale to 200 DPI. - QSize downscaled_size(input.size()); - if ((dpi.horizontal() < 180) || (dpi.horizontal() > 220) || (dpi.vertical() < 180) || (dpi.vertical() > 220)) { - downscaled_size.setWidth(std::max(1, input.width() * 200 / dpi.horizontal())); - downscaled_size.setHeight(std::max(1, input.height() * 200 / dpi.vertical())); - } - - return scaleToGray(input, downscaled_size); + // Downscale to 200 DPI. + QSize downscaled_size(input.size()); + if ((dpi.horizontal() < 180) || (dpi.horizontal() > 220) || (dpi.vertical() < 180) || (dpi.vertical() > 220)) { + downscaled_size.setWidth(std::max(1, input.width() * 200 / dpi.horizontal())); + downscaled_size.setHeight(std::max(1, input.height() * 200 / dpi.vertical())); + } + + return scaleToGray(input, downscaled_size); } void TextLineTracer::sanitizeBinaryImage(BinaryImage& image, const QRect& content_rect) { - // Kill connected components touching the borders. - BinaryImage seed(image.size(), WHITE); - seed.fillExcept(seed.rect().adjusted(1, 1, -1, -1), BLACK); - - BinaryImage touching_border(seedFill(seed.release(), image, CONN8)); - rasterOp>(image, touching_border.release()); - - // Poor man's despeckle. - BinaryImage content_seeds(openBrick(image, QSize(2, 3), WHITE)); - rasterOp>(content_seeds, openBrick(image, QSize(3, 2), WHITE)); - image = seedFill(content_seeds.release(), image, CONN8); - // Clear margins. - image.fillExcept(content_rect, WHITE); + // Kill connected components touching the borders. + BinaryImage seed(image.size(), WHITE); + seed.fillExcept(seed.rect().adjusted(1, 1, -1, -1), BLACK); + + BinaryImage touching_border(seedFill(seed.release(), image, CONN8)); + rasterOp>(image, touching_border.release()); + + // Poor man's despeckle. + BinaryImage content_seeds(openBrick(image, QSize(2, 3), WHITE)); + rasterOp>(content_seeds, openBrick(image, QSize(3, 2), WHITE)); + image = seedFill(content_seeds.release(), image, CONN8); + // Clear margins. + image.fillExcept(content_rect, WHITE); } /** * Returns false if the curve contains both significant convexities and concavities. */ bool TextLineTracer::isCurvatureConsistent(const std::vector& polyline) { - const size_t num_nodes = polyline.size(); - - if (num_nodes <= 1) { - // Even though we can't say anything about curvature in this case, - // we don't like such gegenerate curves, so we reject them. - return false; - } else if (num_nodes == 2) { - // These are fine. - return true; - } - // Threshold angle between a polyline segment and a normal to the previous one. - const auto cos_threshold = static_cast(std::cos((90.0f - 6.0f) * constants::DEG2RAD)); - const float cos_sq_threshold = cos_threshold * cos_threshold; - bool significant_positive = false; - bool significant_negative = false; - - Vec2f prev_normal(polyline[1] - polyline[0]); - std::swap(prev_normal[0], prev_normal[1]); - prev_normal[0] = -prev_normal[0]; - float prev_normal_sqlen = prev_normal.squaredNorm(); - - for (size_t i = 1; i < num_nodes - 1; ++i) { - const Vec2f next_segment(polyline[i + 1] - polyline[i]); - const float next_segment_sqlen = next_segment.squaredNorm(); - - float cos_sq = 0; - const float sqlen_mult = prev_normal_sqlen * next_segment_sqlen; - if (sqlen_mult > std::numeric_limits::epsilon()) { - const float dot = prev_normal.dot(next_segment); - cos_sq = std::fabs(dot) * dot / sqlen_mult; - } - - if (std::fabs(cos_sq) >= cos_sq_threshold) { - if (cos_sq > 0) { - significant_positive = true; - } else { - significant_negative = true; - } - } - - prev_normal[0] = -next_segment[1]; - prev_normal[1] = next_segment[0]; - prev_normal_sqlen = next_segment_sqlen; - } - - return !(significant_positive && significant_negative); + const size_t num_nodes = polyline.size(); + + if (num_nodes <= 1) { + // Even though we can't say anything about curvature in this case, + // we don't like such gegenerate curves, so we reject them. + return false; + } else if (num_nodes == 2) { + // These are fine. + return true; + } + // Threshold angle between a polyline segment and a normal to the previous one. + const auto cos_threshold = static_cast(std::cos((90.0f - 6.0f) * constants::DEG2RAD)); + const float cos_sq_threshold = cos_threshold * cos_threshold; + bool significant_positive = false; + bool significant_negative = false; + + Vec2f prev_normal(polyline[1] - polyline[0]); + std::swap(prev_normal[0], prev_normal[1]); + prev_normal[0] = -prev_normal[0]; + float prev_normal_sqlen = prev_normal.squaredNorm(); + + for (size_t i = 1; i < num_nodes - 1; ++i) { + const Vec2f next_segment(polyline[i + 1] - polyline[i]); + const float next_segment_sqlen = next_segment.squaredNorm(); + + float cos_sq = 0; + const float sqlen_mult = prev_normal_sqlen * next_segment_sqlen; + if (sqlen_mult > std::numeric_limits::epsilon()) { + const float dot = prev_normal.dot(next_segment); + cos_sq = std::fabs(dot) * dot / sqlen_mult; + } + + if (std::fabs(cos_sq) >= cos_sq_threshold) { + if (cos_sq > 0) { + significant_positive = true; + } else { + significant_negative = true; + } + } + + prev_normal[0] = -next_segment[1]; + prev_normal[1] = next_segment[0]; + prev_normal_sqlen = next_segment_sqlen; + } + + return !(significant_positive && significant_negative); } // TextLineTracer::isCurvatureConsistent bool TextLineTracer::isInsideBounds(const QPointF& pt, const QLineF& left_bound, const QLineF& right_bound) { - QPointF left_normal_inside(left_bound.normalVector().p2() - left_bound.p1()); - if (left_normal_inside.x() < 0) { - left_normal_inside = -left_normal_inside; - } - const QPointF left_vec(pt - left_bound.p1()); - if (left_normal_inside.x() * left_vec.x() + left_normal_inside.y() * left_vec.y() < 0) { - return false; - } - - QPointF right_normal_inside(right_bound.normalVector().p2() - right_bound.p1()); - if (right_normal_inside.x() > 0) { - right_normal_inside = -right_normal_inside; - } - const QPointF right_vec(pt - right_bound.p1()); - if (right_normal_inside.x() * right_vec.x() + right_normal_inside.y() * right_vec.y() < 0) { - return false; - } - - return true; + QPointF left_normal_inside(left_bound.normalVector().p2() - left_bound.p1()); + if (left_normal_inside.x() < 0) { + left_normal_inside = -left_normal_inside; + } + const QPointF left_vec(pt - left_bound.p1()); + if (left_normal_inside.x() * left_vec.x() + left_normal_inside.y() * left_vec.y() < 0) { + return false; + } + + QPointF right_normal_inside(right_bound.normalVector().p2() - right_bound.p1()); + if (right_normal_inside.x() > 0) { + right_normal_inside = -right_normal_inside; + } + const QPointF right_vec(pt - right_bound.p1()); + if (right_normal_inside.x() * right_vec.x() + right_normal_inside.y() * right_vec.y() < 0) { + return false; + } + + return true; } void TextLineTracer::filterShortCurves(std::list>& polylines, const QLineF& left_bound, const QLineF& right_bound) { - const ToLineProjector proj1(left_bound); - const ToLineProjector proj2(right_bound); - - auto it(polylines.begin()); - const auto end(polylines.end()); - while (it != end) { - assert(!it->empty()); - const QPointF front(it->front()); - const QPointF back(it->back()); - const double front_proj_len = proj1.projectionDist(front); - const double back_proj_len = proj2.projectionDist(back); - const double chord_len = QLineF(front, back).length(); - - if (front_proj_len + back_proj_len > 0.3 * chord_len) { - polylines.erase(it++); - } else { - ++it; - } + const ToLineProjector proj1(left_bound); + const ToLineProjector proj2(right_bound); + + auto it(polylines.begin()); + const auto end(polylines.end()); + while (it != end) { + assert(!it->empty()); + const QPointF front(it->front()); + const QPointF back(it->back()); + const double front_proj_len = proj1.projectionDist(front); + const double back_proj_len = proj2.projectionDist(back); + const double chord_len = QLineF(front, back).length(); + + if (front_proj_len + back_proj_len > 0.3 * chord_len) { + polylines.erase(it++); + } else { + ++it; } + } } void TextLineTracer::filterOutOfBoundsCurves(std::list>& polylines, const QLineF& left_bound, const QLineF& right_bound) { - auto it(polylines.begin()); - const auto end(polylines.end()); - while (it != end) { - if (!isInsideBounds(it->front(), left_bound, right_bound) - && !isInsideBounds(it->back(), left_bound, right_bound)) { - polylines.erase(it++); - } else { - ++it; - } + auto it(polylines.begin()); + const auto end(polylines.end()); + while (it != end) { + if (!isInsideBounds(it->front(), left_bound, right_bound) && !isInsideBounds(it->back(), left_bound, right_bound)) { + polylines.erase(it++); + } else { + ++it; } + } } void TextLineTracer::filterEdgyCurves(std::list>& polylines) { - auto it(polylines.begin()); - const auto end(polylines.end()); - while (it != end) { - if (!isCurvatureConsistent(*it)) { - polylines.erase(it++); - } else { - ++it; - } + auto it(polylines.begin()); + const auto end(polylines.end()); + while (it != end) { + if (!isCurvatureConsistent(*it)) { + polylines.erase(it++); + } else { + ++it; } + } } void TextLineTracer::extractTextLines(std::list>& out, const imageproc::GrayImage& image, const std::pair& bounds, DebugImages* dbg) { - using namespace boost::lambda; - - const int width = image.width(); - const int height = image.height(); - const QSize size(image.size()); - const Vec2f direction(calcAvgUnitVector(bounds)); - Grid main_grid(image.width(), image.height(), 0); - Grid aux_grid(image.width(), image.height(), 0); - - const float downscale = 1.0f / (255.0f * 8.0f); - horizontalSobel(width, height, image.data(), image.stride(), _1 * downscale, aux_grid.data(), - aux_grid.stride(), _1 = _2, _1, main_grid.data(), main_grid.stride(), _1 = _2); - verticalSobel(width, height, image.data(), image.stride(), _1 * downscale, aux_grid.data(), - aux_grid.stride(), _1 = _2, _1, main_grid.data(), main_grid.stride(), - _1 = _1 * direction[0] + _2 * direction[1]); - if (dbg) { - dbg->add(visualizeGradient(image, main_grid), "first_dir_deriv"); - } - - gaussBlurGeneric(size, 6.0f, 6.0f, main_grid.data(), main_grid.stride(), _1, main_grid.data(), main_grid.stride(), - _1 = _2); - if (dbg) { - dbg->add(visualizeGradient(image, main_grid), "first_dir_deriv_blurred"); - } - - horizontalSobel(width, height, main_grid.data(), main_grid.stride(), _1, aux_grid.data(), aux_grid.stride(), - _1 = _2, _1, aux_grid.data(), aux_grid.stride(), _1 = _2); - verticalSobel(width, height, main_grid.data(), main_grid.stride(), _1, main_grid.data(), main_grid.stride(), - _1 = _2, _1, main_grid.data(), main_grid.stride(), _1 = _2); - rasterOpGeneric(aux_grid.data(), aux_grid.stride(), size, main_grid.data(), main_grid.stride(), - _2 = _1 * direction[0] + _2 * direction[1]); - if (dbg) { - dbg->add(visualizeGradient(image, main_grid), "second_dir_deriv"); - } - - float max = 0; - rasterOpGeneric(main_grid.data(), main_grid.stride(), size, if_then(_1 > var(max), var(max) = _1)); - const float threshold = max * 15.0f / 255.0f; - - BinaryImage initial_binarization(image.size()); - rasterOpGeneric(initial_binarization, main_grid.data(), main_grid.stride(), - if_then_else(_2 > threshold, _1 = uint32_t(1), _1 = uint32_t(0))); - if (dbg) { - dbg->add(initial_binarization, "initial_binarization"); - } - - rasterOpGeneric(main_grid.data(), main_grid.stride(), size, aux_grid.data(), aux_grid.stride(), - _2 = bind((float (*)(float)) & std::fabs, _1)); - if (dbg) { - dbg->add(visualizeGradient(image, aux_grid), "abs"); - } - - gaussBlurGeneric(size, 12.0f, 12.0f, aux_grid.data(), aux_grid.stride(), _1, aux_grid.data(), aux_grid.stride(), - _1 = _2); - if (dbg) { - dbg->add(visualizeGradient(image, aux_grid), "blurred"); - } - - rasterOpGeneric(main_grid.data(), main_grid.stride(), size, aux_grid.data(), aux_grid.stride(), - _2 += _1 - bind((float (*)(float)) & std::fabs, _1)); - if (dbg) { - dbg->add(visualizeGradient(image, aux_grid), "+= diff"); - } - - BinaryImage post_binarization(image.size()); - rasterOpGeneric(post_binarization, aux_grid.data(), aux_grid.stride(), - if_then_else(_2 > threshold, _1 = uint32_t(1), _1 = uint32_t(0))); - if (dbg) { - dbg->add(post_binarization, "post_binarization"); - } - - BinaryImage obstacles(image.size()); - rasterOpGeneric(obstacles, aux_grid.data(), aux_grid.stride(), - if_then_else(_2 < -threshold, _1 = uint32_t(1), _1 = uint32_t(0))); - if (dbg) { - dbg->add(obstacles, "obstacles"); - } - - Grid().swap(aux_grid); // Save memory. - initial_binarization = closeWithObstacles(initial_binarization, obstacles, QSize(21, 21)); - if (dbg) { - dbg->add(initial_binarization, "initial_closed"); - } - - obstacles.release(); // Save memory. - rasterOp>(post_binarization, initial_binarization); - if (dbg) { - dbg->add(post_binarization, "post &&= initial"); - } - - initial_binarization.release(); // Save memory. - const SEDM sedm(post_binarization); - - std::vector seeds; - QLineF mid_line(calcMidLine(bounds.first, bounds.second)); - findMidLineSeeds(sedm, mid_line, seeds); - if (dbg) { - dbg->add(visualizeMidLineSeeds(image, post_binarization, bounds, mid_line, seeds), "seeds"); - } - - post_binarization.release(); // Save memory. - for (const QPoint seed : seeds) { - std::vector polyline; - - { - TowardsLineTracer tracer(&sedm, &main_grid, bounds.first, seed); - while (const QPoint* pt = tracer.trace(10.0f)) { - polyline.emplace_back(*pt); - } - std::reverse(polyline.begin(), polyline.end()); - } - - polyline.emplace_back(seed); - - { - TowardsLineTracer tracer(&sedm, &main_grid, bounds.second, seed); - while (const QPoint* pt = tracer.trace(10.0f)) { - polyline.emplace_back(*pt); - } - } - - out.emplace_back(); - out.back().swap(polyline); - } + using namespace boost::lambda; + + const int width = image.width(); + const int height = image.height(); + const QSize size(image.size()); + const Vec2f direction(calcAvgUnitVector(bounds)); + Grid main_grid(image.width(), image.height(), 0); + Grid aux_grid(image.width(), image.height(), 0); + + const float downscale = 1.0f / (255.0f * 8.0f); + horizontalSobel(width, height, image.data(), image.stride(), _1 * downscale, aux_grid.data(), + aux_grid.stride(), _1 = _2, _1, main_grid.data(), main_grid.stride(), _1 = _2); + verticalSobel(width, height, image.data(), image.stride(), _1 * downscale, aux_grid.data(), aux_grid.stride(), + _1 = _2, _1, main_grid.data(), main_grid.stride(), _1 = _1 * direction[0] + _2 * direction[1]); + if (dbg) { + dbg->add(visualizeGradient(image, main_grid), "first_dir_deriv"); + } + + gaussBlurGeneric(size, 6.0f, 6.0f, main_grid.data(), main_grid.stride(), _1, main_grid.data(), main_grid.stride(), + _1 = _2); + if (dbg) { + dbg->add(visualizeGradient(image, main_grid), "first_dir_deriv_blurred"); + } + + horizontalSobel(width, height, main_grid.data(), main_grid.stride(), _1, aux_grid.data(), aux_grid.stride(), + _1 = _2, _1, aux_grid.data(), aux_grid.stride(), _1 = _2); + verticalSobel(width, height, main_grid.data(), main_grid.stride(), _1, main_grid.data(), main_grid.stride(), + _1 = _2, _1, main_grid.data(), main_grid.stride(), _1 = _2); + rasterOpGeneric(aux_grid.data(), aux_grid.stride(), size, main_grid.data(), main_grid.stride(), + _2 = _1 * direction[0] + _2 * direction[1]); + if (dbg) { + dbg->add(visualizeGradient(image, main_grid), "second_dir_deriv"); + } + + float max = 0; + rasterOpGeneric(main_grid.data(), main_grid.stride(), size, if_then(_1 > var(max), var(max) = _1)); + const float threshold = max * 15.0f / 255.0f; + + BinaryImage initial_binarization(image.size()); + rasterOpGeneric(initial_binarization, main_grid.data(), main_grid.stride(), + if_then_else(_2 > threshold, _1 = uint32_t(1), _1 = uint32_t(0))); + if (dbg) { + dbg->add(initial_binarization, "initial_binarization"); + } + + rasterOpGeneric(main_grid.data(), main_grid.stride(), size, aux_grid.data(), aux_grid.stride(), + _2 = bind((float (*)(float)) & std::fabs, _1)); + if (dbg) { + dbg->add(visualizeGradient(image, aux_grid), "abs"); + } + + gaussBlurGeneric(size, 12.0f, 12.0f, aux_grid.data(), aux_grid.stride(), _1, aux_grid.data(), aux_grid.stride(), + _1 = _2); + if (dbg) { + dbg->add(visualizeGradient(image, aux_grid), "blurred"); + } + + rasterOpGeneric(main_grid.data(), main_grid.stride(), size, aux_grid.data(), aux_grid.stride(), + _2 += _1 - bind((float (*)(float)) & std::fabs, _1)); + if (dbg) { + dbg->add(visualizeGradient(image, aux_grid), "+= diff"); + } + + BinaryImage post_binarization(image.size()); + rasterOpGeneric(post_binarization, aux_grid.data(), aux_grid.stride(), + if_then_else(_2 > threshold, _1 = uint32_t(1), _1 = uint32_t(0))); + if (dbg) { + dbg->add(post_binarization, "post_binarization"); + } + + BinaryImage obstacles(image.size()); + rasterOpGeneric(obstacles, aux_grid.data(), aux_grid.stride(), + if_then_else(_2 < -threshold, _1 = uint32_t(1), _1 = uint32_t(0))); + if (dbg) { + dbg->add(obstacles, "obstacles"); + } + + Grid().swap(aux_grid); // Save memory. + initial_binarization = closeWithObstacles(initial_binarization, obstacles, QSize(21, 21)); + if (dbg) { + dbg->add(initial_binarization, "initial_closed"); + } + + obstacles.release(); // Save memory. + rasterOp>(post_binarization, initial_binarization); + if (dbg) { + dbg->add(post_binarization, "post &&= initial"); + } + + initial_binarization.release(); // Save memory. + const SEDM sedm(post_binarization); + + std::vector seeds; + QLineF mid_line(calcMidLine(bounds.first, bounds.second)); + findMidLineSeeds(sedm, mid_line, seeds); + if (dbg) { + dbg->add(visualizeMidLineSeeds(image, post_binarization, bounds, mid_line, seeds), "seeds"); + } + + post_binarization.release(); // Save memory. + for (const QPoint seed : seeds) { + std::vector polyline; + + { + TowardsLineTracer tracer(&sedm, &main_grid, bounds.first, seed); + while (const QPoint* pt = tracer.trace(10.0f)) { + polyline.emplace_back(*pt); + } + std::reverse(polyline.begin(), polyline.end()); + } + + polyline.emplace_back(seed); + + { + TowardsLineTracer tracer(&sedm, &main_grid, bounds.second, seed); + while (const QPoint* pt = tracer.trace(10.0f)) { + polyline.emplace_back(*pt); + } + } + + out.emplace_back(); + out.back().swap(polyline); + } } // TextLineTracer::extractTextLines Vec2f TextLineTracer::calcAvgUnitVector(const std::pair& bounds) { - Vec2f v1(bounds.first.p2() - bounds.first.p1()); - v1 /= std::sqrt(v1.squaredNorm()); + Vec2f v1(bounds.first.p2() - bounds.first.p1()); + v1 /= std::sqrt(v1.squaredNorm()); - Vec2f v2(bounds.second.p2() - bounds.second.p1()); - v2 /= std::sqrt(v2.squaredNorm()); + Vec2f v2(bounds.second.p2() - bounds.second.p1()); + v2 /= std::sqrt(v2.squaredNorm()); - Vec2f v3(v1 + v2); - v3 /= std::sqrt(v3.squaredNorm()); + Vec2f v3(v1 + v2); + v3 /= std::sqrt(v3.squaredNorm()); - return v3; + return v3; } BinaryImage TextLineTracer::closeWithObstacles(const BinaryImage& image, const BinaryImage& obstacles, const QSize& brick) { - BinaryImage mask(closeBrick(image, brick)); - rasterOp>(mask, obstacles); + BinaryImage mask(closeBrick(image, brick)); + rasterOp>(mask, obstacles); - return seedFill(image, mask, CONN4); + return seedFill(image, mask, CONN4); } void TextLineTracer::findMidLineSeeds(const SEDM& sedm, QLineF mid_line, std::vector& seeds) { - lineBoundedByRect(mid_line, QRect(QPoint(0, 0), sedm.size()).adjusted(0, 0, -1, -1)); - - const uint32_t* sedm_data = sedm.data(); - const int sedm_stride = sedm.stride(); - - QPoint prev_pt; - int32_t prev_level = 0; - int dir = 1; // Distance growing. - GridLineTraverser traverser(mid_line); - while (traverser.hasNext()) { - const QPoint pt(traverser.next()); - const int32_t level = sedm_data[pt.y() * sedm_stride + pt.x()]; - if ((level - prev_level) * dir < 0) { - // Direction changed. - if (dir > 0) { - seeds.push_back(prev_pt); - } - dir *= -1; - } - - prev_pt = pt; - prev_level = level; - } + lineBoundedByRect(mid_line, QRect(QPoint(0, 0), sedm.size()).adjusted(0, 0, -1, -1)); + + const uint32_t* sedm_data = sedm.data(); + const int sedm_stride = sedm.stride(); + + QPoint prev_pt; + int32_t prev_level = 0; + int dir = 1; // Distance growing. + GridLineTraverser traverser(mid_line); + while (traverser.hasNext()) { + const QPoint pt(traverser.next()); + const int32_t level = sedm_data[pt.y() * sedm_stride + pt.x()]; + if ((level - prev_level) * dir < 0) { + // Direction changed. + if (dir > 0) { + seeds.push_back(prev_pt); + } + dir *= -1; + } + + prev_pt = pt; + prev_level = level; + } } QLineF TextLineTracer::calcMidLine(const QLineF& line1, const QLineF& line2) { - QPointF intersection; - if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { - // Lines are parallel. - const QPointF p1(line2.p1()); - const QPointF p2(ToLineProjector(line1).projectionPoint(p1)); - const QPointF origin(0.5 * (p1 + p2)); - const QPointF vector(line2.p2() - line2.p1()); - - return QLineF(origin, origin + vector); - } else { - // Lines do intersect. - Vec2d v1(line1.p2() - line1.p1()); - Vec2d v2(line2.p2() - line2.p1()); - v1 /= std::sqrt(v1.squaredNorm()); - v2 /= std::sqrt(v2.squaredNorm()); + QPointF intersection; + if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { + // Lines are parallel. + const QPointF p1(line2.p1()); + const QPointF p2(ToLineProjector(line1).projectionPoint(p1)); + const QPointF origin(0.5 * (p1 + p2)); + const QPointF vector(line2.p2() - line2.p1()); + + return QLineF(origin, origin + vector); + } else { + // Lines do intersect. + Vec2d v1(line1.p2() - line1.p1()); + Vec2d v2(line2.p2() - line2.p1()); + v1 /= std::sqrt(v1.squaredNorm()); + v2 /= std::sqrt(v2.squaredNorm()); - return QLineF(intersection, intersection + 0.5 * (v1 + v2)); - } + return QLineF(intersection, intersection + 0.5 * (v1 + v2)); + } } QImage TextLineTracer::visualizeVerticalBounds(const QImage& background, const std::pair& bounds) { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - QPen pen(Qt::blue); - pen.setWidthF(2.0); - painter.setPen(pen); - painter.setOpacity(0.7); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(Qt::blue); + pen.setWidthF(2.0); + painter.setPen(pen); + painter.setOpacity(0.7); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); - return canvas; + return canvas; } QImage TextLineTracer::visualizeGradient(const QImage& background, const Grid& grad) { - const int width = grad.width(); - const int height = grad.height(); - const int grad_stride = grad.stride(); - // First let's find the maximum and minimum values. - float min_value = NumericTraits::max(); - float max_value = NumericTraits::min(); - - const float* grad_line = grad.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grad_line[x]; - if (value < min_value) { - min_value = value; - } else if (value > max_value) { - max_value = value; - } - } - grad_line += grad_stride; - } - - float scale = std::max(max_value, -min_value); - if (scale > std::numeric_limits::epsilon()) { - scale = 255.0f / scale; - } - - QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); - auto* overlay_line = (uint32_t*) overlay.bits(); - const int overlay_stride = overlay.bytesPerLine() / 4; - - grad_line = grad.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grad_line[x] * scale; - const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); - if (value < 0) { - overlay_line[x] = qRgba(0, 0, magnitude, magnitude); - } else { - overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); - } - } - grad_line += grad_stride; - overlay_line += overlay_stride; - } - - QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.drawImage(0, 0, overlay); - - return canvas; + const int width = grad.width(); + const int height = grad.height(); + const int grad_stride = grad.stride(); + // First let's find the maximum and minimum values. + float min_value = NumericTraits::max(); + float max_value = NumericTraits::min(); + + const float* grad_line = grad.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grad_line[x]; + if (value < min_value) { + min_value = value; + } else if (value > max_value) { + max_value = value; + } + } + grad_line += grad_stride; + } + + float scale = std::max(max_value, -min_value); + if (scale > std::numeric_limits::epsilon()) { + scale = 255.0f / scale; + } + + QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); + auto* overlay_line = (uint32_t*) overlay.bits(); + const int overlay_stride = overlay.bytesPerLine() / 4; + + grad_line = grad.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grad_line[x] * scale; + const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); + if (value < 0) { + overlay_line[x] = qRgba(0, 0, magnitude, magnitude); + } else { + overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); + } + } + grad_line += grad_stride; + overlay_line += overlay_stride; + } + + QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.drawImage(0, 0, overlay); + + return canvas; } // TextLineTracer::visualizeGradient QImage TextLineTracer::visualizeMidLineSeeds(const QImage& background, @@ -553,58 +550,58 @@ QImage TextLineTracer::visualizeMidLineSeeds(const QImage& background, std::pair bounds, QLineF mid_line, const std::vector& seeds) { - QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - - painter.drawImage(QPoint(0, 0), overlay.toAlphaMask(QColor(0xff, 0x00, 0x00, 120))); - - lineBoundedByRect(bounds.first, background.rect()); - lineBoundedByRect(bounds.second, background.rect()); - lineBoundedByRect(mid_line, background.rect()); - - QPen pen(QColor(0x00, 0x00, 0xff, 180)); - pen.setWidthF(5.0); - painter.setPen(pen); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); - - pen.setColor(QColor(0x00, 0xff, 0x00, 180)); - painter.setPen(pen); - painter.drawLine(mid_line); - - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0x2d, 0x00, 0x6d, 255)); - QRectF rect(0, 0, 7, 7); - for (const QPoint pt : seeds) { - rect.moveCenter(pt + QPointF(0.5, 0.5)); - painter.drawEllipse(rect); - } - - return canvas; + QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + + painter.drawImage(QPoint(0, 0), overlay.toAlphaMask(QColor(0xff, 0x00, 0x00, 120))); + + lineBoundedByRect(bounds.first, background.rect()); + lineBoundedByRect(bounds.second, background.rect()); + lineBoundedByRect(mid_line, background.rect()); + + QPen pen(QColor(0x00, 0x00, 0xff, 180)); + pen.setWidthF(5.0); + painter.setPen(pen); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); + + pen.setColor(QColor(0x00, 0xff, 0x00, 180)); + painter.setPen(pen); + painter.drawLine(mid_line); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0x2d, 0x00, 0x6d, 255)); + QRectF rect(0, 0, 7, 7); + for (const QPoint pt : seeds) { + rect.moveCenter(pt + QPointF(0.5, 0.5)); + painter.drawEllipse(rect); + } + + return canvas; } // TextLineTracer::visualizeMidLineSeeds QImage TextLineTracer::visualizePolylines(const QImage& background, const std::list>& polylines, const std::pair* vert_bounds) { - QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - QPen pen(Qt::blue); - pen.setWidthF(3.0); - painter.setPen(pen); - - for (const std::vector& polyline : polylines) { - if (!polyline.empty()) { - painter.drawPolyline(&polyline[0], static_cast(polyline.size())); - } - } + QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(Qt::blue); + pen.setWidthF(3.0); + painter.setPen(pen); - if (vert_bounds) { - painter.drawLine(vert_bounds->first); - painter.drawLine(vert_bounds->second); + for (const std::vector& polyline : polylines) { + if (!polyline.empty()) { + painter.drawPolyline(&polyline[0], static_cast(polyline.size())); } + } + + if (vert_bounds) { + painter.drawLine(vert_bounds->first); + painter.drawLine(vert_bounds->second); + } - return canvas; + return canvas; } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/TextLineTracer.h b/dewarping/TextLineTracer.h index 6a3f1de36..3550a91da 100644 --- a/dewarping/TextLineTracer.h +++ b/dewarping/TextLineTracer.h @@ -19,15 +19,15 @@ #ifndef DEWARPING_TEXT_LINE_TRACER_H_ #define DEWARPING_TEXT_LINE_TRACER_H_ -#include "Grid.h" -#include "VecNT.h" +#include #include #include -#include -#include +#include #include #include -#include +#include +#include "Grid.h" +#include "VecNT.h" class Dpi; class QImage; @@ -47,61 +47,61 @@ namespace dewarping { class DistortionModelBuilder; class TextLineTracer { -public: - static void trace(const imageproc::GrayImage& input, - const Dpi& dpi, - const QRect& content_rect, - DistortionModelBuilder& output, - const TaskStatus& status, - DebugImages* dbg = nullptr); + public: + static void trace(const imageproc::GrayImage& input, + const Dpi& dpi, + const QRect& content_rect, + DistortionModelBuilder& output, + const TaskStatus& status, + DebugImages* dbg = nullptr); -private: - static imageproc::GrayImage downscale(const imageproc::GrayImage& input, const Dpi& dpi); + private: + static imageproc::GrayImage downscale(const imageproc::GrayImage& input, const Dpi& dpi); - static void sanitizeBinaryImage(imageproc::BinaryImage& image, const QRect& content_rect); + static void sanitizeBinaryImage(imageproc::BinaryImage& image, const QRect& content_rect); - static void extractTextLines(std::list>& out, - const imageproc::GrayImage& image, - const std::pair& bounds, - DebugImages* dbg); + static void extractTextLines(std::list>& out, + const imageproc::GrayImage& image, + const std::pair& bounds, + DebugImages* dbg); - static Vec2f calcAvgUnitVector(const std::pair& bounds); + static Vec2f calcAvgUnitVector(const std::pair& bounds); - static imageproc::BinaryImage closeWithObstacles(const imageproc::BinaryImage& image, - const imageproc::BinaryImage& obstacles, - const QSize& brick); + static imageproc::BinaryImage closeWithObstacles(const imageproc::BinaryImage& image, + const imageproc::BinaryImage& obstacles, + const QSize& brick); - static QLineF calcMidLine(const QLineF& line1, const QLineF& line2); + static QLineF calcMidLine(const QLineF& line1, const QLineF& line2); - static void findMidLineSeeds(const imageproc::SEDM& sedm, QLineF mid_line, std::vector& seeds); + static void findMidLineSeeds(const imageproc::SEDM& sedm, QLineF mid_line, std::vector& seeds); - static bool isCurvatureConsistent(const std::vector& polyline); + static bool isCurvatureConsistent(const std::vector& polyline); - static bool isInsideBounds(const QPointF& pt, const QLineF& left_bound, const QLineF& right_bound); + static bool isInsideBounds(const QPointF& pt, const QLineF& left_bound, const QLineF& right_bound); - static void filterShortCurves(std::list>& polylines, - const QLineF& left_bound, - const QLineF& right_bound); + static void filterShortCurves(std::list>& polylines, + const QLineF& left_bound, + const QLineF& right_bound); - static void filterOutOfBoundsCurves(std::list>& polylines, - const QLineF& left_bound, - const QLineF& right_bound); + static void filterOutOfBoundsCurves(std::list>& polylines, + const QLineF& left_bound, + const QLineF& right_bound); - static void filterEdgyCurves(std::list>& polylines); + static void filterEdgyCurves(std::list>& polylines); - static QImage visualizeVerticalBounds(const QImage& background, const std::pair& bounds); + static QImage visualizeVerticalBounds(const QImage& background, const std::pair& bounds); - static QImage visualizeGradient(const QImage& background, const Grid& grad); + static QImage visualizeGradient(const QImage& background, const Grid& grad); - static QImage visualizeMidLineSeeds(const QImage& background, - const imageproc::BinaryImage& overlay, - std::pair bounds, - QLineF mid_line, - const std::vector& seeds); + static QImage visualizeMidLineSeeds(const QImage& background, + const imageproc::BinaryImage& overlay, + std::pair bounds, + QLineF mid_line, + const std::vector& seeds); - static QImage visualizePolylines(const QImage& background, - const std::list>& polylines, - const std::pair* vert_bounds = nullptr); + static QImage visualizePolylines(const QImage& background, + const std::list>& polylines, + const std::pair* vert_bounds = nullptr); }; } // namespace dewarping #endif // ifndef DEWARPING_TEXT_LINE_TRACER_H_ diff --git a/dewarping/TopBottomEdgeTracer.cpp b/dewarping/TopBottomEdgeTracer.cpp index c5bc4bfec..8ce194975 100644 --- a/dewarping/TopBottomEdgeTracer.cpp +++ b/dewarping/TopBottomEdgeTracer.cpp @@ -17,170 +17,155 @@ */ #include "TopBottomEdgeTracer.h" -#include "DistortionModelBuilder.h" -#include "TaskStatus.h" +#include +#include +#include +#include +#include +#include #include "DebugImages.h" +#include "DistortionModelBuilder.h" +#include "GridLineTraverser.h" +#include "LineBoundedByRect.h" +#include "MatrixCalc.h" #include "NumericTraits.h" #include "PriorityQueue.h" +#include "TaskStatus.h" #include "ToLineProjector.h" -#include "LineBoundedByRect.h" -#include "GridLineTraverser.h" -#include "MatrixCalc.h" -#include "imageproc/GrayImage.h" -#include "imageproc/Scale.h" #include "imageproc/Constants.h" #include "imageproc/GaussBlur.h" -#include -#include -#include -#include -#include -#include +#include "imageproc/GrayImage.h" +#include "imageproc/Scale.h" using namespace imageproc; namespace dewarping { struct TopBottomEdgeTracer::GridNode { -private: - static const uint32_t HEAP_IDX_BITS = 28; - static const uint32_t PREV_NEIGHBOUR_BITS = 3; - static const uint32_t PATH_CONTINUATION_BITS = 1; - - static const uint32_t HEAP_IDX_SHIFT = 0; - static const uint32_t PREV_NEIGHBOUR_SHIFT = HEAP_IDX_SHIFT + HEAP_IDX_BITS; - static const uint32_t PATH_CONTINUATION_SHIFT = PREV_NEIGHBOUR_SHIFT + PREV_NEIGHBOUR_BITS; - - static const uint32_t HEAP_IDX_MASK = ((uint32_t(1) << HEAP_IDX_BITS) - uint32_t(1)) << HEAP_IDX_SHIFT; - static const uint32_t PREV_NEIGHBOUR_MASK = ((uint32_t(1) << PREV_NEIGHBOUR_BITS) - uint32_t(1)) - << PREV_NEIGHBOUR_SHIFT; - static const uint32_t PATH_CONTINUATION_MASK = ((uint32_t(1) << PATH_CONTINUATION_BITS) - uint32_t(1)) - << PATH_CONTINUATION_SHIFT; - -public: - static const uint32_t INVALID_HEAP_IDX = HEAP_IDX_MASK >> HEAP_IDX_SHIFT; - - union { - float dirDeriv; // Directional derivative. - float xGrad; // x component of the gradient. - }; - - union { - float pathCost; - float blurred; - float yGrad; // y component of the gradient. - }; - - // Note: xGrad and yGrad are used to calculate the directional - // derivative, which then gets stored in dirDeriv. Obviously, - // pathCost gets overwritten, which is not a problem in our case. - uint32_t packedData; - - float absDirDeriv() const { - return std::fabs(dirDeriv); - } - - void setupForPadding() { - dirDeriv = 0; - pathCost = -1; - packedData = INVALID_HEAP_IDX; - } - - /** - * Note that is one doesn't modify dirDeriv. - */ - void setupForInterior() { - pathCost = NumericTraits::max(); - packedData = INVALID_HEAP_IDX; - } - - uint32_t heapIdx() const { - return (packedData & HEAP_IDX_MASK) >> HEAP_IDX_SHIFT; - } - - void setHeapIdx(uint32_t idx) { - assert(!(idx & ~(HEAP_IDX_MASK >> HEAP_IDX_SHIFT))); - packedData = idx | (packedData & ~HEAP_IDX_MASK); - } - - bool hasPathContinuation() const { - return static_cast(packedData & PATH_CONTINUATION_MASK); - } - - /** - * Neibhgours are indexed like this: - * 0 1 2 - * 3 4 - * 5 6 7 - */ - uint32_t prevNeighbourIdx() const { - return (packedData & PREV_NEIGHBOUR_MASK) >> PREV_NEIGHBOUR_SHIFT; - } - - void setPrevNeighbourIdx(uint32_t idx) { - assert(!(idx & ~(PREV_NEIGHBOUR_MASK >> PREV_NEIGHBOUR_SHIFT))); - packedData = PATH_CONTINUATION_MASK | (idx << PREV_NEIGHBOUR_SHIFT) | (packedData & ~PREV_NEIGHBOUR_MASK); - } - - void setBothGradients(float grad) { - xGrad = grad; - yGrad = grad; - } + private: + static const uint32_t HEAP_IDX_BITS = 28; + static const uint32_t PREV_NEIGHBOUR_BITS = 3; + static const uint32_t PATH_CONTINUATION_BITS = 1; + + static const uint32_t HEAP_IDX_SHIFT = 0; + static const uint32_t PREV_NEIGHBOUR_SHIFT = HEAP_IDX_SHIFT + HEAP_IDX_BITS; + static const uint32_t PATH_CONTINUATION_SHIFT = PREV_NEIGHBOUR_SHIFT + PREV_NEIGHBOUR_BITS; + + static const uint32_t HEAP_IDX_MASK = ((uint32_t(1) << HEAP_IDX_BITS) - uint32_t(1)) << HEAP_IDX_SHIFT; + static const uint32_t PREV_NEIGHBOUR_MASK = ((uint32_t(1) << PREV_NEIGHBOUR_BITS) - uint32_t(1)) + << PREV_NEIGHBOUR_SHIFT; + static const uint32_t PATH_CONTINUATION_MASK = ((uint32_t(1) << PATH_CONTINUATION_BITS) - uint32_t(1)) + << PATH_CONTINUATION_SHIFT; + + public: + static const uint32_t INVALID_HEAP_IDX = HEAP_IDX_MASK >> HEAP_IDX_SHIFT; + + union { + float dirDeriv; // Directional derivative. + float xGrad; // x component of the gradient. + }; + + union { + float pathCost; + float blurred; + float yGrad; // y component of the gradient. + }; + + // Note: xGrad and yGrad are used to calculate the directional + // derivative, which then gets stored in dirDeriv. Obviously, + // pathCost gets overwritten, which is not a problem in our case. + uint32_t packedData; + + float absDirDeriv() const { return std::fabs(dirDeriv); } + + void setupForPadding() { + dirDeriv = 0; + pathCost = -1; + packedData = INVALID_HEAP_IDX; + } + + /** + * Note that is one doesn't modify dirDeriv. + */ + void setupForInterior() { + pathCost = NumericTraits::max(); + packedData = INVALID_HEAP_IDX; + } + + uint32_t heapIdx() const { return (packedData & HEAP_IDX_MASK) >> HEAP_IDX_SHIFT; } + + void setHeapIdx(uint32_t idx) { + assert(!(idx & ~(HEAP_IDX_MASK >> HEAP_IDX_SHIFT))); + packedData = idx | (packedData & ~HEAP_IDX_MASK); + } + + bool hasPathContinuation() const { return static_cast(packedData & PATH_CONTINUATION_MASK); } + + /** + * Neibhgours are indexed like this: + * 0 1 2 + * 3 4 + * 5 6 7 + */ + uint32_t prevNeighbourIdx() const { return (packedData & PREV_NEIGHBOUR_MASK) >> PREV_NEIGHBOUR_SHIFT; } + + void setPrevNeighbourIdx(uint32_t idx) { + assert(!(idx & ~(PREV_NEIGHBOUR_MASK >> PREV_NEIGHBOUR_SHIFT))); + packedData = PATH_CONTINUATION_MASK | (idx << PREV_NEIGHBOUR_SHIFT) | (packedData & ~PREV_NEIGHBOUR_MASK); + } + + void setBothGradients(float grad) { + xGrad = grad; + yGrad = grad; + } }; class TopBottomEdgeTracer::PrioQueue : public PriorityQueue { -public: - explicit PrioQueue(Grid& grid) : m_pData(grid.data()) { - } + public: + explicit PrioQueue(Grid& grid) : m_data(grid.data()) {} - bool higherThan(uint32_t lhs, uint32_t rhs) const { - return m_pData[lhs].pathCost < m_pData[rhs].pathCost; - } + bool higherThan(uint32_t lhs, uint32_t rhs) const { return m_data[lhs].pathCost < m_data[rhs].pathCost; } - void setIndex(uint32_t grid_idx, size_t heap_idx) { - m_pData[grid_idx].setHeapIdx(static_cast(heap_idx)); - } + void setIndex(uint32_t grid_idx, size_t heap_idx) { m_data[grid_idx].setHeapIdx(static_cast(heap_idx)); } - void reposition(GridNode* node) { - PriorityQueue::reposition(node->heapIdx()); - } + void reposition(GridNode* node) { PriorityQueue::reposition(node->heapIdx()); } -private: - GridNode* const m_pData; + private: + GridNode* const m_data; }; struct TopBottomEdgeTracer::Step { - Vec2f pt; - uint32_t prevStepIdx{}; - float pathCost{}; + Vec2f pt; + uint32_t prevStepIdx{}; + float pathCost{}; }; -template +template float TopBottomEdgeTracer::interpolatedGridValue(const Grid& grid, Extractor extractor, const Vec2f pos, float default_value) { - const auto x_base = static_cast(std::floor(pos[0])); - const auto y_base = static_cast(std::floor(pos[1])); - const auto x_base_i = (int) x_base; - const auto y_base_i = (int) y_base; + const auto x_base = static_cast(std::floor(pos[0])); + const auto y_base = static_cast(std::floor(pos[1])); + const auto x_base_i = (int) x_base; + const auto y_base_i = (int) y_base; - if ((x_base_i < 0) || (y_base_i < 0) || (x_base_i + 1 >= grid.width()) || (y_base_i + 1 >= grid.height())) { - return default_value; - } + if ((x_base_i < 0) || (y_base_i < 0) || (x_base_i + 1 >= grid.width()) || (y_base_i + 1 >= grid.height())) { + return default_value; + } - const float x = pos[0] - x_base; - const float y = pos[1] - y_base; - const float x1 = 1.0f - x; - const float y1 = 1.0f - y; + const float x = pos[0] - x_base; + const float y = pos[1] - y_base; + const float x1 = 1.0f - x; + const float y1 = 1.0f - y; - const int stride = grid.stride(); - const GridNode* base = grid.data() + y_base_i * stride + x_base_i; + const int stride = grid.stride(); + const GridNode* base = grid.data() + y_base_i * stride + x_base_i; - return extractor(base[0]) * x1 * y1 + extractor(base[1]) * x * y1 + extractor(base[stride]) * x1 * y - + extractor(base[stride + 1]) * x * y; + return extractor(base[0]) * x1 * y1 + extractor(base[1]) * x * y1 + extractor(base[stride]) * x1 * y + + extractor(base[stride + 1]) * x * y; } void TopBottomEdgeTracer::trace(const imageproc::GrayImage& image, @@ -188,1058 +173,1055 @@ void TopBottomEdgeTracer::trace(const imageproc::GrayImage& image, DistortionModelBuilder& output, const TaskStatus& status, DebugImages* dbg) { - if ((bounds.first.p1() == bounds.first.p2()) || (bounds.second.p1() == bounds.second.p2())) { - return; // Bad bounds. - } - - GrayImage downscaled; - QSize downscaled_size(image.size()); - QTransform downscaling_xform; - - if (std::max(image.width(), image.height()) < 1500) { - // Don't downscale - it's already small. - downscaled = image; - } else { - // Proceed with downscaling. - downscaled_size.scale(1000, 1000, Qt::KeepAspectRatio); - downscaling_xform.scale(double(downscaled_size.width()) / image.width(), - double(downscaled_size.height()) / image.height()); - downscaled = scaleToGray(image, downscaled_size); - if (dbg) { - dbg->add(downscaled, "downscaled"); - } - - status.throwIfCancelled(); - - bounds.first = downscaling_xform.map(bounds.first); - bounds.second = downscaling_xform.map(bounds.second); - } - - // Those -1's are to make sure the endpoints, rounded to integers, - // will be within the image. - if (!intersectWithRect(bounds, QRectF(downscaled.rect()).adjusted(0, 0, -1, -1))) { - return; - } - - forceSameDirection(bounds); - - const Vec2f avg_bounds_dir(calcAvgUnitVector(bounds)); - Grid grid(downscaled.width(), downscaled.height(), /*padding=*/1); - calcDirectionalDerivative(grid, downscaled, avg_bounds_dir); + if ((bounds.first.p1() == bounds.first.p2()) || (bounds.second.p1() == bounds.second.p2())) { + return; // Bad bounds. + } + + GrayImage downscaled; + QSize downscaled_size(image.size()); + QTransform downscaling_xform; + + if (std::max(image.width(), image.height()) < 1500) { + // Don't downscale - it's already small. + downscaled = image; + } else { + // Proceed with downscaling. + downscaled_size.scale(1000, 1000, Qt::KeepAspectRatio); + downscaling_xform.scale(double(downscaled_size.width()) / image.width(), + double(downscaled_size.height()) / image.height()); + downscaled = scaleToGray(image, downscaled_size); if (dbg) { - dbg->add(visualizeGradient(grid), "gradient"); + dbg->add(downscaled, "downscaled"); } status.throwIfCancelled(); - PrioQueue queue(grid); - - // Shortest paths from bounds.first towards bounds.second. - prepareForShortestPathsFrom(queue, grid, bounds.first); - const Vec2f dir_1st_to_2nd(directionFromPointToLine(bounds.first.pointAt(0.5), bounds.second)); - propagateShortestPaths(dir_1st_to_2nd, queue, grid); - const std::vector endpoints1(locateBestPathEndpoints(grid, bounds.second)); - if (dbg) { - dbg->add(visualizePaths(downscaled, grid, bounds, endpoints1), "best_paths_ltr"); - } - - gaussBlurGradient(grid); - - std::vector> snakes; - snakes.reserve(endpoints1.size()); - - for (QPoint endpoint : endpoints1) { - snakes.push_back(pathToSnake(grid, endpoint)); - const Vec2f dir(downTheHillDirection(downscaled.rect(), snakes.back(), avg_bounds_dir)); - downTheHillSnake(snakes.back(), grid, dir); - } - if (dbg) { - const QImage background(visualizeBlurredGradient(grid)); - dbg->add(visualizeSnakes(background, snakes, bounds), "down_the_hill_snakes"); - } - - for (std::vector& snake : snakes) { - const Vec2f dir(-downTheHillDirection(downscaled.rect(), snake, avg_bounds_dir)); - upTheHillSnake(snake, grid, dir); - } - if (dbg) { - const QImage background(visualizeGradient(grid)); - dbg->add(visualizeSnakes(background, snakes, bounds), "up_the_hill_snakes"); - } - - // Convert snakes back to the original coordinate system. - const QTransform upscaling_xform(downscaling_xform.inverted()); - for (std::vector& snake : snakes) { - for (QPointF& pt : snake) { - pt = upscaling_xform.map(pt); - } - output.addHorizontalCurve(snake); - } + bounds.first = downscaling_xform.map(bounds.first); + bounds.second = downscaling_xform.map(bounds.second); + } + + // Those -1's are to make sure the endpoints, rounded to integers, + // will be within the image. + if (!intersectWithRect(bounds, QRectF(downscaled.rect()).adjusted(0, 0, -1, -1))) { + return; + } + + forceSameDirection(bounds); + + const Vec2f avg_bounds_dir(calcAvgUnitVector(bounds)); + Grid grid(downscaled.width(), downscaled.height(), /*padding=*/1); + calcDirectionalDerivative(grid, downscaled, avg_bounds_dir); + if (dbg) { + dbg->add(visualizeGradient(grid), "gradient"); + } + + status.throwIfCancelled(); + + PrioQueue queue(grid); + + // Shortest paths from bounds.first towards bounds.second. + prepareForShortestPathsFrom(queue, grid, bounds.first); + const Vec2f dir_1st_to_2nd(directionFromPointToLine(bounds.first.pointAt(0.5), bounds.second)); + propagateShortestPaths(dir_1st_to_2nd, queue, grid); + const std::vector endpoints1(locateBestPathEndpoints(grid, bounds.second)); + if (dbg) { + dbg->add(visualizePaths(downscaled, grid, bounds, endpoints1), "best_paths_ltr"); + } + + gaussBlurGradient(grid); + + std::vector> snakes; + snakes.reserve(endpoints1.size()); + + for (QPoint endpoint : endpoints1) { + snakes.push_back(pathToSnake(grid, endpoint)); + const Vec2f dir(downTheHillDirection(downscaled.rect(), snakes.back(), avg_bounds_dir)); + downTheHillSnake(snakes.back(), grid, dir); + } + if (dbg) { + const QImage background(visualizeBlurredGradient(grid)); + dbg->add(visualizeSnakes(background, snakes, bounds), "down_the_hill_snakes"); + } + + for (std::vector& snake : snakes) { + const Vec2f dir(-downTheHillDirection(downscaled.rect(), snake, avg_bounds_dir)); + upTheHillSnake(snake, grid, dir); + } + if (dbg) { + const QImage background(visualizeGradient(grid)); + dbg->add(visualizeSnakes(background, snakes, bounds), "up_the_hill_snakes"); + } + + // Convert snakes back to the original coordinate system. + const QTransform upscaling_xform(downscaling_xform.inverted()); + for (std::vector& snake : snakes) { + for (QPointF& pt : snake) { + pt = upscaling_xform.map(pt); + } + output.addHorizontalCurve(snake); + } } // TopBottomEdgeTracer::trace bool TopBottomEdgeTracer::intersectWithRect(std::pair& bounds, const QRectF& rect) { - return lineBoundedByRect(bounds.first, rect) && lineBoundedByRect(bounds.second, rect); + return lineBoundedByRect(bounds.first, rect) && lineBoundedByRect(bounds.second, rect); } void TopBottomEdgeTracer::forceSameDirection(std::pair& bounds) { - const QPointF v1(bounds.first.p2() - bounds.first.p1()); - const QPointF v2(bounds.second.p2() - bounds.second.p1()); - if (v1.x() * v2.x() + v1.y() * v2.y() < 0) { - bounds.second.setPoints(bounds.second.p2(), bounds.second.p1()); - } + const QPointF v1(bounds.first.p2() - bounds.first.p1()); + const QPointF v2(bounds.second.p2() - bounds.second.p1()); + if (v1.x() * v2.x() + v1.y() * v2.y() < 0) { + bounds.second.setPoints(bounds.second.p2(), bounds.second.p1()); + } } void TopBottomEdgeTracer::calcDirectionalDerivative(Grid& grid, const imageproc::GrayImage& image, const Vec2f& direction) { - assert(grid.padding() == 1); + assert(grid.padding() == 1); - const int width = grid.width(); - const int height = grid.height(); + const int width = grid.width(); + const int height = grid.height(); - const int grid_stride = grid.stride(); - const int image_stride = image.stride(); + const int grid_stride = grid.stride(); + const int image_stride = image.stride(); - const uint8_t* image_line = image.data(); - GridNode* grid_line = grid.data(); + const uint8_t* image_line = image.data(); + GridNode* grid_line = grid.data(); - // This ensures that partial derivatives never go beyond the [-1, 1] range. - const float scale = 1.0f / (255.0f * 8.0f); + // This ensures that partial derivatives never go beyond the [-1, 1] range. + const float scale = 1.0f / (255.0f * 8.0f); - // We are going to use both GridNode::gradient and GridNode::pathCost - // to calculate the gradient. + // We are going to use both GridNode::gradient and GridNode::pathCost + // to calculate the gradient. - // Copy image to gradient. - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - grid_line[x].setBothGradients(scale * image_line[x]); - } - image_line += image_stride; - grid_line += grid_stride; - } - - // Write border corners. - grid_line = grid.paddedData(); - grid_line[0].setBothGradients(grid_line[grid_stride + 1].xGrad); - grid_line[grid_stride - 1].setBothGradients(grid_line[grid_stride * 2 - 2].xGrad); - grid_line += grid_stride * (height + 1); - grid_line[0].setBothGradients(grid_line[1 - grid_stride].xGrad); - grid_line[grid_stride - 1].setBothGradients(grid_line[-2].xGrad); - - // Top border line. - grid_line = grid.paddedData() + 1; + // Copy image to gradient. + for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - grid_line[0].setBothGradients(grid_line[grid_stride].xGrad); - ++grid_line; - } - - // Bottom border line. - grid_line = grid.paddedData() + grid_stride * (height + 1) + 1; + grid_line[x].setBothGradients(scale * image_line[x]); + } + image_line += image_stride; + grid_line += grid_stride; + } + + // Write border corners. + grid_line = grid.paddedData(); + grid_line[0].setBothGradients(grid_line[grid_stride + 1].xGrad); + grid_line[grid_stride - 1].setBothGradients(grid_line[grid_stride * 2 - 2].xGrad); + grid_line += grid_stride * (height + 1); + grid_line[0].setBothGradients(grid_line[1 - grid_stride].xGrad); + grid_line[grid_stride - 1].setBothGradients(grid_line[-2].xGrad); + + // Top border line. + grid_line = grid.paddedData() + 1; + for (int x = 0; x < width; ++x) { + grid_line[0].setBothGradients(grid_line[grid_stride].xGrad); + ++grid_line; + } + + // Bottom border line. + grid_line = grid.paddedData() + grid_stride * (height + 1) + 1; + for (int x = 0; x < width; ++x) { + grid_line[0].setBothGradients(grid_line[-grid_stride].xGrad); + ++grid_line; + } + // Left and right border lines. + grid_line = grid.paddedData() + grid_stride; + for (int y = 0; y < height; ++y) { + grid_line[0].setBothGradients(grid_line[1].xGrad); + grid_line[grid_stride - 1].setBothGradients(grid_line[grid_stride - 2].xGrad); + grid_line += grid_stride; + } + + horizontalSobelInPlace(grid); + verticalSobelInPlace(grid); + // From horizontal and vertical gradients, calculate the directional one. + grid_line = grid.data(); + for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - grid_line[0].setBothGradients(grid_line[-grid_stride].xGrad); - ++grid_line; - } - // Left and right border lines. - grid_line = grid.paddedData() + grid_stride; - for (int y = 0; y < height; ++y) { - grid_line[0].setBothGradients(grid_line[1].xGrad); - grid_line[grid_stride - 1].setBothGradients(grid_line[grid_stride - 2].xGrad); - grid_line += grid_stride; + const Vec2f grad_vec(grid_line[x].xGrad, grid_line[x].yGrad); + grid_line[x].dirDeriv = grad_vec.dot(direction); + assert(std::fabs(grid_line[x].dirDeriv) <= 1.0); } - horizontalSobelInPlace(grid); - verticalSobelInPlace(grid); - // From horizontal and vertical gradients, calculate the directional one. - grid_line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const Vec2f grad_vec(grid_line[x].xGrad, grid_line[x].yGrad); - grid_line[x].dirDeriv = grad_vec.dot(direction); - assert(std::fabs(grid_line[x].dirDeriv) <= 1.0); - } - - grid_line += grid_stride; - } + grid_line += grid_stride; + } } // TopBottomEdgeTracer::calcDirectionalDerivative void TopBottomEdgeTracer::horizontalSobelInPlace(Grid& grid) { - assert(grid.padding() == 1); - - const int width = grid.width(); - const int height = grid.height(); - const int grid_stride = grid.stride(); - - // Do a vertical pass. - for (int x = -1; x < width + 1; ++x) { - GridNode* p_grid = grid.data() + x; - float prev = p_grid[-grid_stride].xGrad; - for (int y = 0; y < height; ++y) { - const float cur = p_grid->xGrad; - p_grid->xGrad = prev + cur + cur + p_grid[grid_stride].xGrad; - prev = cur; - p_grid += grid_stride; - } - } + assert(grid.padding() == 1); - // Do a horizontal pass and write results. - GridNode* grid_line = grid.data(); + const int width = grid.width(); + const int height = grid.height(); + const int grid_stride = grid.stride(); + + // Do a vertical pass. + for (int x = -1; x < width + 1; ++x) { + GridNode* p_grid = grid.data() + x; + float prev = p_grid[-grid_stride].xGrad; for (int y = 0; y < height; ++y) { - float prev = grid_line[-1].xGrad; - for (int x = 0; x < width; ++x) { - float cur = grid_line[x].xGrad; - grid_line[x].xGrad = grid_line[x + 1].xGrad - prev; - prev = cur; - } - grid_line += grid_stride; + const float cur = p_grid->xGrad; + p_grid->xGrad = prev + cur + cur + p_grid[grid_stride].xGrad; + prev = cur; + p_grid += grid_stride; + } + } + + // Do a horizontal pass and write results. + GridNode* grid_line = grid.data(); + for (int y = 0; y < height; ++y) { + float prev = grid_line[-1].xGrad; + for (int x = 0; x < width; ++x) { + float cur = grid_line[x].xGrad; + grid_line[x].xGrad = grid_line[x + 1].xGrad - prev; + prev = cur; } + grid_line += grid_stride; + } } void TopBottomEdgeTracer::verticalSobelInPlace(Grid& grid) { - assert(grid.padding() == 1); - - const int width = grid.width(); - const int height = grid.height(); - const int grid_stride = grid.stride(); - // Do a horizontal pass. - GridNode* grid_line = grid.paddedData() + 1; - for (int y = 0; y < height + 2; ++y) { - float prev = grid_line[-1].yGrad; - for (int x = 0; x < width; ++x) { - float cur = grid_line[x].yGrad; - grid_line[x].yGrad = prev + cur + cur + grid_line[x + 1].yGrad; - prev = cur; - } - grid_line += grid_stride; + assert(grid.padding() == 1); + + const int width = grid.width(); + const int height = grid.height(); + const int grid_stride = grid.stride(); + // Do a horizontal pass. + GridNode* grid_line = grid.paddedData() + 1; + for (int y = 0; y < height + 2; ++y) { + float prev = grid_line[-1].yGrad; + for (int x = 0; x < width; ++x) { + float cur = grid_line[x].yGrad; + grid_line[x].yGrad = prev + cur + cur + grid_line[x + 1].yGrad; + prev = cur; } + grid_line += grid_stride; + } - // Do a vertical pass and write resuts. - for (int x = 0; x < width; ++x) { - GridNode* p_grid = grid.data() + x; - float prev = p_grid[-grid_stride].yGrad; - for (int y = 0; y < height; ++y) { - const float cur = p_grid->yGrad; - p_grid->yGrad = p_grid[grid_stride].yGrad - prev; - prev = cur; - p_grid += grid_stride; - } + // Do a vertical pass and write resuts. + for (int x = 0; x < width; ++x) { + GridNode* p_grid = grid.data() + x; + float prev = p_grid[-grid_stride].yGrad; + for (int y = 0; y < height; ++y) { + const float cur = p_grid->yGrad; + p_grid->yGrad = p_grid[grid_stride].yGrad - prev; + prev = cur; + p_grid += grid_stride; } + } } Vec2f TopBottomEdgeTracer::calcAvgUnitVector(const std::pair& bounds) { - Vec2f v1(bounds.first.p2() - bounds.first.p1()); - v1 /= std::sqrt(v1.squaredNorm()); + Vec2f v1(bounds.first.p2() - bounds.first.p1()); + v1 /= std::sqrt(v1.squaredNorm()); - Vec2f v2(bounds.second.p2() - bounds.second.p1()); - v2 /= std::sqrt(v2.squaredNorm()); + Vec2f v2(bounds.second.p2() - bounds.second.p1()); + v2 /= std::sqrt(v2.squaredNorm()); - Vec2f v3(v1 + v2); - v3 /= std::sqrt(v3.squaredNorm()); + Vec2f v3(v1 + v2); + v3 /= std::sqrt(v3.squaredNorm()); - return v3; + return v3; } Vec2f TopBottomEdgeTracer::directionFromPointToLine(const QPointF& pt, const QLineF& line) { - Vec2f vec(ToLineProjector(line).projectionVector(pt)); - const float sqlen = vec.squaredNorm(); - if (sqlen > 1e-5) { - vec /= std::sqrt(sqlen); - } + Vec2f vec(ToLineProjector(line).projectionVector(pt)); + const float sqlen = vec.squaredNorm(); + if (sqlen > 1e-5) { + vec /= std::sqrt(sqlen); + } - return vec; + return vec; } void TopBottomEdgeTracer::prepareForShortestPathsFrom(PrioQueue& queue, Grid& grid, const QLineF& from) { - GridNode padding_node{}; - padding_node.setupForPadding(); - grid.initPadding(padding_node); + GridNode padding_node{}; + padding_node.setupForPadding(); + grid.initPadding(padding_node); - const int width = grid.width(); - const int height = grid.height(); - const int stride = grid.stride(); - GridNode* const data = grid.data(); + const int width = grid.width(); + const int height = grid.height(); + const int stride = grid.stride(); + GridNode* const data = grid.data(); - GridNode* line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - GridNode* node = line + x; - node->setupForInterior(); - // This doesn't modify dirDeriv, which is why - // we can't use grid.initInterior(). - } - line += stride; + GridNode* line = grid.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + GridNode* node = line + x; + node->setupForInterior(); + // This doesn't modify dirDeriv, which is why + // we can't use grid.initInterior(). } + line += stride; + } - GridLineTraverser traverser(from); - while (traverser.hasNext()) { - const QPoint pt(traverser.next()); + GridLineTraverser traverser(from); + while (traverser.hasNext()) { + const QPoint pt(traverser.next()); - // intersectWithRect() ensures that. - assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); + // intersectWithRect() ensures that. + assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); - const int offset = pt.y() * stride + pt.x(); - data[offset].pathCost = 0; - queue.push(offset); - } + const int offset = pt.y() * stride + pt.x(); + data[offset].pathCost = 0; + queue.push(offset); + } } void TopBottomEdgeTracer::propagateShortestPaths(const Vec2f& direction, PrioQueue& queue, Grid& grid) { - GridNode* const data = grid.data(); - - int next_nbh_offsets[8]; - int prev_nbh_indexes[8]; - const int num_neighbours = initNeighbours(next_nbh_offsets, prev_nbh_indexes, grid.stride(), direction); - - while (!queue.empty()) { - const int grid_idx = queue.front(); - GridNode* node = data + grid_idx; - assert(node->pathCost >= 0); - queue.pop(); - node->setHeapIdx(GridNode::INVALID_HEAP_IDX); - - for (int i = 0; i < num_neighbours; ++i) { - const int nbh_grid_idx = grid_idx + next_nbh_offsets[i]; - GridNode* nbh_node = data + nbh_grid_idx; - - assert(std::fabs(node->dirDeriv) <= 1.0); - const float new_cost - = std::max(node->pathCost, static_cast(1.0f - std::fabs(node->dirDeriv))); - if (new_cost < nbh_node->pathCost) { - nbh_node->pathCost = new_cost; - nbh_node->setPrevNeighbourIdx(prev_nbh_indexes[i]); - if (nbh_node->heapIdx() == GridNode::INVALID_HEAP_IDX) { - queue.push(nbh_grid_idx); - } else { - queue.reposition(nbh_node); - } - } + GridNode* const data = grid.data(); + + int next_nbh_offsets[8]; + int prev_nbh_indexes[8]; + const int num_neighbours = initNeighbours(next_nbh_offsets, prev_nbh_indexes, grid.stride(), direction); + + while (!queue.empty()) { + const int grid_idx = queue.front(); + GridNode* node = data + grid_idx; + assert(node->pathCost >= 0); + queue.pop(); + node->setHeapIdx(GridNode::INVALID_HEAP_IDX); + + for (int i = 0; i < num_neighbours; ++i) { + const int nbh_grid_idx = grid_idx + next_nbh_offsets[i]; + GridNode* nbh_node = data + nbh_grid_idx; + + assert(std::fabs(node->dirDeriv) <= 1.0); + const float new_cost + = std::max(node->pathCost, static_cast(1.0f - std::fabs(node->dirDeriv))); + if (new_cost < nbh_node->pathCost) { + nbh_node->pathCost = new_cost; + nbh_node->setPrevNeighbourIdx(prev_nbh_indexes[i]); + if (nbh_node->heapIdx() == GridNode::INVALID_HEAP_IDX) { + queue.push(nbh_grid_idx); + } else { + queue.reposition(nbh_node); } + } } + } } // TopBottomEdgeTracer::propagateShortestPaths int TopBottomEdgeTracer::initNeighbours(int* next_nbh_offsets, int* prev_nbh_indexes, int stride, const Vec2f& direction) { - const int candidate_offsets[] = {-stride - 1, -stride, -stride + 1, -1, 1, stride - 1, stride, stride + 1}; + const int candidate_offsets[] = {-stride - 1, -stride, -stride + 1, -1, 1, stride - 1, stride, stride + 1}; - const float candidate_vectors[8][2] = {{-1.0f, -1.0f}, {0.0f, -1.0f}, {1.0f, -1.0f}, {-1.0f, 0.0f}, - {1.0f, 0.0f}, {-1.0f, 1.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}}; + const float candidate_vectors[8][2] = {{-1.0f, -1.0f}, {0.0f, -1.0f}, {1.0f, -1.0f}, {-1.0f, 0.0f}, + {1.0f, 0.0f}, {-1.0f, 1.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}}; - static const int opposite_nbh_map[] = {7, 6, 5, 4, 3, 2, 1, 0}; + static const int opposite_nbh_map[] = {7, 6, 5, 4, 3, 2, 1, 0}; - int out_idx = 0; - for (int i = 0; i < 8; ++i) { - const Vec2f vec(candidate_vectors[i][0], candidate_vectors[i][1]); - if (vec.dot(direction) > 0) { - next_nbh_offsets[out_idx] = candidate_offsets[i]; - prev_nbh_indexes[out_idx] = opposite_nbh_map[i]; - ++out_idx; - } + int out_idx = 0; + for (int i = 0; i < 8; ++i) { + const Vec2f vec(candidate_vectors[i][0], candidate_vectors[i][1]); + if (vec.dot(direction) > 0) { + next_nbh_offsets[out_idx] = candidate_offsets[i]; + prev_nbh_indexes[out_idx] = opposite_nbh_map[i]; + ++out_idx; } + } - return out_idx; + return out_idx; } // TopBottomEdgeTracer::initNeighbours namespace { struct Path { - QPoint pt; - float cost; + QPoint pt; + float cost; - Path(QPoint pt, float cost) : pt(pt), cost(cost) { - } + Path(QPoint pt, float cost) : pt(pt), cost(cost) {} }; } // namespace std::vector TopBottomEdgeTracer::locateBestPathEndpoints(const Grid& grid, const QLineF& line) { - const int width = grid.width(); - const int height = grid.height(); - const int stride = grid.stride(); - const GridNode* const data = grid.data(); - - const size_t num_best_paths = 2; // Take N best paths. - const int min_sqdist = 100 * 100; - std::vector best_paths; - - GridLineTraverser traverser(line); - while (traverser.hasNext()) { - const QPoint pt(traverser.next()); - - // intersectWithRect() ensures that. - assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); - - const uint32_t offset = pt.y() * stride + pt.x(); - const GridNode* node = data + offset; - - // Find the closest path. - Path* closest_path = nullptr; - int closest_sqdist = std::numeric_limits::max(); - for (Path& path : best_paths) { - const QPoint delta(path.pt - pt); - const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); - if (sqdist < closest_sqdist) { - closest_path = &path; - closest_sqdist = sqdist; - } - } - - if (closest_sqdist < min_sqdist) { - // That's too close. - if (node->pathCost < closest_path->cost) { - closest_path->pt = pt; - closest_path->cost = node->pathCost; - } - continue; - } - - if (best_paths.size() < num_best_paths) { - best_paths.emplace_back(pt, node->pathCost); - } else { - // Find the one to kick out (if any). - for (Path& path : best_paths) { - if (node->pathCost < path.cost) { - path = Path(pt, node->pathCost); - break; - } - } + const int width = grid.width(); + const int height = grid.height(); + const int stride = grid.stride(); + const GridNode* const data = grid.data(); + + const size_t num_best_paths = 2; // Take N best paths. + const int min_sqdist = 100 * 100; + std::vector best_paths; + + GridLineTraverser traverser(line); + while (traverser.hasNext()) { + const QPoint pt(traverser.next()); + + // intersectWithRect() ensures that. + assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); + + const uint32_t offset = pt.y() * stride + pt.x(); + const GridNode* node = data + offset; + + // Find the closest path. + Path* closest_path = nullptr; + int closest_sqdist = std::numeric_limits::max(); + for (Path& path : best_paths) { + const QPoint delta(path.pt - pt); + const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); + if (sqdist < closest_sqdist) { + closest_path = &path; + closest_sqdist = sqdist; + } + } + + if (closest_sqdist < min_sqdist) { + // That's too close. + if (node->pathCost < closest_path->cost) { + closest_path->pt = pt; + closest_path->cost = node->pathCost; + } + continue; + } + + if (best_paths.size() < num_best_paths) { + best_paths.emplace_back(pt, node->pathCost); + } else { + // Find the one to kick out (if any). + for (Path& path : best_paths) { + if (node->pathCost < path.cost) { + path = Path(pt, node->pathCost); + break; } + } } + } - std::vector best_endpoints; + std::vector best_endpoints; - for (const Path& path : best_paths) { - if (path.cost < 0.95f) { - best_endpoints.push_back(path.pt); - } + for (const Path& path : best_paths) { + if (path.cost < 0.95f) { + best_endpoints.push_back(path.pt); } + } - return best_endpoints; + return best_endpoints; } // TopBottomEdgeTracer::locateBestPathEndpoints std::vector TopBottomEdgeTracer::tracePathFromEndpoint(const Grid& grid, const QPoint& endpoint) { - static const int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; - static const int dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; - - const int stride = grid.stride(); - const int grid_offsets[8] = {-stride - 1, -stride, -stride + 1, -1, +1, +stride - 1, +stride, +stride + 1}; + static const int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + static const int dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; - const GridNode* const data = grid.data(); - std::vector path; + const int stride = grid.stride(); + const int grid_offsets[8] = {-stride - 1, -stride, -stride + 1, -1, +1, +stride - 1, +stride, +stride + 1}; - QPoint pt(endpoint); - int grid_offset = pt.x() + pt.y() * stride; - for (;;) { - path.push_back(pt); + const GridNode* const data = grid.data(); + std::vector path; - const GridNode* node = data + grid_offset; - if (!node->hasPathContinuation()) { - break; - } + QPoint pt(endpoint); + int grid_offset = pt.x() + pt.y() * stride; + while (true) { + path.push_back(pt); - const int nbh_idx = node->prevNeighbourIdx(); - grid_offset += grid_offsets[nbh_idx]; - pt += QPoint(dx[nbh_idx], dy[nbh_idx]); + const GridNode* node = data + grid_offset; + if (!node->hasPathContinuation()) { + break; } - return path; + const int nbh_idx = node->prevNeighbourIdx(); + grid_offset += grid_offsets[nbh_idx]; + pt += QPoint(dx[nbh_idx], dy[nbh_idx]); + } + + return path; } // TopBottomEdgeTracer::tracePathFromEndpoint std::vector TopBottomEdgeTracer::pathToSnake(const Grid& grid, const QPoint& endpoint) { - const int max_dist = 15; // Maximum distance between two snake knots. - const int max_dist_sq = max_dist * max_dist; - const int half_max_dist = max_dist / 2; - const int half_max_dist_sq = half_max_dist * half_max_dist; - - static const int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; - static const int dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; - - const int stride = grid.stride(); - const int grid_offsets[8] = {-stride - 1, -stride, -stride + 1, -1, +1, +stride - 1, +stride, +stride + 1}; - - const GridNode* const data = grid.data(); - std::vector snake; - snake.emplace_back(endpoint); - QPoint snake_tail(endpoint); - - QPoint pt(endpoint); - int grid_offset = pt.x() + pt.y() * stride; - for (;;) { - const QPoint delta(pt - snake_tail); - const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); - - const GridNode* node = data + grid_offset; - if (!node->hasPathContinuation()) { - if (sqdist >= half_max_dist_sq) { - snake.emplace_back(pt); - snake_tail = pt; - } - break; - } + const int max_dist = 15; // Maximum distance between two snake knots. + const int max_dist_sq = max_dist * max_dist; + const int half_max_dist = max_dist / 2; + const int half_max_dist_sq = half_max_dist * half_max_dist; - if (sqdist >= max_dist_sq) { - snake.emplace_back(pt); - snake_tail = pt; - } + static const int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + static const int dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + + const int stride = grid.stride(); + const int grid_offsets[8] = {-stride - 1, -stride, -stride + 1, -1, +1, +stride - 1, +stride, +stride + 1}; + + const GridNode* const data = grid.data(); + std::vector snake; + snake.emplace_back(endpoint); + QPoint snake_tail(endpoint); + + QPoint pt(endpoint); + int grid_offset = pt.x() + pt.y() * stride; + while (true) { + const QPoint delta(pt - snake_tail); + const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); - const int nbh_idx = node->prevNeighbourIdx(); - grid_offset += grid_offsets[nbh_idx]; - pt += QPoint(dx[nbh_idx], dy[nbh_idx]); + const GridNode* node = data + grid_offset; + if (!node->hasPathContinuation()) { + if (sqdist >= half_max_dist_sq) { + snake.emplace_back(pt); + snake_tail = pt; + } + break; } - return snake; + if (sqdist >= max_dist_sq) { + snake.emplace_back(pt); + snake_tail = pt; + } + + const int nbh_idx = node->prevNeighbourIdx(); + grid_offset += grid_offsets[nbh_idx]; + pt += QPoint(dx[nbh_idx], dy[nbh_idx]); + } + + return snake; } // TopBottomEdgeTracer::pathToSnake void TopBottomEdgeTracer::gaussBlurGradient(Grid& grid) { - using namespace boost::lambda; + using namespace boost::lambda; - gaussBlurGeneric(QSize(grid.width(), grid.height()), 2.0f, 2.0f, grid.data(), grid.stride(), - bind(&GridNode::absDirDeriv, _1), grid.data(), grid.stride(), bind(&GridNode::blurred, _1) = _2); + gaussBlurGeneric(QSize(grid.width(), grid.height()), 2.0f, 2.0f, grid.data(), grid.stride(), + bind(&GridNode::absDirDeriv, _1), grid.data(), grid.stride(), bind(&GridNode::blurred, _1) = _2); } Vec2f TopBottomEdgeTracer::downTheHillDirection(const QRectF& page_rect, const std::vector& snake, const Vec2f& bounds_dir) { - assert(!snake.empty()); - - // Take the centroid of a snake. - QPointF centroid; - for (const QPointF& pt : snake) { - centroid += pt; - } - centroid /= snake.size(); - - QLineF line(centroid, centroid + bounds_dir); - lineBoundedByRect(line, page_rect); - // The downhill direction is the direction *inside* the page. - const Vec2d v1(line.p1() - centroid); - const Vec2d v2(line.p2() - centroid); - if (v1.squaredNorm() > v2.squaredNorm()) { - return v1; - } else { - return v2; - } + assert(!snake.empty()); + + // Take the centroid of a snake. + QPointF centroid; + for (const QPointF& pt : snake) { + centroid += pt; + } + centroid /= snake.size(); + + QLineF line(centroid, centroid + bounds_dir); + lineBoundedByRect(line, page_rect); + // The downhill direction is the direction *inside* the page. + const Vec2d v1(line.p1() - centroid); + const Vec2d v2(line.p2() - centroid); + if (v1.squaredNorm() > v2.squaredNorm()) { + return v1; + } else { + return v2; + } } void TopBottomEdgeTracer::downTheHillSnake(std::vector& snake, const Grid& grid, const Vec2f dir) { - using namespace boost::lambda; + using namespace boost::lambda; - const size_t num_nodes = snake.size(); - if (num_nodes <= 1) { - return; - } + const size_t num_nodes = snake.size(); + if (num_nodes <= 1) { + return; + } - float avg_dist = 0; - for (size_t i = 1; i < num_nodes; ++i) { - const Vec2f vec(snake[i] - snake[i - 1]); - avg_dist += std::sqrt(vec.squaredNorm()); - } - avg_dist /= num_nodes - 1; - - std::vector step_storage; - - Vec2f displacements[9]; - const int num_displacements = initDisplacementVectors(displacements, dir); - - const float elasticity_weight = 0.6f; - const float bending_weight = 8.0f; - const float external_weight = 0.4f; - - const float segment_dist_threshold = 1; - - for (int iteration = 0; iteration < 40; ++iteration) { - step_storage.clear(); - - std::vector paths; - std::vector new_paths; - - for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { - const Vec2f pt(snake[node_idx]); - const float cur_external_energy - = interpolatedGridValue(grid, bind(&GridNode::blurred, _1), pt, 1000); - - for (int displacement_idx = 0; displacement_idx < num_displacements; ++displacement_idx) { - Step step; - step.prevStepIdx = ~uint32_t(0); - step.pt = pt + displacements[displacement_idx]; - step.pathCost = 0; - - const float adjusted_external_energy - = interpolatedGridValue(grid, bind(&GridNode::blurred, _1), step.pt, 1000); - if (displacement_idx == 0) { - step.pathCost += 100; - } else if (cur_external_energy < 0.01) { - if (cur_external_energy - adjusted_external_energy < 0.01f) { - continue; - } - } - - step.pathCost += external_weight * adjusted_external_energy; - - float best_cost = NumericTraits::max(); - uint32_t best_prev_step_idx = step.prevStepIdx; - - for (uint32_t prev_step_idx : paths) { - const Step& prev_step = step_storage[prev_step_idx]; - float cost = prev_step.pathCost + step.pathCost; - - const Vec2f vec(step.pt - prev_step.pt); - const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); - if (vec_len < segment_dist_threshold) { - cost += 1000; - } - - // Elasticity. - const auto dist_diff = std::fabs(avg_dist - vec_len); - cost += elasticity_weight * (dist_diff / avg_dist); - // Bending energy. - if ((prev_step.prevStepIdx != ~uint32_t(0)) && (vec_len >= segment_dist_threshold)) { - const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; - Vec2f prev_normal(prev_step.pt - prev_prev_step.pt); - std::swap(prev_normal[0], prev_normal[1]); - prev_normal[0] = -prev_normal[0]; - const auto prev_normal_len = static_cast(std::sqrt(prev_normal.squaredNorm())); - if (prev_normal_len < segment_dist_threshold) { - cost += 1000; - } else { - const float cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); - // cost += 0.7 * std::fabs(cos); - cost += bending_weight * cos * cos; - } - } - - assert(cost < NumericTraits::max()); - - if (cost < best_cost) { - best_cost = cost; - best_prev_step_idx = prev_step_idx; - } - } - - step.prevStepIdx = best_prev_step_idx; - if (best_prev_step_idx != ~uint32_t(0)) { - step.pathCost = best_cost; - } - - new_paths.push_back(static_cast(step_storage.size())); - step_storage.push_back(step); - } - assert(!new_paths.empty()); - paths.swap(new_paths); - new_paths.clear(); + float avg_dist = 0; + for (size_t i = 1; i < num_nodes; ++i) { + const Vec2f vec(snake[i] - snake[i - 1]); + avg_dist += std::sqrt(vec.squaredNorm()); + } + avg_dist /= num_nodes - 1; + + std::vector step_storage; + + Vec2f displacements[9]; + const int num_displacements = initDisplacementVectors(displacements, dir); + + const float elasticity_weight = 0.6f; + const float bending_weight = 8.0f; + const float external_weight = 0.4f; + + const float segment_dist_threshold = 1; + + for (int iteration = 0; iteration < 40; ++iteration) { + step_storage.clear(); + + std::vector paths; + std::vector new_paths; + + for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { + const Vec2f pt(snake[node_idx]); + const float cur_external_energy = interpolatedGridValue(grid, bind(&GridNode::blurred, _1), pt, 1000); + + for (int displacement_idx = 0; displacement_idx < num_displacements; ++displacement_idx) { + Step step; + step.prevStepIdx = ~uint32_t(0); + step.pt = pt + displacements[displacement_idx]; + step.pathCost = 0; + + const float adjusted_external_energy + = interpolatedGridValue(grid, bind(&GridNode::blurred, _1), step.pt, 1000); + if (displacement_idx == 0) { + step.pathCost += 100; + } else if (cur_external_energy < 0.01) { + if (cur_external_energy - adjusted_external_energy < 0.01f) { + continue; + } } - uint32_t best_path_idx = ~uint32_t(0); + step.pathCost += external_weight * adjusted_external_energy; + float best_cost = NumericTraits::max(); - for (uint32_t last_step_idx : paths) { - const Step& step = step_storage[last_step_idx]; - if (step.pathCost < best_cost) { - best_cost = step.pathCost; - best_path_idx = last_step_idx; + uint32_t best_prev_step_idx = step.prevStepIdx; + + for (uint32_t prev_step_idx : paths) { + const Step& prev_step = step_storage[prev_step_idx]; + float cost = prev_step.pathCost + step.pathCost; + + const Vec2f vec(step.pt - prev_step.pt); + const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); + if (vec_len < segment_dist_threshold) { + cost += 1000; + } + + // Elasticity. + const auto dist_diff = std::fabs(avg_dist - vec_len); + cost += elasticity_weight * (dist_diff / avg_dist); + // Bending energy. + if ((prev_step.prevStepIdx != ~uint32_t(0)) && (vec_len >= segment_dist_threshold)) { + const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; + Vec2f prev_normal(prev_step.pt - prev_prev_step.pt); + std::swap(prev_normal[0], prev_normal[1]); + prev_normal[0] = -prev_normal[0]; + const auto prev_normal_len = static_cast(std::sqrt(prev_normal.squaredNorm())); + if (prev_normal_len < segment_dist_threshold) { + cost += 1000; + } else { + const float cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); + // cost += 0.7 * std::fabs(cos); + cost += bending_weight * cos * cos; } + } + + assert(cost < NumericTraits::max()); + + if (cost < best_cost) { + best_cost = cost; + best_prev_step_idx = prev_step_idx; + } } - // Having found the best path, convert it back to a snake. - snake.clear(); - uint32_t step_idx = best_path_idx; - while (step_idx != ~uint32_t(0)) { - const Step& step = step_storage[step_idx]; - snake.push_back(step.pt); - step_idx = step.prevStepIdx; + + step.prevStepIdx = best_prev_step_idx; + if (best_prev_step_idx != ~uint32_t(0)) { + step.pathCost = best_cost; } - assert(num_nodes == snake.size()); - } + + new_paths.push_back(static_cast(step_storage.size())); + step_storage.push_back(step); + } + assert(!new_paths.empty()); + paths.swap(new_paths); + new_paths.clear(); + } + + uint32_t best_path_idx = ~uint32_t(0); + float best_cost = NumericTraits::max(); + for (uint32_t last_step_idx : paths) { + const Step& step = step_storage[last_step_idx]; + if (step.pathCost < best_cost) { + best_cost = step.pathCost; + best_path_idx = last_step_idx; + } + } + // Having found the best path, convert it back to a snake. + snake.clear(); + uint32_t step_idx = best_path_idx; + while (step_idx != ~uint32_t(0)) { + const Step& step = step_storage[step_idx]; + snake.push_back(step.pt); + step_idx = step.prevStepIdx; + } + assert(num_nodes == snake.size()); + } } // TopBottomEdgeTracer::downTheHillSnake void TopBottomEdgeTracer::upTheHillSnake(std::vector& snake, const Grid& grid, const Vec2f dir) { - using namespace boost::lambda; - - const size_t num_nodes = snake.size(); - if (num_nodes <= 1) { - return; - } + using namespace boost::lambda; + + const size_t num_nodes = snake.size(); + if (num_nodes <= 1) { + return; + } + + float avg_dist = 0; + for (size_t i = 1; i < num_nodes; ++i) { + const Vec2f vec(snake[i] - snake[i - 1]); + avg_dist += std::sqrt(vec.squaredNorm()); + } + avg_dist /= num_nodes - 1; + + std::vector step_storage; + + Vec2f displacements[9]; + const int num_displacements = initDisplacementVectors(displacements, dir); + for (int i = 0; i < num_displacements; ++i) { + // We need more accuracy here. + displacements[i] *= 0.5f; + } + + const float elasticity_weight = 0.6f; + const float bending_weight = 3.0f; + const float external_weight = 2.0f; + + const float segment_dist_threshold = 1; + + for (int iteration = 0; iteration < 40; ++iteration) { + step_storage.clear(); + + std::vector paths; + std::vector new_paths; + + for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { + const Vec2f pt(snake[node_idx]); + const float cur_external_energy = -interpolatedGridValue(grid, bind(&GridNode::absDirDeriv, _1), pt, 1000); + + for (int displacement_idx = 0; displacement_idx < num_displacements; ++displacement_idx) { + Step step; + step.prevStepIdx = ~uint32_t(0); + step.pt = pt + displacements[displacement_idx]; + step.pathCost = 0; + + const float adjusted_external_energy + = -interpolatedGridValue(grid, bind(&GridNode::absDirDeriv, _1), step.pt, 1000); + if ((displacement_idx == 0) && (adjusted_external_energy > -0.02)) { + // Discorage staying on the spot if the gradient magnitude is too + // small at that point. + step.pathCost += 100; + } - float avg_dist = 0; - for (size_t i = 1; i < num_nodes; ++i) { - const Vec2f vec(snake[i] - snake[i - 1]); - avg_dist += std::sqrt(vec.squaredNorm()); - } - avg_dist /= num_nodes - 1; + step.pathCost += external_weight * adjusted_external_energy; - std::vector step_storage; + float best_cost = NumericTraits::max(); + uint32_t best_prev_step_idx = step.prevStepIdx; + + for (uint32_t prev_step_idx : paths) { + const Step& prev_step = step_storage[prev_step_idx]; + float cost = prev_step.pathCost + step.pathCost; + + const Vec2f vec(step.pt - prev_step.pt); + const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); + if (vec_len < segment_dist_threshold) { + cost += 1000; + } + + // Elasticity. + const auto dist_diff = std::fabs(avg_dist - vec_len); + cost += elasticity_weight * (dist_diff / avg_dist); + // Bending energy. + if ((prev_step.prevStepIdx != ~uint32_t(0)) && (vec_len >= segment_dist_threshold)) { + const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; + Vec2f prev_normal(prev_step.pt - prev_prev_step.pt); + std::swap(prev_normal[0], prev_normal[1]); + prev_normal[0] = -prev_normal[0]; + const auto prev_normal_len = static_cast(std::sqrt(prev_normal.squaredNorm())); + if (prev_normal_len < segment_dist_threshold) { + cost += 1000; + } else { + const float cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); + // cost += 0.7 * std::fabs(cos); + cost += bending_weight * cos * cos; + } + } - Vec2f displacements[9]; - const int num_displacements = initDisplacementVectors(displacements, dir); - for (int i = 0; i < num_displacements; ++i) { - // We need more accuracy here. - displacements[i] *= 0.5f; - } + assert(cost < NumericTraits::max()); - const float elasticity_weight = 0.6f; - const float bending_weight = 3.0f; - const float external_weight = 2.0f; - - const float segment_dist_threshold = 1; - - for (int iteration = 0; iteration < 40; ++iteration) { - step_storage.clear(); - - std::vector paths; - std::vector new_paths; - - for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { - const Vec2f pt(snake[node_idx]); - const float cur_external_energy - = -interpolatedGridValue(grid, bind(&GridNode::absDirDeriv, _1), pt, 1000); - - for (int displacement_idx = 0; displacement_idx < num_displacements; ++displacement_idx) { - Step step; - step.prevStepIdx = ~uint32_t(0); - step.pt = pt + displacements[displacement_idx]; - step.pathCost = 0; - - const float adjusted_external_energy - = -interpolatedGridValue(grid, bind(&GridNode::absDirDeriv, _1), step.pt, 1000); - if ((displacement_idx == 0) && (adjusted_external_energy > -0.02)) { - // Discorage staying on the spot if the gradient magnitude is too - // small at that point. - step.pathCost += 100; - } - - step.pathCost += external_weight * adjusted_external_energy; - - float best_cost = NumericTraits::max(); - uint32_t best_prev_step_idx = step.prevStepIdx; - - for (uint32_t prev_step_idx : paths) { - const Step& prev_step = step_storage[prev_step_idx]; - float cost = prev_step.pathCost + step.pathCost; - - const Vec2f vec(step.pt - prev_step.pt); - const auto vec_len = static_cast(std::sqrt(vec.squaredNorm())); - if (vec_len < segment_dist_threshold) { - cost += 1000; - } - - // Elasticity. - const auto dist_diff = std::fabs(avg_dist - vec_len); - cost += elasticity_weight * (dist_diff / avg_dist); - // Bending energy. - if ((prev_step.prevStepIdx != ~uint32_t(0)) && (vec_len >= segment_dist_threshold)) { - const Step& prev_prev_step = step_storage[prev_step.prevStepIdx]; - Vec2f prev_normal(prev_step.pt - prev_prev_step.pt); - std::swap(prev_normal[0], prev_normal[1]); - prev_normal[0] = -prev_normal[0]; - const auto prev_normal_len = static_cast(std::sqrt(prev_normal.squaredNorm())); - if (prev_normal_len < segment_dist_threshold) { - cost += 1000; - } else { - const float cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); - // cost += 0.7 * std::fabs(cos); - cost += bending_weight * cos * cos; - } - } - - assert(cost < NumericTraits::max()); - - if (cost < best_cost) { - best_cost = cost; - best_prev_step_idx = prev_step_idx; - } - } - - step.prevStepIdx = best_prev_step_idx; - if (best_prev_step_idx != ~uint32_t(0)) { - step.pathCost = best_cost; - } - - new_paths.push_back(static_cast(step_storage.size())); - step_storage.push_back(step); - } - assert(!new_paths.empty()); - paths.swap(new_paths); - new_paths.clear(); + if (cost < best_cost) { + best_cost = cost; + best_prev_step_idx = prev_step_idx; + } } - uint32_t best_path_idx = ~uint32_t(0); - float best_cost = NumericTraits::max(); - for (uint32_t last_step_idx : paths) { - const Step& step = step_storage[last_step_idx]; - if (step.pathCost < best_cost) { - best_cost = step.pathCost; - best_path_idx = last_step_idx; - } + step.prevStepIdx = best_prev_step_idx; + if (best_prev_step_idx != ~uint32_t(0)) { + step.pathCost = best_cost; } - // Having found the best path, convert it back to a snake. - snake.clear(); - uint32_t step_idx = best_path_idx; - while (step_idx != ~uint32_t(0)) { - const Step& step = step_storage[step_idx]; - snake.push_back(step.pt); - step_idx = step.prevStepIdx; - } - assert(num_nodes == snake.size()); - } + + new_paths.push_back(static_cast(step_storage.size())); + step_storage.push_back(step); + } + assert(!new_paths.empty()); + paths.swap(new_paths); + new_paths.clear(); + } + + uint32_t best_path_idx = ~uint32_t(0); + float best_cost = NumericTraits::max(); + for (uint32_t last_step_idx : paths) { + const Step& step = step_storage[last_step_idx]; + if (step.pathCost < best_cost) { + best_cost = step.pathCost; + best_path_idx = last_step_idx; + } + } + // Having found the best path, convert it back to a snake. + snake.clear(); + uint32_t step_idx = best_path_idx; + while (step_idx != ~uint32_t(0)) { + const Step& step = step_storage[step_idx]; + snake.push_back(step.pt); + step_idx = step.prevStepIdx; + } + assert(num_nodes == snake.size()); + } } // TopBottomEdgeTracer::upTheHillSnake int TopBottomEdgeTracer::initDisplacementVectors(Vec2f vectors[], Vec2f valid_direction) { - int out_idx = 0; - // This one must always be present, and must be first, as we want to prefer it - // over another one with exactly the same score. - vectors[out_idx++] = Vec2f(0, 0); + int out_idx = 0; + // This one must always be present, and must be first, as we want to prefer it + // over another one with exactly the same score. + vectors[out_idx++] = Vec2f(0, 0); - static const float dx[] = {-1, 0, 1, -1, 1, -1, 0, 1}; + static const float dx[] = {-1, 0, 1, -1, 1, -1, 0, 1}; - static const float dy[] = {-1, -1, -1, 0, 0, 1, 1, 1}; + static const float dy[] = {-1, -1, -1, 0, 0, 1, 1, 1}; - for (int i = 0; i < 8; ++i) { - const Vec2f vec(dx[i], dy[i]); - if (vec.dot(valid_direction) > 0) { - vectors[out_idx++] = vec; - } + for (int i = 0; i < 8; ++i) { + const Vec2f vec(dx[i], dy[i]); + if (vec.dot(valid_direction) > 0) { + vectors[out_idx++] = vec; } + } - return out_idx; + return out_idx; } QImage TopBottomEdgeTracer::visualizeGradient(const Grid& grid, const QImage* background) { - const int width = grid.width(); - const int height = grid.height(); - const int grid_stride = grid.stride(); - // First let's find the maximum and minimum values. - float min_value = NumericTraits::max(); - float max_value = NumericTraits::min(); - - const GridNode* grid_line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grid_line[x].dirDeriv; - if (value < min_value) { - min_value = value; - } else if (value > max_value) { - max_value = value; - } - } - grid_line += grid_stride; - } - - float scale = std::max(max_value, -min_value); - if (scale > std::numeric_limits::epsilon()) { - scale = 255.0f / scale; - } - - QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); - auto* overlay_line = (uint32_t*) overlay.bits(); - const int overlay_stride = overlay.bytesPerLine() / 4; - - grid_line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grid_line[x].dirDeriv * scale; - const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); - if (value > 0) { - // Red for positive gradients which indicate bottom edges. - overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); - } else { - overlay_line[x] = qRgba(0, 0, magnitude, magnitude); - } - } - grid_line += grid_stride; - overlay_line += overlay_stride; - } - - QImage canvas; - if (background) { - canvas = background->convertToFormat(QImage::Format_ARGB32_Premultiplied); - } else { - canvas = QImage(width, height, QImage::Format_ARGB32_Premultiplied); - canvas.fill(0xffffffff); // Opaque white. - } + const int width = grid.width(); + const int height = grid.height(); + const int grid_stride = grid.stride(); + // First let's find the maximum and minimum values. + float min_value = NumericTraits::max(); + float max_value = NumericTraits::min(); + + const GridNode* grid_line = grid.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grid_line[x].dirDeriv; + if (value < min_value) { + min_value = value; + } else if (value > max_value) { + max_value = value; + } + } + grid_line += grid_stride; + } + + float scale = std::max(max_value, -min_value); + if (scale > std::numeric_limits::epsilon()) { + scale = 255.0f / scale; + } + + QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); + auto* overlay_line = (uint32_t*) overlay.bits(); + const int overlay_stride = overlay.bytesPerLine() / 4; + + grid_line = grid.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grid_line[x].dirDeriv * scale; + const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); + if (value > 0) { + // Red for positive gradients which indicate bottom edges. + overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); + } else { + overlay_line[x] = qRgba(0, 0, magnitude, magnitude); + } + } + grid_line += grid_stride; + overlay_line += overlay_stride; + } + + QImage canvas; + if (background) { + canvas = background->convertToFormat(QImage::Format_ARGB32_Premultiplied); + } else { + canvas = QImage(width, height, QImage::Format_ARGB32_Premultiplied); + canvas.fill(0xffffffff); // Opaque white. + } - QPainter painter(&canvas); - painter.drawImage(0, 0, overlay); + QPainter painter(&canvas); + painter.drawImage(0, 0, overlay); - return canvas; + return canvas; } // TopBottomEdgeTracer::visualizeGradient QImage TopBottomEdgeTracer::visualizeBlurredGradient(const Grid& grid) { - const int width = grid.width(); - const int height = grid.height(); - const int grid_stride = grid.stride(); - // First let's find the maximum and minimum values. - float min_value = NumericTraits::max(); - float max_value = NumericTraits::min(); - - const GridNode* grid_line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grid_line[x].blurred; - if (value < min_value) { - min_value = value; - } else if (value > max_value) { - max_value = value; - } - } - grid_line += grid_stride; - } - - float scale = std::max(max_value, -min_value); - if (scale > std::numeric_limits::epsilon()) { - scale = 255.0f / scale; - } - - QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); - auto* overlay_line = (uint32_t*) overlay.bits(); - const int overlay_stride = overlay.bytesPerLine() / 4; - - grid_line = grid.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const float value = grid_line[x].blurred * scale; - const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); - overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); - } - grid_line += grid_stride; - overlay_line += overlay_stride; + const int width = grid.width(); + const int height = grid.height(); + const int grid_stride = grid.stride(); + // First let's find the maximum and minimum values. + float min_value = NumericTraits::max(); + float max_value = NumericTraits::min(); + + const GridNode* grid_line = grid.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grid_line[x].blurred; + if (value < min_value) { + min_value = value; + } else if (value > max_value) { + max_value = value; + } + } + grid_line += grid_stride; + } + + float scale = std::max(max_value, -min_value); + if (scale > std::numeric_limits::epsilon()) { + scale = 255.0f / scale; + } + + QImage overlay(width, height, QImage::Format_ARGB32_Premultiplied); + auto* overlay_line = (uint32_t*) overlay.bits(); + const int overlay_stride = overlay.bytesPerLine() / 4; + + grid_line = grid.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const float value = grid_line[x].blurred * scale; + const int magnitude = qBound(0, static_cast(std::round(std::fabs(value))), 255); + overlay_line[x] = qRgba(magnitude, 0, 0, magnitude); } + grid_line += grid_stride; + overlay_line += overlay_stride; + } - QImage canvas(grid.width(), grid.height(), QImage::Format_ARGB32_Premultiplied); - canvas.fill(0xffffffff); // Opaque white. - QPainter painter(&canvas); - painter.drawImage(0, 0, overlay); + QImage canvas(grid.width(), grid.height(), QImage::Format_ARGB32_Premultiplied); + canvas.fill(0xffffffff); // Opaque white. + QPainter painter(&canvas); + painter.drawImage(0, 0, overlay); - return canvas; + return canvas; } // TopBottomEdgeTracer::visualizeBlurredGradient QImage TopBottomEdgeTracer::visualizePaths(const QImage& background, const Grid& grid, const std::pair& bounds, const std::vector& path_endpoints) { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - auto* const canvas_data = (uint32_t*) canvas.bits(); - const int canvas_stride = canvas.bytesPerLine() / 4; - - const int width = grid.width(); - const int height = grid.height(); - const int grid_stride = grid.stride(); - const GridNode* const grid_data = grid.data(); - - const int nbh_canvas_offsets[8] = {-canvas_stride - 1, -canvas_stride, -canvas_stride + 1, -1, +1, - +canvas_stride - 1, +canvas_stride, +canvas_stride + 1}; - const int nbh_grid_offsets[8] = {-grid_stride - 1, -grid_stride, -grid_stride + 1, -1, +1, - +grid_stride - 1, +grid_stride, +grid_stride + 1}; - - for (const QPoint path_endpoint : path_endpoints) { - int grid_offset = path_endpoint.x() + path_endpoint.y() * grid_stride; - int canvas_offset = path_endpoint.x() + path_endpoint.y() * canvas_stride; - for (;;) { - const GridNode* node = grid_data + grid_offset; - canvas_data[canvas_offset] = 0x00ff0000; - if (!node->hasPathContinuation()) { - break; - } - - const int nbh_idx = node->prevNeighbourIdx(); - grid_offset += nbh_grid_offsets[nbh_idx]; - canvas_offset += nbh_canvas_offsets[nbh_idx]; - } - } - - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - QPen pen(Qt::blue); - pen.setWidthF(1.0); - painter.setPen(pen); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); - - return canvas; + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + auto* const canvas_data = (uint32_t*) canvas.bits(); + const int canvas_stride = canvas.bytesPerLine() / 4; + + const int width = grid.width(); + const int height = grid.height(); + const int grid_stride = grid.stride(); + const GridNode* const grid_data = grid.data(); + + const int nbh_canvas_offsets[8] = {-canvas_stride - 1, -canvas_stride, -canvas_stride + 1, -1, +1, + +canvas_stride - 1, +canvas_stride, +canvas_stride + 1}; + const int nbh_grid_offsets[8] + = {-grid_stride - 1, -grid_stride, -grid_stride + 1, -1, +1, +grid_stride - 1, +grid_stride, +grid_stride + 1}; + + for (const QPoint path_endpoint : path_endpoints) { + int grid_offset = path_endpoint.x() + path_endpoint.y() * grid_stride; + int canvas_offset = path_endpoint.x() + path_endpoint.y() * canvas_stride; + while (true) { + const GridNode* node = grid_data + grid_offset; + canvas_data[canvas_offset] = 0x00ff0000; + if (!node->hasPathContinuation()) { + break; + } + + const int nbh_idx = node->prevNeighbourIdx(); + grid_offset += nbh_grid_offsets[nbh_idx]; + canvas_offset += nbh_canvas_offsets[nbh_idx]; + } + } + + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(Qt::blue); + pen.setWidthF(1.0); + painter.setPen(pen); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); + + return canvas; } // TopBottomEdgeTracer::visualizePaths QImage TopBottomEdgeTracer::visualizePaths(const QImage& background, const std::vector>& paths, const std::pair& bounds) { - QImage canvas(background.convertToFormat(QImage::Format_RGB32)); - auto* const canvas_data = (uint32_t*) canvas.bits(); - const int canvas_stride = canvas.bytesPerLine() / 4; + QImage canvas(background.convertToFormat(QImage::Format_RGB32)); + auto* const canvas_data = (uint32_t*) canvas.bits(); + const int canvas_stride = canvas.bytesPerLine() / 4; - for (const std::vector& path : paths) { - for (QPoint pt : path) { - canvas_data[pt.x() + pt.y() * canvas_stride] = 0x00ff0000; - } + for (const std::vector& path : paths) { + for (QPoint pt : path) { + canvas_data[pt.x() + pt.y() * canvas_stride] = 0x00ff0000; } + } - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - QPen pen(Qt::blue); - pen.setWidthF(1.0); - painter.setPen(pen); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(Qt::blue); + pen.setWidthF(1.0); + painter.setPen(pen); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); - return canvas; + return canvas; } QImage TopBottomEdgeTracer::visualizeSnakes(const QImage& background, const std::vector>& snakes, const std::pair& bounds) { - QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); + QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); - QPen snake_pen(QColor(0, 255, 0)); - snake_pen.setWidthF(1.5); + QPen snake_pen(QColor(0, 255, 0)); + snake_pen.setWidthF(1.5); - QBrush knot_brush(QColor(255, 255, 0, 180)); - painter.setBrush(knot_brush); + QBrush knot_brush(QColor(255, 255, 0, 180)); + painter.setBrush(knot_brush); - QRectF knot_rect(0, 0, 7, 7); + QRectF knot_rect(0, 0, 7, 7); - for (const std::vector& snake : snakes) { - if (snake.empty()) { - continue; - } + for (const std::vector& snake : snakes) { + if (snake.empty()) { + continue; + } - painter.setPen(snake_pen); - painter.drawPolyline(&snake[0], static_cast(snake.size())); - painter.setPen(Qt::NoPen); - for (const QPointF& knot : snake) { - knot_rect.moveCenter(knot); - painter.drawEllipse(knot_rect); - } + painter.setPen(snake_pen); + painter.drawPolyline(&snake[0], static_cast(snake.size())); + painter.setPen(Qt::NoPen); + for (const QPointF& knot : snake) { + knot_rect.moveCenter(knot); + painter.drawEllipse(knot_rect); } + } - QPen bounds_pen(Qt::blue); - bounds_pen.setWidthF(1.5); - painter.setPen(bounds_pen); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); + QPen bounds_pen(Qt::blue); + bounds_pen.setWidthF(1.5); + painter.setPen(bounds_pen); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); - return canvas; + return canvas; } // TopBottomEdgeTracer::visualizeSnakes QImage TopBottomEdgeTracer::visualizePolylines(const QImage& background, const std::list>& polylines, const std::pair& bounds) { - QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing); - - QPen polyline_pen(QColor(255, 0, 0, 100)); - polyline_pen.setWidthF(4.0); - painter.setPen(polyline_pen); + QImage canvas(background.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); - for (const std::vector& polyline : polylines) { - if (polyline.empty()) { - continue; - } + QPen polyline_pen(QColor(255, 0, 0, 100)); + polyline_pen.setWidthF(4.0); + painter.setPen(polyline_pen); - painter.drawPolyline(&polyline[0], static_cast(polyline.size())); + for (const std::vector& polyline : polylines) { + if (polyline.empty()) { + continue; } - QPen bounds_pen(Qt::blue); - bounds_pen.setWidthF(1.5); - painter.setPen(bounds_pen); - painter.drawLine(bounds.first); - painter.drawLine(bounds.second); + painter.drawPolyline(&polyline[0], static_cast(polyline.size())); + } + + QPen bounds_pen(Qt::blue); + bounds_pen.setWidthF(1.5); + painter.setPen(bounds_pen); + painter.drawLine(bounds.first); + painter.drawLine(bounds.second); - return canvas; + return canvas; } } // namespace dewarping \ No newline at end of file diff --git a/dewarping/TopBottomEdgeTracer.h b/dewarping/TopBottomEdgeTracer.h index 8cec394f7..18cc381b4 100644 --- a/dewarping/TopBottomEdgeTracer.h +++ b/dewarping/TopBottomEdgeTracer.h @@ -19,14 +19,14 @@ #ifndef DEWARPING_TOP_BOTTOM_EDGE_TRACER_H_ #define DEWARPING_TOP_BOTTOM_EDGE_TRACER_H_ -#include "Grid.h" -#include "VecNT.h" -#include #include +#include #include #include #include #include +#include "Grid.h" +#include "VecNT.h" class TaskStatus; class DebugImages; @@ -41,83 +41,83 @@ namespace dewarping { class DistortionModelBuilder; class TopBottomEdgeTracer { -public: - static void trace(const imageproc::GrayImage& image, - std::pair bounds, - DistortionModelBuilder& output, - const TaskStatus& status, - DebugImages* dbg = nullptr); + public: + static void trace(const imageproc::GrayImage& image, + std::pair bounds, + DistortionModelBuilder& output, + const TaskStatus& status, + DebugImages* dbg = nullptr); -private: - struct GridNode; + private: + struct GridNode; - class PrioQueue; + class PrioQueue; - struct Step; + struct Step; - static bool intersectWithRect(std::pair& bounds, const QRectF& rect); + static bool intersectWithRect(std::pair& bounds, const QRectF& rect); - static void forceSameDirection(std::pair& bounds); + static void forceSameDirection(std::pair& bounds); - static void calcDirectionalDerivative(Grid& gradient, - const imageproc::GrayImage& image, - const Vec2f& direction); + static void calcDirectionalDerivative(Grid& gradient, + const imageproc::GrayImage& image, + const Vec2f& direction); - static void horizontalSobelInPlace(Grid& grid); + static void horizontalSobelInPlace(Grid& grid); - static void verticalSobelInPlace(Grid& grid); + static void verticalSobelInPlace(Grid& grid); - static Vec2f calcAvgUnitVector(const std::pair& bounds); + static Vec2f calcAvgUnitVector(const std::pair& bounds); - static Vec2f directionFromPointToLine(const QPointF& pt, const QLineF& line); + static Vec2f directionFromPointToLine(const QPointF& pt, const QLineF& line); - static void prepareForShortestPathsFrom(PrioQueue& queue, Grid& grid, const QLineF& from); + static void prepareForShortestPathsFrom(PrioQueue& queue, Grid& grid, const QLineF& from); - static void propagateShortestPaths(const Vec2f& direction, PrioQueue& queue, Grid& grid); + static void propagateShortestPaths(const Vec2f& direction, PrioQueue& queue, Grid& grid); - static int initNeighbours(int* next_nbh_offsets, int* prev_nbh_indexes, int stride, const Vec2f& direction); + static int initNeighbours(int* next_nbh_offsets, int* prev_nbh_indexes, int stride, const Vec2f& direction); - static std::vector locateBestPathEndpoints(const Grid& grid, const QLineF& line); + static std::vector locateBestPathEndpoints(const Grid& grid, const QLineF& line); - static std::vector tracePathFromEndpoint(const Grid& grid, const QPoint& endpoint); + static std::vector tracePathFromEndpoint(const Grid& grid, const QPoint& endpoint); - static std::vector pathToSnake(const Grid& grid, const QPoint& endpoint); + static std::vector pathToSnake(const Grid& grid, const QPoint& endpoint); - static void gaussBlurGradient(Grid& grid); + static void gaussBlurGradient(Grid& grid); - static Vec2f downTheHillDirection(const QRectF& page_rect, - const std::vector& snake, - const Vec2f& bounds_dir); + static Vec2f downTheHillDirection(const QRectF& page_rect, + const std::vector& snake, + const Vec2f& bounds_dir); - static void downTheHillSnake(std::vector& snake, const Grid& grid, Vec2f dir); + static void downTheHillSnake(std::vector& snake, const Grid& grid, Vec2f dir); - static void upTheHillSnake(std::vector& snake, const Grid& grid, Vec2f dir); + static void upTheHillSnake(std::vector& snake, const Grid& grid, Vec2f dir); - static int initDisplacementVectors(Vec2f vectors[], Vec2f valid_direction); + static int initDisplacementVectors(Vec2f vectors[], Vec2f valid_direction); - template - static float interpolatedGridValue(const Grid& grid, Extractor extractor, Vec2f pos, float default_value); + template + static float interpolatedGridValue(const Grid& grid, Extractor extractor, Vec2f pos, float default_value); - static QImage visualizeGradient(const Grid& grid, const QImage* background = nullptr); + static QImage visualizeGradient(const Grid& grid, const QImage* background = nullptr); - static QImage visualizeBlurredGradient(const Grid& grid); + static QImage visualizeBlurredGradient(const Grid& grid); - static QImage visualizePaths(const QImage& background, - const Grid& grid, - const std::pair& bounds, - const std::vector& path_endpoints); + static QImage visualizePaths(const QImage& background, + const Grid& grid, + const std::pair& bounds, + const std::vector& path_endpoints); - static QImage visualizePaths(const QImage& background, - const std::vector>& paths, - const std::pair& bounds); + static QImage visualizePaths(const QImage& background, + const std::vector>& paths, + const std::pair& bounds); - static QImage visualizeSnakes(const QImage& background, - const std::vector>& snakes, - const std::pair& bounds); + static QImage visualizeSnakes(const QImage& background, + const std::vector>& snakes, + const std::pair& bounds); - static QImage visualizePolylines(const QImage& background, - const std::list>& snakes, - const std::pair& bounds); + static QImage visualizePolylines(const QImage& background, + const std::list>& snakes, + const std::pair& bounds); }; } // namespace dewarping #endif // ifndef DEWARPING_TOP_BOTTOM_EDGE_TRACER_H_ diff --git a/dewarping/TowardsLineTracer.cpp b/dewarping/TowardsLineTracer.cpp index 598cd8696..84bf9abf4 100644 --- a/dewarping/TowardsLineTracer.cpp +++ b/dewarping/TowardsLineTracer.cpp @@ -17,13 +17,13 @@ */ #include "TowardsLineTracer.h" -#include "SidesOfLine.h" -#include "NumericTraits.h" -#include "imageproc/SEDM.h" #include -#include #include +#include #include +#include "NumericTraits.h" +#include "SidesOfLine.h" +#include "imageproc/SEDM.h" using namespace imageproc; @@ -32,145 +32,145 @@ TowardsLineTracer::TowardsLineTracer(const imageproc::SEDM* dm, const Grid* pm, const QLineF& line, const QPoint& initial_pos) - : m_pDmData(dm->data()), - m_dmStride(dm->stride()), - m_pPmData(pm->data()), - m_pmStride(pm->stride()), - m_rect(QPoint(0, 0), dm->size()), - m_line(line), - m_normalTowardsLine(m_line.normalVector().p2() - m_line.p1()), - m_lastOutputPos(initial_pos), - m_numSteps(0), - m_finished(false) { - if (sidesOfLine(m_line, initial_pos, line.p1() + m_normalTowardsLine) > 0) { - // It points the wrong way -> fix that. - m_normalTowardsLine = -m_normalTowardsLine; - } - - setupSteps(); + : m_dmData(dm->data()), + m_dmStride(dm->stride()), + m_pmData(pm->data()), + m_pmStride(pm->stride()), + m_rect(QPoint(0, 0), dm->size()), + m_line(line), + m_normalTowardsLine(m_line.normalVector().p2() - m_line.p1()), + m_lastOutputPos(initial_pos), + m_numSteps(0), + m_finished(false) { + if (sidesOfLine(m_line, initial_pos, line.p1() + m_normalTowardsLine) > 0) { + // It points the wrong way -> fix that. + m_normalTowardsLine = -m_normalTowardsLine; + } + + setupSteps(); } const QPoint* TowardsLineTracer::trace(const float max_dist) { - if (m_finished) { - return nullptr; + if (m_finished) { + return nullptr; + } + + const int max_sqdist = qRound(max_dist * max_dist); + + QPoint cur_pos(m_lastOutputPos); + QPoint last_content_pos(-1, -1); + + const uint32_t* p_dm = m_dmData + cur_pos.y() * m_dmStride + cur_pos.x(); + const float* p_pm = m_pmData + cur_pos.y() * m_pmStride + cur_pos.x(); + + while (true) { + int best_dm_idx = -1; + int best_pm_idx = -1; + uint32_t best_squared_dist = 0; + float best_probability = NumericTraits::min(); + + for (int i = 0; i < m_numSteps; ++i) { + const Step& step = m_steps[i]; + const QPoint new_pos(cur_pos + step.vec); + if (!m_rect.contains(new_pos)) { + continue; + } + + const uint32_t sqd = p_dm[step.dmOffset]; + if (sqd > best_squared_dist) { + best_squared_dist = sqd; + best_dm_idx = i; + } + const float probability = p_pm[step.pmOffset]; + if (probability > best_probability) { + best_probability = probability; + best_pm_idx = i; + } } - const int max_sqdist = qRound(max_dist * max_dist); - - QPoint cur_pos(m_lastOutputPos); - QPoint last_content_pos(-1, -1); - - const uint32_t* p_dm = m_pDmData + cur_pos.y() * m_dmStride + cur_pos.x(); - const float* p_pm = m_pPmData + cur_pos.y() * m_pmStride + cur_pos.x(); - - for (;;) { - int best_dm_idx = -1; - int best_pm_idx = -1; - uint32_t best_squared_dist = 0; - float best_probability = NumericTraits::min(); - - for (int i = 0; i < m_numSteps; ++i) { - const Step& step = m_steps[i]; - const QPoint new_pos(cur_pos + step.vec); - if (!m_rect.contains(new_pos)) { - continue; - } - - const uint32_t sqd = p_dm[step.dmOffset]; - if (sqd > best_squared_dist) { - best_squared_dist = sqd; - best_dm_idx = i; - } - const float probability = p_pm[step.pmOffset]; - if (probability > best_probability) { - best_probability = probability; - best_pm_idx = i; - } - } - - if (best_dm_idx == -1) { - m_finished = true; - break; - } - assert(best_pm_idx != -1); - - int best_idx = best_pm_idx; - if (p_dm[m_steps[best_dm_idx].dmOffset] > *p_dm) { - best_idx = best_dm_idx; - } - - Step& step = m_steps[best_idx]; - - if (sidesOfLine(m_line, cur_pos + step.vec, m_lastOutputPos) < 0) { - // Note that this has to be done before we update cur_pos, - // as it will be used after breaking from this loop. - m_finished = true; - break; - } - - cur_pos += step.vec; - p_dm += step.dmOffset; - p_pm += step.pmOffset; - - const QPoint vec(cur_pos - m_lastOutputPos); - if (vec.x() * vec.x() + vec.y() * vec.y() > max_sqdist) { - m_lastOutputPos = cur_pos; - - return &m_lastOutputPos; - } + if (best_dm_idx == -1) { + m_finished = true; + break; } + assert(best_pm_idx != -1); - if (m_lastOutputPos != cur_pos) { - m_lastOutputPos = cur_pos; + int best_idx = best_pm_idx; + if (p_dm[m_steps[best_dm_idx].dmOffset] > *p_dm) { + best_idx = best_dm_idx; + } + + Step& step = m_steps[best_idx]; - return &m_lastOutputPos; - } else { - return nullptr; + if (sidesOfLine(m_line, cur_pos + step.vec, m_lastOutputPos) < 0) { + // Note that this has to be done before we update cur_pos, + // as it will be used after breaking from this loop. + m_finished = true; + break; } -} // TowardsLineTracer::trace -void TowardsLineTracer::setupSteps() { - QPoint all_directions[8]; - // all_directions[0] is north-west, and then clockwise from there. - static const int m0p[] = {-1, 0, 1}; - static const int p0m[] = {1, 0, -1}; - - for (int i = 0; i < 3; ++i) { - // north - all_directions[i].setX(m0p[i]); - all_directions[i].setY(-1); - - // east - all_directions[2 + i].setX(1); - all_directions[2 + i].setY(m0p[i]); - - // south - all_directions[4 + i].setX(p0m[i]); - all_directions[4 + i].setY(1); - - // west - all_directions[(6 + i) & 7].setX(-1); - all_directions[(6 + i) & 7].setY(p0m[i]); + cur_pos += step.vec; + p_dm += step.dmOffset; + p_pm += step.pmOffset; + + const QPoint vec(cur_pos - m_lastOutputPos); + if (vec.x() * vec.x() + vec.y() * vec.y() > max_sqdist) { + m_lastOutputPos = cur_pos; + + return &m_lastOutputPos; } + } + + if (m_lastOutputPos != cur_pos) { + m_lastOutputPos = cur_pos; - m_numSteps = 0; - for (const QPoint dir : all_directions) { - if (m_normalTowardsLine.dot(QPointF(dir)) > 0.0) { - Step& step = m_steps[m_numSteps]; - step.vec = dir; - step.unitVec = Vec2d(step.vec.x(), step.vec.y()); - step.unitVec /= std::sqrt(step.unitVec.squaredNorm()); - step.dmOffset = step.vec.y() * m_dmStride + step.vec.x(); - step.pmOffset = step.vec.y() * m_pmStride + step.vec.x(); - ++m_numSteps; - assert(m_numSteps <= int(sizeof(m_steps) / sizeof(m_steps[0]))); - } + return &m_lastOutputPos; + } else { + return nullptr; + } +} // TowardsLineTracer::trace + +void TowardsLineTracer::setupSteps() { + QPoint all_directions[8]; + // all_directions[0] is north-west, and then clockwise from there. + static const int m0p[] = {-1, 0, 1}; + static const int p0m[] = {1, 0, -1}; + + for (int i = 0; i < 3; ++i) { + // north + all_directions[i].setX(m0p[i]); + all_directions[i].setY(-1); + + // east + all_directions[2 + i].setX(1); + all_directions[2 + i].setY(m0p[i]); + + // south + all_directions[4 + i].setX(p0m[i]); + all_directions[4 + i].setY(1); + + // west + all_directions[(6 + i) & 7].setX(-1); + all_directions[(6 + i) & 7].setY(p0m[i]); + } + + m_numSteps = 0; + for (const QPoint dir : all_directions) { + if (m_normalTowardsLine.dot(QPointF(dir)) > 0.0) { + Step& step = m_steps[m_numSteps]; + step.vec = dir; + step.unitVec = Vec2d(step.vec.x(), step.vec.y()); + step.unitVec /= std::sqrt(step.unitVec.squaredNorm()); + step.dmOffset = step.vec.y() * m_dmStride + step.vec.x(); + step.pmOffset = step.vec.y() * m_pmStride + step.vec.x(); + ++m_numSteps; + assert(m_numSteps <= int(sizeof(m_steps) / sizeof(m_steps[0]))); } + } - // Sort by decreasing alignment with m_normalTowardsLine. - using namespace boost::lambda; - std::sort(m_steps, m_steps + m_numSteps, - bind(&Vec2d::dot, m_normalTowardsLine, bind(&Step::unitVec, _1)) - > bind(&Vec2d::dot, m_normalTowardsLine, bind(&Step::unitVec, _2))); + // Sort by decreasing alignment with m_normalTowardsLine. + using namespace boost::lambda; + std::sort(m_steps, m_steps + m_numSteps, + bind(&Vec2d::dot, m_normalTowardsLine, bind(&Step::unitVec, _1)) + > bind(&Vec2d::dot, m_normalTowardsLine, bind(&Step::unitVec, _2))); } // TowardsLineTracer::setupSteps } // namespace dewarping \ No newline at end of file diff --git a/dewarping/TowardsLineTracer.h b/dewarping/TowardsLineTracer.h index 0ae3ae280..d6e596473 100644 --- a/dewarping/TowardsLineTracer.h +++ b/dewarping/TowardsLineTracer.h @@ -19,12 +19,12 @@ #ifndef DEWARPING_TOWARDS_LINE_TRACER_H_ #define DEWARPING_TOWARDS_LINE_TRACER_H_ -#include "VecNT.h" -#include "Grid.h" +#include #include #include -#include #include +#include "Grid.h" +#include "VecNT.h" namespace imageproc { class SEDM; @@ -35,32 +35,32 @@ namespace dewarping { * This class is used for tracing a path towards intersection with a given line. */ class TowardsLineTracer { -public: - TowardsLineTracer(const imageproc::SEDM* dm, const Grid* pm, const QLineF& line, const QPoint& initial_pos); + public: + TowardsLineTracer(const imageproc::SEDM* dm, const Grid* pm, const QLineF& line, const QPoint& initial_pos); - const QPoint* trace(float max_dist); + const QPoint* trace(float max_dist); -private: - struct Step { - Vec2d unitVec; - QPoint vec; - int dmOffset{}; - int pmOffset{}; - }; + private: + struct Step { + Vec2d unitVec; + QPoint vec; + int dmOffset{}; + int pmOffset{}; + }; - void setupSteps(); + void setupSteps(); - const uint32_t* m_pDmData; - int m_dmStride; - const float* m_pPmData; - int m_pmStride; - QRect m_rect; - QLineF m_line; - Vec2d m_normalTowardsLine; - QPoint m_lastOutputPos; - Step m_steps[5]; - int m_numSteps; - bool m_finished; + const uint32_t* m_dmData; + int m_dmStride; + const float* m_pmData; + int m_pmStride; + QRect m_rect; + QLineF m_line; + Vec2d m_normalTowardsLine; + QPoint m_lastOutputPos; + Step m_steps[5]; + int m_numSteps; + bool m_finished; }; } // namespace dewarping #endif // ifndef DEWARPING_TOWARDS_LINE_TRACER_H_ diff --git a/filter_dc/AbstractFilterDataCollector.h b/filter_dc/AbstractFilterDataCollector.h index 3ebf82b0d..6a09902d2 100644 --- a/filter_dc/AbstractFilterDataCollector.h +++ b/filter_dc/AbstractFilterDataCollector.h @@ -20,8 +20,8 @@ #define ABSTRACTFILTERDATACOLLECTOR_H_ class AbstractFilterDataCollector { -public: - virtual ~AbstractFilterDataCollector() = default; + public: + virtual ~AbstractFilterDataCollector() = default; }; diff --git a/filter_dc/ContentBoxCollector.h b/filter_dc/ContentBoxCollector.h index ab8be5cb1..02f8acf55 100644 --- a/filter_dc/ContentBoxCollector.h +++ b/filter_dc/ContentBoxCollector.h @@ -25,8 +25,8 @@ class ImageTransformation; class QRectF; class ContentBoxCollector : public AbstractFilterDataCollector { -public: - virtual void process(const ImageTransformation& xform, const QRectF& content_rect) = 0; + public: + virtual void process(const ImageTransformation& xform, const QRectF& content_rect) = 0; }; diff --git a/filter_dc/PageOrientationCollector.h b/filter_dc/PageOrientationCollector.h index 366b67919..db68b65aa 100644 --- a/filter_dc/PageOrientationCollector.h +++ b/filter_dc/PageOrientationCollector.h @@ -24,8 +24,8 @@ class OrthogonalRotation; class PageOrientationCollector : public AbstractFilterDataCollector { -public: - virtual void process(const OrthogonalRotation& orientation) = 0; + public: + virtual void process(const OrthogonalRotation& orientation) = 0; }; diff --git a/filter_dc/ThumbnailCollector.h b/filter_dc/ThumbnailCollector.h index ccda21da5..9679c31d9 100644 --- a/filter_dc/ThumbnailCollector.h +++ b/filter_dc/ThumbnailCollector.h @@ -19,20 +19,20 @@ #ifndef THUMBNAILCOLLECTOR_H_ #define THUMBNAILCOLLECTOR_H_ -#include "AbstractFilterDataCollector.h" #include +#include "AbstractFilterDataCollector.h" class ThumbnailPixmapCache; class QGraphicsItem; class QSizeF; class ThumbnailCollector : public AbstractFilterDataCollector { -public: - virtual void processThumbnail(std::unique_ptr) = 0; + public: + virtual void processThumbnail(std::unique_ptr) = 0; - virtual intrusive_ptr thumbnailCache() = 0; + virtual intrusive_ptr thumbnailCache() = 0; - virtual QSizeF maxLogicalThumbSize() const = 0; + virtual QSizeF maxLogicalThumbSize() const = 0; }; diff --git a/filters/deskew/ApplyDialog.cpp b/filters/deskew/ApplyDialog.cpp index b5af69fb8..f9031be7b 100644 --- a/filters/deskew/ApplyDialog.cpp +++ b/filters/deskew/ApplyDialog.cpp @@ -16,70 +16,69 @@ */ #include "ApplyDialog.h" -#include "PageSelectionAccessor.h" - #include +#include "PageSelectionAccessor.h" namespace deskew { ApplyDialog::ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_curPage(cur_page), - m_selectedPages(page_selection_accessor.selectedPages()), - m_pScopeGroup(new QButtonGroup(this)) { - setupUi(this); - m_pScopeGroup->addButton(thisPageRB); - m_pScopeGroup->addButton(allPagesRB); - m_pScopeGroup->addButton(thisPageAndFollowersRB); - m_pScopeGroup->addButton(everyOtherRB); - m_pScopeGroup->addButton(thisEveryOtherRB); - m_pScopeGroup->addButton(selectedPagesRB); - m_pScopeGroup->addButton(everyOtherSelectedRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - everyOtherSelectedRB->setEnabled(false); - everyOtherSelectedHint->setEnabled(false); - } + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_curPage(cur_page), + m_selectedPages(page_selection_accessor.selectedPages()), + m_scopeGroup(new QButtonGroup(this)) { + setupUi(this); + m_scopeGroup->addButton(thisPageRB); + m_scopeGroup->addButton(allPagesRB); + m_scopeGroup->addButton(thisPageAndFollowersRB); + m_scopeGroup->addButton(everyOtherRB); + m_scopeGroup->addButton(thisEveryOtherRB); + m_scopeGroup->addButton(selectedPagesRB); + m_scopeGroup->addButton(everyOtherSelectedRB); + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + everyOtherSelectedRB->setEnabled(false); + everyOtherSelectedHint->setEnabled(false); + } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() = default; void ApplyDialog::onSubmit() { - std::set pages; - // thisPageRB is intentionally not handled. - if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - emit appliedToAllPages(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - emit appliedTo(pages); - } else if (selectedPagesRB->isChecked()) { - emit appliedTo(m_selectedPages); - } else if (everyOtherRB->isChecked()) { - m_pages.selectEveryOther(m_curPage).swap(pages); - emit appliedTo(pages); - } else if (thisEveryOtherRB->isChecked()) { - std::set tmp; - m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); - auto it = tmp.begin(); - for (int i = 0; it != tmp.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - emit appliedTo(pages); - } else if (everyOtherSelectedRB->isChecked()) { - auto it = m_selectedPages.begin(); - for (int i = 0; it != m_selectedPages.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - emit appliedTo(pages); + std::set pages; + // thisPageRB is intentionally not handled. + if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + emit appliedToAllPages(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + emit appliedTo(pages); + } else if (selectedPagesRB->isChecked()) { + emit appliedTo(m_selectedPages); + } else if (everyOtherRB->isChecked()) { + m_pages.selectEveryOther(m_curPage).swap(pages); + emit appliedTo(pages); + } else if (thisEveryOtherRB->isChecked()) { + std::set tmp; + m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); + auto it = tmp.begin(); + for (int i = 0; it != tmp.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } + } + emit appliedTo(pages); + } else if (everyOtherSelectedRB->isChecked()) { + auto it = m_selectedPages.begin(); + for (int i = 0; it != m_selectedPages.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } } - accept(); + emit appliedTo(pages); + } + accept(); } // ApplyDialog::onSubmit } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/ApplyDialog.h b/filters/deskew/ApplyDialog.h index bf1a05521..597981040 100644 --- a/filters/deskew/ApplyDialog.h +++ b/filters/deskew/ApplyDialog.h @@ -18,39 +18,39 @@ #ifndef DESKEW_APPLYDIALOG_H_ #define DESKEW_APPLYDIALOG_H_ -#include "ui_DeskewApplyDialog.h" +#include +#include +#include #include "PageId.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include +#include "ui_DeskewApplyDialog.h" -class QButtonGroup; class PageSelectionAccessor; namespace deskew { class ApplyDialog : public QDialog, private Ui::DeskewApplyDialog { - Q_OBJECT -public: - ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); - ~ApplyDialog() override; + ~ApplyDialog() override; -signals: + signals: - void appliedTo(const std::set& pages); + void appliedTo(const std::set& pages); - void appliedToAllPages(const std::set& pages); + void appliedToAllPages(const std::set& pages); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - PageId m_curPage; - std::set m_selectedPages; - QButtonGroup* m_pScopeGroup; + private: + PageSequence m_pages; + PageId m_curPage; + std::set m_selectedPages; + QButtonGroup* m_scopeGroup; }; } // namespace deskew #endif // ifndef DESKEW_APPLYDIALOG_H_ diff --git a/filters/deskew/CMakeLists.txt b/filters/deskew/CMakeLists.txt index 8874b5dd9..4377b53f8 100644 --- a/filters/deskew/CMakeLists.txt +++ b/filters/deskew/CMakeLists.txt @@ -1,30 +1,30 @@ -PROJECT("Deskew Filter") +project("Deskew Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - ImageView.cpp ImageView.h - Thumbnail.cpp Thumbnail.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - Settings.cpp Settings.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h - Dependencies.cpp Dependencies.h - Params.cpp Params.h - ApplyDialog.cpp ApplyDialog.h +set( + sources + ImageView.cpp ImageView.h + Thumbnail.cpp Thumbnail.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + Settings.cpp Settings.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h + Dependencies.cpp Dependencies.h + Params.cpp Params.h + ApplyDialog.cpp ApplyDialog.h ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(deskew STATIC ${sources} ${ui_sources}) +add_library(deskew STATIC ${sources} ${ui_sources}) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) +translation_sources(scantailor ${sources} ${ui_files}) diff --git a/filters/deskew/CacheDrivenTask.cpp b/filters/deskew/CacheDrivenTask.cpp index 2641f363b..24fd6fa7e 100644 --- a/filters/deskew/CacheDrivenTask.cpp +++ b/filters/deskew/CacheDrivenTask.cpp @@ -18,12 +18,12 @@ #include "CacheDrivenTask.h" -#include #include -#include "Thumbnail.h" +#include #include "IncompleteThumbnail.h" -#include "Settings.h" #include "PageInfo.h" +#include "Settings.h" +#include "Thumbnail.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filters/select_content/CacheDrivenTask.h" @@ -31,42 +31,41 @@ namespace deskew { CacheDrivenTask::CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task) - : m_ptrNextTask(std::move(next_task)), m_ptrSettings(std::move(settings)) { -} + : m_nextTask(std::move(next_task)), m_settings(std::move(settings)) {} CacheDrivenTask::~CacheDrivenTask() = default; void CacheDrivenTask::process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform) { - const Dependencies deps(xform.preCropArea(), xform.preRotation()); - std::unique_ptr params(m_ptrSettings->getPageParams(page_info.id())); - if (!params || (!deps.matches(params->dependencies()) && (params->mode() == MODE_AUTO))) { - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); - } - - return; + const Dependencies deps(xform.preCropArea(), xform.preRotation()); + std::unique_ptr params(m_settings->getPageParams(page_info.id())); + if (!params || (!deps.matches(params->dependencies()) && (params->mode() == MODE_AUTO))) { + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); } - ImageTransformation new_xform(xform); - new_xform.setPostRotation(params->deskewAngle()); + return; + } - if (m_ptrNextTask) { - m_ptrNextTask->process(page_info, collector, new_xform); + ImageTransformation new_xform(xform); + new_xform.setPostRotation(params->deskewAngle()); - return; - } + if (m_nextTask) { + m_nextTask->process(page_info, collector, new_xform); - QSettings settings; - const double deviationCoef = settings.value("settings/deskewDeviationCoef", 1.5).toDouble(); - const double deviationThreshold = settings.value("settings/deskewDeviationThreshold", 1.0).toDouble(); + return; + } - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new Thumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform, - m_ptrSettings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); - } + QSettings settings; + const double deviationCoef = settings.value("settings/deskewDeviationCoef", 1.5).toDouble(); + const double deviationThreshold = settings.value("settings/deskewDeviationThreshold", 1.0).toDouble(); + + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr( + new Thumbnail(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform, + m_settings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); + } } // CacheDrivenTask::process } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/CacheDrivenTask.h b/filters/deskew/CacheDrivenTask.h index 16ed50923..0d210dcd9 100644 --- a/filters/deskew/CacheDrivenTask.h +++ b/filters/deskew/CacheDrivenTask.h @@ -20,8 +20,8 @@ #define DESKEW_CACHEDRIVENTASK_H_ #include "NonCopyable.h" -#include "ref_countable.h" #include "intrusive_ptr.h" +#include "ref_countable.h" class QSizeF; class PageInfo; @@ -36,18 +36,18 @@ namespace deskew { class Settings; class CacheDrivenTask : public ref_countable { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); + public: + CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); + void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); -private: - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; }; } // namespace deskew #endif // ifndef DESKEW_CACHEDRIVENTASK_H_ diff --git a/filters/deskew/Dependencies.cpp b/filters/deskew/Dependencies.cpp index 1924f35cb..d32b0ca14 100644 --- a/filters/deskew/Dependencies.cpp +++ b/filters/deskew/Dependencies.cpp @@ -27,31 +27,29 @@ namespace deskew { Dependencies::Dependencies() = default; Dependencies::Dependencies(const QPolygonF& page_outline, const OrthogonalRotation rotation) - : m_pageOutline(page_outline), m_rotation(rotation) { -} + : m_pageOutline(page_outline), m_rotation(rotation) {} Dependencies::Dependencies(const QDomElement& deps_el) - : m_pageOutline(XmlUnmarshaller::polygonF(deps_el.namedItem("page-outline").toElement())), - m_rotation(XmlUnmarshaller::rotation(deps_el.namedItem("rotation").toElement())) { -} + : m_pageOutline(XmlUnmarshaller::polygonF(deps_el.namedItem("page-outline").toElement())), + m_rotation(XmlUnmarshaller::rotation(deps_el.namedItem("rotation").toElement())) {} Dependencies::~Dependencies() = default; bool Dependencies::matches(const Dependencies& other) const { - if (m_rotation != other.m_rotation) { - return false; - } + if (m_rotation != other.m_rotation) { + return false; + } - return PolygonUtils::fuzzyCompare(m_pageOutline, other.m_pageOutline); + return PolygonUtils::fuzzyCompare(m_pageOutline, other.m_pageOutline); } QDomElement Dependencies::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); + XmlMarshaller marshaller(doc); - QDomElement el(doc.createElement(name)); - el.appendChild(marshaller.rotation(m_rotation, "rotation")); - el.appendChild(marshaller.polygonF(m_pageOutline, "page-outline")); + QDomElement el(doc.createElement(name)); + el.appendChild(marshaller.rotation(m_rotation, "rotation")); + el.appendChild(marshaller.polygonF(m_pageOutline, "page-outline")); - return el; + return el; } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/Dependencies.h b/filters/deskew/Dependencies.h index 7fa7651de..e54b3ad8a 100644 --- a/filters/deskew/Dependencies.h +++ b/filters/deskew/Dependencies.h @@ -33,24 +33,24 @@ namespace deskew { * Once dependencies change, deskew parameters are no longer valid. */ class Dependencies { -public: - // Member-wise copying is OK. + public: + // Member-wise copying is OK. - Dependencies(); + Dependencies(); - Dependencies(const QPolygonF& page_outline, OrthogonalRotation rotation); + Dependencies(const QPolygonF& page_outline, OrthogonalRotation rotation); - explicit Dependencies(const QDomElement& deps_el); + explicit Dependencies(const QDomElement& deps_el); - ~Dependencies(); + ~Dependencies(); - bool matches(const Dependencies& other) const; + bool matches(const Dependencies& other) const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - QPolygonF m_pageOutline; - OrthogonalRotation m_rotation; + private: + QPolygonF m_pageOutline; + OrthogonalRotation m_rotation; }; } // namespace deskew #endif // ifndef DESKEW_DEPENDENCIES_H_ diff --git a/filters/deskew/Filter.cpp b/filters/deskew/Filter.cpp index f281ed10f..13fe6aec2 100644 --- a/filters/deskew/Filter.cpp +++ b/filters/deskew/Filter.cpp @@ -17,216 +17,213 @@ */ #include "Filter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AbstractRelinker.h" +#include "CacheDrivenTask.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" -#include "Task.h" -#include "CacheDrivenTask.h" #include "ProjectReader.h" #include "ProjectWriter.h" -#include "AbstractRelinker.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "Task.h" namespace deskew { Filter::Filter(const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(new Settings), m_ptrImageSettings(new ImageSettings), m_selectedPageOrder(0) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); - } - - typedef PageOrderOption::ProviderPtr ProviderPtr; + : m_settings(new Settings), m_imageSettings(new ImageSettings), m_selectedPageOrder(0) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, page_selection_accessor)); + } - const ProviderPtr default_order; - const auto order_by_deviation = make_intrusive(m_ptrSettings->deviationProvider()); - m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); - m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); + const PageOrderOption::ProviderPtr default_order; + const auto order_by_deviation = make_intrusive(m_settings->deviationProvider()); + m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); + m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); } Filter::~Filter() = default; QString Filter::getName() const { - return QCoreApplication::translate("deskew::Filter", "Deskew"); + return QCoreApplication::translate("deskew::Filter", "Deskew"); } PageView Filter::getView() const { - return PAGE_VIEW; + return PAGE_VIEW; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); - m_ptrImageSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); + m_imageSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* const ui, const PageInfo& page_info) { - m_ptrOptionsWidget->preUpdateUI(page_info.id()); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); + m_optionsWidget->preUpdateUI(page_info.id()); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("deskew")); + QDomElement filter_el(doc.createElement("deskew")); - writer.enumPages([&](const PageId& page_id, const int numeric_id) { - this->writeParams(doc, filter_el, page_id, numeric_id); - }); + writer.enumPages( + [&](const PageId& page_id, const int numeric_id) { this->writeParams(doc, filter_el, page_id, numeric_id); }); - saveImageSettings(writer, doc, filter_el); + saveImageSettings(writer, doc, filter_el); - return filter_el; + return filter_el; } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); - - const QDomElement filter_el(filters_el.namedItem("deskew").toElement()); - - const QString page_tag_name("page"); - QDomNode node(filter_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const QDomElement params_el(el.namedItem("params").toElement()); - if (params_el.isNull()) { - continue; - } - - const Params params(params_el); - m_ptrSettings->setPageParams(page_id, params); + m_settings->clear(); + + const QDomElement filter_el(filters_el.namedItem("deskew").toElement()); + + const QString page_tag_name("page"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; } - loadImageSettings(reader, filter_el.namedItem("image-settings").toElement()); + const QDomElement params_el(el.namedItem("params").toElement()); + if (params_el.isNull()) { + continue; + } + + const Params params(params_el); + m_settings->setPageParams(page_id, params); + } + + loadImageSettings(reader, filter_el.namedItem("image-settings").toElement()); } // Filter::loadSettings void Filter::writeParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const std::unique_ptr params(m_ptrSettings->getPageParams(page_id)); - if (!params) { - return; - } + const std::unique_ptr params(m_settings->getPageParams(page_id)); + if (!params) { + return; + } - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); - page_el.appendChild(params->toXml(doc, "params")); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); + page_el.appendChild(params->toXml(doc, "params")); - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } intrusive_ptr Filter::createTask(const PageId& page_id, intrusive_ptr next_task, const bool batch_processing, const bool debug) { - return make_intrusive(intrusive_ptr(this), m_ptrSettings, m_ptrImageSettings, std::move(next_task), - page_id, batch_processing, debug); + return make_intrusive(intrusive_ptr(this), m_settings, m_imageSettings, std::move(next_task), page_id, + batch_processing, debug); } intrusive_ptr Filter::createCacheDrivenTask(intrusive_ptr next_task) { - return make_intrusive(m_ptrSettings, std::move(next_task)); + return make_intrusive(m_settings, std::move(next_task)); } std::vector Filter::pageOrderOptions() const { - return m_pageOrderOptions; + return m_pageOrderOptions; } int Filter::selectedPageOrder() const { - return m_selectedPageOrder; + return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { - assert((unsigned) option < m_pageOrderOptions.size()); - m_selectedPageOrder = option; + assert((unsigned) option < m_pageOrderOptions.size()); + m_selectedPageOrder = option; } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->isParamsNull(page_info.id())) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::DeskewParams& deskewParams = defaultParams.getDeskewParams(); + if (!m_settings->isParamsNull(page_info.id())) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::DeskewParams& deskewParams = defaultParams.getDeskewParams(); - m_ptrSettings->setPageParams(page_info.id(), - Params(deskewParams.getDeskewAngleDeg(), Dependencies(), deskewParams.getMode())); + m_settings->setPageParams(page_info.id(), + Params(deskewParams.getDeskewAngleDeg(), Dependencies(), deskewParams.getMode())); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); } void Filter::saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const { - QDomElement image_settings_el(doc.createElement("image-settings")); - writer.enumPages([&](const PageId& page_id, const int numeric_id) { - this->writeImageParams(doc, image_settings_el, page_id, numeric_id); - }); + QDomElement image_settings_el(doc.createElement("image-settings")); + writer.enumPages([&](const PageId& page_id, const int numeric_id) { + this->writeImageParams(doc, image_settings_el, page_id, numeric_id); + }); - filter_el.appendChild(image_settings_el); + filter_el.appendChild(image_settings_el); } void Filter::writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const std::unique_ptr params(m_ptrImageSettings->getPageParams(page_id)); - if (!params) { - return; - } + const std::unique_ptr params(m_imageSettings->getPageParams(page_id)); + if (!params) { + return; + } - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); - page_el.appendChild(params->toXml(doc, "image-params")); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); + page_el.appendChild(params->toXml(doc, "image-params")); - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } void Filter::loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el) { - m_ptrImageSettings->clear(); - - const QString page_tag_name("page"); - QDomNode node(image_settings_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const QDomElement params_el(el.namedItem("image-params").toElement()); - if (params_el.isNull()) { - continue; - } - - const ImageSettings::PageParams params(params_el); - m_ptrImageSettings->setPageParams(page_id, params); + m_imageSettings->clear(); + + const QString page_tag_name("page"); + QDomNode node(image_settings_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; } + + const QDomElement params_el(el.namedItem("image-params").toElement()); + if (params_el.isNull()) { + continue; + } + + const ImageSettings::PageParams params(params_el); + m_imageSettings->setPageParams(page_id, params); + } } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/Filter.h b/filters/deskew/Filter.h index d37931a20..5ec3dbaa7 100644 --- a/filters/deskew/Filter.h +++ b/filters/deskew/Filter.h @@ -19,14 +19,14 @@ #ifndef DESKEW_FILTER_H_ #define DESKEW_FILTER_H_ -#include "NonCopyable.h" +#include #include "AbstractFilter.h" -#include "PageView.h" -#include "intrusive_ptr.h" #include "FilterResult.h" +#include "NonCopyable.h" +#include "PageView.h" #include "SafeDeletingQObjectPtr.h" #include "Settings.h" -#include +#include "intrusive_ptr.h" class QString; class PageSelectionAccessor; @@ -44,57 +44,57 @@ class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) - Q_DECLARE_TR_FUNCTIONS(deskew::Filter) -public: - explicit Filter(const PageSelectionAccessor& page_selection_accessor); + Q_DECLARE_TR_FUNCTIONS(deskew::Filter) + public: + explicit Filter(const PageSelectionAccessor& page_selection_accessor); - ~Filter() override; + ~Filter() override; - QString getName() const override; + QString getName() const override; - PageView getView() const override; + PageView getView() const override; - void performRelinking(const AbstractRelinker& relinker) override; + void performRelinking(const AbstractRelinker& relinker) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; + QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void loadDefaultSettings(const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - intrusive_ptr createTask(const PageId& page_id, - intrusive_ptr next_task, - bool batch_processing, - bool debug); + intrusive_ptr createTask(const PageId& page_id, + intrusive_ptr next_task, + bool batch_processing, + bool debug); - intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); + intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); - OptionsWidget* optionsWidget(); + OptionsWidget* optionsWidget(); - std::vector pageOrderOptions() const override; + std::vector pageOrderOptions() const override; - int selectedPageOrder() const override; + int selectedPageOrder() const override; - void selectPageOrder(int option) override; + void selectPageOrder(int option) override; -private: - void writeParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + private: + void writeParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - void saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const; + void saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const; - void writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + void writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - void loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el); + void loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el); - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrImageSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; - std::vector m_pageOrderOptions; - int m_selectedPageOrder; + intrusive_ptr m_settings; + intrusive_ptr m_imageSettings; + SafeDeletingQObjectPtr m_optionsWidget; + std::vector m_pageOrderOptions; + int m_selectedPageOrder; }; } // namespace deskew #endif // ifndef DESKEW_FILTER_H_ diff --git a/filters/deskew/ImageView.cpp b/filters/deskew/ImageView.cpp index e3e21e38f..d5221aab7 100644 --- a/filters/deskew/ImageView.cpp +++ b/filters/deskew/ImageView.cpp @@ -17,14 +17,14 @@ */ #include "ImageView.h" -#include "ImagePresentation.h" -#include "imageproc/Constants.h" #include #include -#include #include #include +#include #include +#include "ImagePresentation.h" +#include "imageproc/Constants.h" namespace deskew { const double ImageView::m_maxRotationDeg = 45.0; @@ -32,184 +32,184 @@ const double ImageView::m_maxRotationSin = std::sin(m_maxRotationDeg * imageproc const int ImageView::m_cellSize = 20; ImageView::ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform) - : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), - m_handlePixmap(":/icons/aqua-sphere.png"), - m_dragHandler(*this), - m_zoomHandler(*this), - m_xform(xform) { - setMouseTracking(true); - - interactionState().setDefaultStatusTip(tr("Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation.")); - - const QString tip(tr("Drag this handle to rotate the image.")); - const double hit_radius = std::max(0.5 * m_handlePixmap.width(), 15.0); - for (int i = 0; i < 2; ++i) { - m_handles[i].setHitRadius(hit_radius); - m_handles[i].setPositionCallback(boost::bind(&ImageView::handlePosition, this, i)); - m_handles[i].setMoveRequestCallback(boost::bind(&ImageView::handleMoveRequest, this, i, _1)); - m_handles[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - - m_handleInteractors[i].setProximityStatusTip(tip); - m_handleInteractors[i].setObject(&m_handles[i]); - - makeLastFollower(m_handleInteractors[i]); - } - - m_zoomHandler.setFocus(ZoomHandler::CENTER); - - rootInteractionHandler().makeLastFollower(*this); - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); - - auto* rotateLeft = new QAction(nullptr); - rotateLeft->setShortcut(QKeySequence(",")); - connect(rotateLeft, SIGNAL(triggered(bool)), SLOT(doRotateLeft())); - addAction(rotateLeft); - - auto* rotateRight = new QAction(nullptr); - rotateRight->setShortcut(QKeySequence(".")); - connect(rotateRight, SIGNAL(triggered(bool)), SLOT(doRotateRight())); - addAction(rotateRight); + : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), + m_handlePixmap(":/icons/aqua-sphere.png"), + m_dragHandler(*this), + m_zoomHandler(*this), + m_xform(xform) { + setMouseTracking(true); + + interactionState().setDefaultStatusTip(tr("Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation.")); + + const QString tip(tr("Drag this handle to rotate the image.")); + const double hit_radius = std::max(0.5 * m_handlePixmap.width(), 15.0); + for (int i = 0; i < 2; ++i) { + m_handles[i].setHitRadius(hit_radius); + m_handles[i].setPositionCallback(boost::bind(&ImageView::handlePosition, this, i)); + m_handles[i].setMoveRequestCallback(boost::bind(&ImageView::handleMoveRequest, this, i, _1)); + m_handles[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + + m_handleInteractors[i].setProximityStatusTip(tip); + m_handleInteractors[i].setObject(&m_handles[i]); + + makeLastFollower(m_handleInteractors[i]); + } + + m_zoomHandler.setFocus(ZoomHandler::CENTER); + + rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); + + auto* rotateLeft = new QAction(nullptr); + rotateLeft->setShortcut(QKeySequence(",")); + connect(rotateLeft, SIGNAL(triggered(bool)), SLOT(doRotateLeft())); + addAction(rotateLeft); + + auto* rotateRight = new QAction(nullptr); + rotateRight->setShortcut(QKeySequence(".")); + connect(rotateRight, SIGNAL(triggered(bool)), SLOT(doRotateRight())); + addAction(rotateRight); } ImageView::~ImageView() = default; void ImageView::doRotate(double deg) { - manualDeskewAngleSetExternally(m_xform.postRotation() + deg); - emit manualDeskewAngleSet(m_xform.postRotation()); + manualDeskewAngleSetExternally(m_xform.postRotation() + deg); + emit manualDeskewAngleSet(m_xform.postRotation()); } void ImageView::doRotateLeft() { - doRotate(-0.10); + doRotate(-0.10); } void ImageView::doRotateRight() { - doRotate(0.10); + doRotate(0.10); } void ImageView::manualDeskewAngleSetExternally(const double degrees) { - if (m_xform.postRotation() == degrees) { - return; - } + if (m_xform.postRotation() == degrees) { + return; + } - m_xform.setPostRotation(degrees); - updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); + m_xform.setPostRotation(degrees); + updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); } void ImageView::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHints(QPainter::Antialiasing, false); - - const double w = maxViewportRect().width(); - const double h = maxViewportRect().height(); - const QPointF center(getImageRotationOrigin()); - - // Draw the semi-transparent grid. - QPen pen(QColor(0, 0, 0xd1, 90)); - pen.setCosmetic(true); - pen.setWidth(1); - painter.setPen(pen); - QVector lines; - for (double y = center.y(); (y -= m_cellSize) > 0.0;) { - lines.push_back(QLineF(0.5, y, w - 0.5, y)); - } - for (double y = center.y(); (y += m_cellSize) < h;) { - lines.push_back(QLineF(0.5, y, w - 0.5, y)); - } - for (double x = center.x(); (x -= m_cellSize) > 0.0;) { - lines.push_back(QLineF(x, 0.5, x, h - 0.5)); - } - for (double x = center.x(); (x += m_cellSize) < w;) { - lines.push_back(QLineF(x, 0.5, x, h - 0.5)); - } - painter.drawLines(lines); - - // Draw the horizontal and vertical line crossing at the center. - pen.setColor(QColor(0, 0, 0xd1)); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - painter.drawLine(QPointF(0.5, center.y()), QPointF(w - 0.5, center.y())); - painter.drawLine(QPointF(center.x(), 0.5), QPointF(center.x(), h - 0.5)); - // Draw the rotation arcs. - // Those will look like this ( ) - const QRectF arc_square(getRotationArcSquare()); - - painter.setRenderHints(QPainter::Antialiasing, true); - pen.setWidthF(1.5); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - painter.drawArc(arc_square, qRound(16 * -m_maxRotationDeg), qRound(16 * 2 * m_maxRotationDeg)); - painter.drawArc(arc_square, qRound(16 * (180 - m_maxRotationDeg)), qRound(16 * 2 * m_maxRotationDeg)); - - const std::pair handles(getRotationHandles(arc_square)); - - QRectF rect(m_handlePixmap.rect()); - rect.moveCenter(handles.first); - painter.drawPixmap(rect.topLeft(), m_handlePixmap); - rect.moveCenter(handles.second); - painter.drawPixmap(rect.topLeft(), m_handlePixmap); + painter.setWorldMatrixEnabled(false); + painter.setRenderHints(QPainter::Antialiasing, false); + + const double w = maxViewportRect().width(); + const double h = maxViewportRect().height(); + const QPointF center(getImageRotationOrigin()); + + // Draw the semi-transparent grid. + QPen pen(QColor(0, 0, 0xd1, 90)); + pen.setCosmetic(true); + pen.setWidth(1); + painter.setPen(pen); + QVector lines; + for (double y = center.y(); (y -= m_cellSize) > 0.0;) { + lines.push_back(QLineF(0.5, y, w - 0.5, y)); + } + for (double y = center.y(); (y += m_cellSize) < h;) { + lines.push_back(QLineF(0.5, y, w - 0.5, y)); + } + for (double x = center.x(); (x -= m_cellSize) > 0.0;) { + lines.push_back(QLineF(x, 0.5, x, h - 0.5)); + } + for (double x = center.x(); (x += m_cellSize) < w;) { + lines.push_back(QLineF(x, 0.5, x, h - 0.5)); + } + painter.drawLines(lines); + + // Draw the horizontal and vertical line crossing at the center. + pen.setColor(QColor(0, 0, 0xd1)); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawLine(QPointF(0.5, center.y()), QPointF(w - 0.5, center.y())); + painter.drawLine(QPointF(center.x(), 0.5), QPointF(center.x(), h - 0.5)); + // Draw the rotation arcs. + // Those will look like this ( ) + const QRectF arc_square(getRotationArcSquare()); + + painter.setRenderHints(QPainter::Antialiasing, true); + pen.setWidthF(1.5); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawArc(arc_square, qRound(16 * -m_maxRotationDeg), qRound(16 * 2 * m_maxRotationDeg)); + painter.drawArc(arc_square, qRound(16 * (180 - m_maxRotationDeg)), qRound(16 * 2 * m_maxRotationDeg)); + + const std::pair handles(getRotationHandles(arc_square)); + + QRectF rect(m_handlePixmap.rect()); + rect.moveCenter(handles.first); + painter.drawPixmap(rect.topLeft(), m_handlePixmap); + rect.moveCenter(handles.second); + painter.drawPixmap(rect.topLeft(), m_handlePixmap); } // ImageView::onPaint void ImageView::onWheelEvent(QWheelEvent* event, InteractionState& interaction) { - if (interaction.captured()) { - return; - } - - double degree_fraction = 0; - - if (event->modifiers() == Qt::ControlModifier) { - degree_fraction = 0.1; - } else if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { - degree_fraction = 0.05; - } else { - return; - } - - event->accept(); - const double delta = degree_fraction * event->delta() / 120; - double angle_deg = m_xform.postRotation() - delta; - angle_deg = qBound(-m_maxRotationDeg, angle_deg, m_maxRotationDeg); - if (angle_deg == m_xform.postRotation()) { - return; - } - - m_xform.setPostRotation(angle_deg); - updateTransformPreservingScale(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); - emit manualDeskewAngleSet(m_xform.postRotation()); + if (interaction.captured()) { + return; + } + + double degree_fraction = 0; + + if (event->modifiers() == Qt::ControlModifier) { + degree_fraction = 0.1; + } else if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + degree_fraction = 0.05; + } else { + return; + } + + event->accept(); + const double delta = degree_fraction * event->delta() / 120; + double angle_deg = m_xform.postRotation() - delta; + angle_deg = qBound(-m_maxRotationDeg, angle_deg, m_maxRotationDeg); + if (angle_deg == m_xform.postRotation()) { + return; + } + + m_xform.setPostRotation(angle_deg); + updateTransformPreservingScale(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); + emit manualDeskewAngleSet(m_xform.postRotation()); } // ImageView::onWheelEvent QPointF ImageView::handlePosition(int idx) const { - const std::pair handles(getRotationHandles(getRotationArcSquare())); - if (idx == 0) { - return handles.first; - } else { - return handles.second; - } + const std::pair handles(getRotationHandles(getRotationArcSquare())); + if (idx == 0) { + return handles.first; + } else { + return handles.second; + } } void ImageView::handleMoveRequest(int idx, const QPointF& pos) { - const QRectF arc_square(getRotationArcSquare()); - const double arc_radius = 0.5 * arc_square.width(); - const double abs_y = pos.y(); - double rel_y = abs_y - arc_square.center().y(); - rel_y = qBound(-arc_radius, rel_y, arc_radius); - - double angle_rad = std::asin(rel_y / arc_radius); - if (idx == 0) { - angle_rad = -angle_rad; - } - double angle_deg = angle_rad * imageproc::constants::RAD2DEG; - angle_deg = qBound(-m_maxRotationDeg, angle_deg, m_maxRotationDeg); - if (angle_deg == m_xform.postRotation()) { - return; - } - - m_xform.setPostRotation(angle_deg); - updateTransformPreservingScale(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); + const QRectF arc_square(getRotationArcSquare()); + const double arc_radius = 0.5 * arc_square.width(); + const double abs_y = pos.y(); + double rel_y = abs_y - arc_square.center().y(); + rel_y = qBound(-arc_radius, rel_y, arc_radius); + + double angle_rad = std::asin(rel_y / arc_radius); + if (idx == 0) { + angle_rad = -angle_rad; + } + double angle_deg = angle_rad * imageproc::constants::RAD2DEG; + angle_deg = qBound(-m_maxRotationDeg, angle_deg, m_maxRotationDeg); + if (angle_deg == m_xform.postRotation()) { + return; + } + + m_xform.setPostRotation(angle_deg); + updateTransformPreservingScale(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); } void ImageView::dragFinished() { - emit manualDeskewAngleSet(m_xform.postRotation()); + emit manualDeskewAngleSet(m_xform.postRotation()); } /** @@ -217,45 +217,45 @@ void ImageView::dragFinished() { * The point may be adjusted to to ensure it's at the center of a pixel. */ QPointF ImageView::getImageRotationOrigin() const { - const QRectF viewport_rect(maxViewportRect()); + const QRectF viewport_rect(maxViewportRect()); - return QPointF(std::floor(0.5 * viewport_rect.width()) + 0.5, std::floor(0.5 * viewport_rect.height()) + 0.5); + return QPointF(std::floor(0.5 * viewport_rect.width()) + 0.5, std::floor(0.5 * viewport_rect.height()) + 0.5); } /** * Get the square in widget coordinates where two rotation arcs will be drawn. */ QRectF ImageView::getRotationArcSquare() const { - const double h_margin - = 0.5 * m_handlePixmap.width() - + verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar()); - const double v_margin - = 0.5 * m_handlePixmap.height() - + horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, horizontalScrollBar()); + const double h_margin + = 0.5 * m_handlePixmap.width() + + verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar()); + const double v_margin + = 0.5 * m_handlePixmap.height() + + horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, horizontalScrollBar()); - QRectF reduced_screen_rect(maxViewportRect()); - reduced_screen_rect.adjust(h_margin, v_margin, -h_margin, -v_margin); + QRectF reduced_screen_rect(maxViewportRect()); + reduced_screen_rect.adjust(h_margin, v_margin, -h_margin, -v_margin); - QSizeF arc_size(1.0, m_maxRotationSin); - arc_size.scale(reduced_screen_rect.size(), Qt::KeepAspectRatio); - arc_size.setHeight(arc_size.width()); + QSizeF arc_size(1.0, m_maxRotationSin); + arc_size.scale(reduced_screen_rect.size(), Qt::KeepAspectRatio); + arc_size.setHeight(arc_size.width()); - QRectF arc_square(QPointF(0, 0), arc_size); - arc_square.moveCenter(reduced_screen_rect.center()); + QRectF arc_square(QPointF(0, 0), arc_size); + arc_square.moveCenter(reduced_screen_rect.center()); - return arc_square; + return arc_square; } std::pair ImageView::getRotationHandles(const QRectF& arc_square) const { - const double rot_sin = m_xform.postRotationSin(); - const double rot_cos = m_xform.postRotationCos(); - const double arc_radius = 0.5 * arc_square.width(); - const QPointF arc_center(arc_square.center()); - QPointF left_handle(-rot_cos * arc_radius, -rot_sin * arc_radius); - left_handle += arc_center; - QPointF right_handle(rot_cos * arc_radius, rot_sin * arc_radius); - right_handle += arc_center; - - return std::make_pair(left_handle, right_handle); + const double rot_sin = m_xform.postRotationSin(); + const double rot_cos = m_xform.postRotationCos(); + const double arc_radius = 0.5 * arc_square.width(); + const QPointF arc_center(arc_square.center()); + QPointF left_handle(-rot_cos * arc_radius, -rot_sin * arc_radius); + left_handle += arc_center; + QPointF right_handle(rot_cos * arc_radius, rot_sin * arc_radius); + right_handle += arc_center; + + return std::make_pair(left_handle, right_handle); } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/ImageView.h b/filters/deskew/ImageView.h index da6107976..747a4a8ff 100644 --- a/filters/deskew/ImageView.h +++ b/filters/deskew/ImageView.h @@ -19,72 +19,72 @@ #ifndef DESKEW_IMAGEVIEW_H_ #define DESKEW_IMAGEVIEW_H_ -#include "ImageViewBase.h" -#include "ImageTransformation.h" -#include "DragHandler.h" -#include "ZoomHandler.h" -#include "ObjectDragHandler.h" -#include "DraggablePoint.h" -#include +#include #include #include +#include #include -#include #include #include +#include "DragHandler.h" +#include "DraggablePoint.h" +#include "ImageTransformation.h" +#include "ImageViewBase.h" +#include "ObjectDragHandler.h" +#include "ZoomHandler.h" class QRect; namespace deskew { class ImageView : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform); + Q_OBJECT + public: + ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform); - ~ImageView() override; + ~ImageView() override; -signals: + signals: - void manualDeskewAngleSet(double degrees); + void manualDeskewAngleSet(double degrees); -public slots: + public slots: - void manualDeskewAngleSetExternally(double degrees); + void manualDeskewAngleSetExternally(double degrees); - void doRotateLeft(); + void doRotateLeft(); - void doRotateRight(); + void doRotateRight(); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onWheelEvent(QWheelEvent* event, InteractionState& interaction) override; + void onWheelEvent(QWheelEvent* event, InteractionState& interaction) override; - void doRotate(double deg); + void doRotate(double deg); -private: - QPointF handlePosition(int idx) const; + private: + QPointF handlePosition(int idx) const; - void handleMoveRequest(int idx, const QPointF& pos); + void handleMoveRequest(int idx, const QPointF& pos); - virtual void dragFinished(); + virtual void dragFinished(); - QPointF getImageRotationOrigin() const; + QPointF getImageRotationOrigin() const; - QRectF getRotationArcSquare() const; + QRectF getRotationArcSquare() const; - std::pair getRotationHandles(const QRectF& arc_square) const; + std::pair getRotationHandles(const QRectF& arc_square) const; - static const int m_cellSize; - static const double m_maxRotationDeg; - static const double m_maxRotationSin; + static const int m_cellSize; + static const double m_maxRotationDeg; + static const double m_maxRotationSin; - QPixmap m_handlePixmap; - DraggablePoint m_handles[2]; - ObjectDragHandler m_handleInteractors[2]; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; - ImageTransformation m_xform; + QPixmap m_handlePixmap; + DraggablePoint m_handles[2]; + ObjectDragHandler m_handleInteractors[2]; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; + ImageTransformation m_xform; }; } // namespace deskew #endif // ifndef DESKEW_IMAGEVIEW_H_ diff --git a/filters/deskew/OptionsWidget.cpp b/filters/deskew/OptionsWidget.cpp index 31db7cb71..d65f1a576 100644 --- a/filters/deskew/OptionsWidget.cpp +++ b/filters/deskew/OptionsWidget.cpp @@ -19,220 +19,224 @@ #include "OptionsWidget.h" #include -#include "Settings.h" -#include "ScopedIncDec.h" #include "ApplyDialog.h" +#include "ScopedIncDec.h" +#include "Settings.h" namespace deskew { const double OptionsWidget::MAX_ANGLE = 45.0; OptionsWidget::OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), - m_ignoreAutoManualToggle(0), - m_ignoreSpinBoxChanges(0), - m_pageSelectionAccessor(page_selection_accessor) { - setupUi(this); - angleSpinBox->setSuffix(QChar(0x00B0)); // the degree symbol - angleSpinBox->setRange(-MAX_ANGLE, MAX_ANGLE); - angleSpinBox->adjustSize(); - setSpinBoxUnknownState(); + : m_settings(std::move(settings)), + m_ignoreAutoManualToggle(0), + m_ignoreSpinBoxChanges(0), + m_pageSelectionAccessor(page_selection_accessor) { + setupUi(this); + angleSpinBox->setSuffix(QChar(0x00B0)); // the degree symbol + angleSpinBox->setRange(-MAX_ANGLE, MAX_ANGLE); + angleSpinBox->adjustSize(); + setSpinBoxUnknownState(); - setupUiConnections(); + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::showDeskewDialog() { - auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Deskew")); - connect(dialog, SIGNAL(appliedTo(const std::set&)), this, SLOT(appliedTo(const std::set&))); - connect(dialog, SIGNAL(appliedToAllPages(const std::set&)), this, - SLOT(appliedToAllPages(const std::set&))); - dialog->show(); + auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Deskew")); + connect(dialog, SIGNAL(appliedTo(const std::set&)), this, SLOT(appliedTo(const std::set&))); + connect(dialog, SIGNAL(appliedToAllPages(const std::set&)), this, + SLOT(appliedToAllPages(const std::set&))); + dialog->show(); } void OptionsWidget::appliedTo(const std::set& pages) { - if (pages.empty()) { - return; - } + if (pages.empty()) { + return; + } - const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); - m_ptrSettings->setDegrees(pages, params); + const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); + m_settings->setDegrees(pages, params); - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } } void OptionsWidget::appliedToAllPages(const std::set& pages) { - if (pages.empty()) { - return; - } + if (pages.empty()) { + return; + } - const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); - m_ptrSettings->setDegrees(pages, params); - emit invalidateAllThumbnails(); + const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); + m_settings->setDegrees(pages, params); + emit invalidateAllThumbnails(); } void OptionsWidget::manualDeskewAngleSetExternally(const double degrees) { - m_uiData.setEffectiveDeskewAngle(degrees); - m_uiData.setMode(MODE_MANUAL); - updateModeIndication(MODE_MANUAL); - setSpinBoxKnownState(degreesToSpinBox(degrees)); - commitCurrentParams(); + m_uiData.setEffectiveDeskewAngle(degrees); + m_uiData.setMode(MODE_MANUAL); + updateModeIndication(MODE_MANUAL); + setSpinBoxKnownState(degreesToSpinBox(degrees)); + commitCurrentParams(); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); + removeUiConnections(); - ScopedIncDec guard(m_ignoreAutoManualToggle); + ScopedIncDec guard(m_ignoreAutoManualToggle); - m_pageId = page_id; - setSpinBoxUnknownState(); - autoBtn->setChecked(true); - autoBtn->setEnabled(false); - manualBtn->setEnabled(false); + m_pageId = page_id; + setSpinBoxUnknownState(); + autoBtn->setChecked(true); + autoBtn->setEnabled(false); + manualBtn->setEnabled(false); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::postUpdateUI(const UiData& ui_data) { - removeUiConnections(); + removeUiConnections(); - m_uiData = ui_data; - autoBtn->setEnabled(true); - manualBtn->setEnabled(true); - updateModeIndication(ui_data.mode()); - setSpinBoxKnownState(degreesToSpinBox(ui_data.effectiveDeskewAngle())); + m_uiData = ui_data; + autoBtn->setEnabled(true); + manualBtn->setEnabled(true); + updateModeIndication(ui_data.mode()); + setSpinBoxKnownState(degreesToSpinBox(ui_data.effectiveDeskewAngle())); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::spinBoxValueChanged(const double value) { - if (m_ignoreSpinBoxChanges) { - return; - } + if (m_ignoreSpinBoxChanges) { + return; + } - const double degrees = spinBoxToDegrees(value); - m_uiData.setEffectiveDeskewAngle(degrees); - m_uiData.setMode(MODE_MANUAL); - updateModeIndication(MODE_MANUAL); - commitCurrentParams(); + const double degrees = spinBoxToDegrees(value); + m_uiData.setEffectiveDeskewAngle(degrees); + m_uiData.setMode(MODE_MANUAL); + updateModeIndication(MODE_MANUAL); + commitCurrentParams(); - emit manualDeskewAngleSet(degrees); - emit invalidateThumbnail(m_pageId); + emit manualDeskewAngleSet(degrees); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::modeChanged(const bool auto_mode) { - if (m_ignoreAutoManualToggle) { - return; - } - - if (auto_mode) { - m_uiData.setMode(MODE_AUTO); - m_ptrSettings->clearPageParams(m_pageId); - emit reloadRequested(); - } else { - m_uiData.setMode(MODE_MANUAL); - commitCurrentParams(); - } + if (m_ignoreAutoManualToggle) { + return; + } + + if (auto_mode) { + m_uiData.setMode(MODE_AUTO); + m_settings->clearPageParams(m_pageId); + emit reloadRequested(); + } else { + m_uiData.setMode(MODE_MANUAL); + commitCurrentParams(); + } } void OptionsWidget::updateModeIndication(const AutoManualMode mode) { - ScopedIncDec guard(m_ignoreAutoManualToggle); + ScopedIncDec guard(m_ignoreAutoManualToggle); - if (mode == MODE_AUTO) { - autoBtn->setChecked(true); - } else { - manualBtn->setChecked(true); - } + if (mode == MODE_AUTO) { + autoBtn->setChecked(true); + } else { + manualBtn->setChecked(true); + } } void OptionsWidget::setSpinBoxUnknownState() { - ScopedIncDec guard(m_ignoreSpinBoxChanges); + ScopedIncDec guard(m_ignoreSpinBoxChanges); - angleSpinBox->setSpecialValueText("?"); - angleSpinBox->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - angleSpinBox->setValue(angleSpinBox->minimum()); - angleSpinBox->setEnabled(false); + angleSpinBox->setSpecialValueText("?"); + angleSpinBox->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + angleSpinBox->setValue(angleSpinBox->minimum()); + angleSpinBox->setEnabled(false); } void OptionsWidget::setSpinBoxKnownState(const double angle) { - ScopedIncDec guard(m_ignoreSpinBoxChanges); + ScopedIncDec guard(m_ignoreSpinBoxChanges); - angleSpinBox->setSpecialValueText(""); - angleSpinBox->setValue(angle); + angleSpinBox->setSpecialValueText(""); + angleSpinBox->setValue(angle); - // Right alignment doesn't work correctly, so we use the left one. - angleSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - angleSpinBox->setEnabled(true); + // Right alignment doesn't work correctly, so we use the left one. + angleSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + angleSpinBox->setEnabled(true); } void OptionsWidget::commitCurrentParams() { - Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); - m_ptrSettings->setPageParams(m_pageId, params); + Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode()); + m_settings->setPageParams(m_pageId, params); } double OptionsWidget::spinBoxToDegrees(const double sb_value) { - // The spin box shows the angle in a usual geometric way, - // with positive angles going counter-clockwise. - // Internally, we operate with angles going clockwise, - // because the Y axis points downwards in computer graphics. - return -sb_value; + // The spin box shows the angle in a usual geometric way, + // with positive angles going counter-clockwise. + // Internally, we operate with angles going clockwise, + // because the Y axis points downwards in computer graphics. + return -sb_value; } double OptionsWidget::degreesToSpinBox(const double degrees) { - // See above. - return -degrees; + // See above. + return -degrees; } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(angleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(spinBoxValueChanged(double))); - connect(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool))); - connect(applyDeskewBtn, SIGNAL(clicked()), this, SLOT(showDeskewDialog())); + CONNECT(angleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(spinBoxValueChanged(double))); + CONNECT(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool))); + CONNECT(applyDeskewBtn, SIGNAL(clicked()), this, SLOT(showDeskewDialog())); } +#undef CONNECT + void OptionsWidget::removeUiConnections() { - disconnect(angleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(spinBoxValueChanged(double))); - disconnect(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool))); - disconnect(applyDeskewBtn, SIGNAL(clicked()), this, SLOT(showDeskewDialog())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } /*========================== OptionsWidget::UiData =========================*/ -OptionsWidget::UiData::UiData() : m_effDeskewAngle(0.0), m_mode(MODE_AUTO) { -} +OptionsWidget::UiData::UiData() : m_effDeskewAngle(0.0), m_mode(MODE_AUTO) {} OptionsWidget::UiData::~UiData() = default; void OptionsWidget::UiData::setEffectiveDeskewAngle(const double degrees) { - m_effDeskewAngle = degrees; + m_effDeskewAngle = degrees; } double OptionsWidget::UiData::effectiveDeskewAngle() const { - return m_effDeskewAngle; + return m_effDeskewAngle; } void OptionsWidget::UiData::setDependencies(const Dependencies& deps) { - m_deps = deps; + m_deps = deps; } const Dependencies& OptionsWidget::UiData::dependencies() const { - return m_deps; + return m_deps; } void OptionsWidget::UiData::setMode(const AutoManualMode mode) { - m_mode = mode; + m_mode = mode; } AutoManualMode OptionsWidget::UiData::mode() const { - return m_mode; + return m_mode; } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/OptionsWidget.h b/filters/deskew/OptionsWidget.h index b3ff076d7..15405775a 100644 --- a/filters/deskew/OptionsWidget.h +++ b/filters/deskew/OptionsWidget.h @@ -19,102 +19,105 @@ #ifndef DESKEW_OPTIONSWIDGET_H_ #define DESKEW_OPTIONSWIDGET_H_ -#include "ui_DeskewOptionsWidget.h" +#include +#include +#include "AutoManualMode.h" +#include "Dependencies.h" #include "FilterOptionsWidget.h" -#include "intrusive_ptr.h" #include "PageId.h" -#include "Dependencies.h" -#include "AutoManualMode.h" #include "PageSelectionAccessor.h" -#include +#include "intrusive_ptr.h" +#include "ui_DeskewOptionsWidget.h" namespace deskew { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::DeskewOptionsWidget { - Q_OBJECT -public: - class UiData { - // Member-wise copying is OK. - public: - UiData(); + Q_OBJECT + public: + class UiData { + // Member-wise copying is OK. + public: + UiData(); + + ~UiData(); - ~UiData(); + void setEffectiveDeskewAngle(double degrees); - void setEffectiveDeskewAngle(double degrees); + double effectiveDeskewAngle() const; - double effectiveDeskewAngle() const; + void setDependencies(const Dependencies& deps); - void setDependencies(const Dependencies& deps); + const Dependencies& dependencies() const; - const Dependencies& dependencies() const; + void setMode(AutoManualMode mode); - void setMode(AutoManualMode mode); + AutoManualMode mode() const; - AutoManualMode mode() const; + private: + double m_effDeskewAngle; + Dependencies m_deps; + AutoManualMode m_mode; + }; - private: - double m_effDeskewAngle; - Dependencies m_deps; - AutoManualMode m_mode; - }; + OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); - OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + ~OptionsWidget() override; - ~OptionsWidget() override; + signals: -signals: + void manualDeskewAngleSet(double degrees); - void manualDeskewAngleSet(double degrees); + public slots: -public slots: + void manualDeskewAngleSetExternally(double degrees); - void manualDeskewAngleSetExternally(double degrees); + public: + void preUpdateUI(const PageId& page_id); -public: - void preUpdateUI(const PageId& page_id); + void postUpdateUI(const UiData& ui_data); - void postUpdateUI(const UiData& ui_data); + private slots: -private slots: + void spinBoxValueChanged(double skew_degrees); - void spinBoxValueChanged(double skew_degrees); + void modeChanged(bool auto_mode); - void modeChanged(bool auto_mode); + void showDeskewDialog(); - void showDeskewDialog(); + void appliedTo(const std::set& pages); - void appliedTo(const std::set& pages); + void appliedToAllPages(const std::set& pages); - void appliedToAllPages(const std::set& pages); + private: + void updateModeIndication(AutoManualMode mode); -private: - void updateModeIndication(AutoManualMode mode); + void setSpinBoxUnknownState(); - void setSpinBoxUnknownState(); + void setSpinBoxKnownState(double angle); - void setSpinBoxKnownState(double angle); + void commitCurrentParams(); - void commitCurrentParams(); + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + static double spinBoxToDegrees(double sb_value); - static double spinBoxToDegrees(double sb_value); + static double degreesToSpinBox(double degrees); - static double degreesToSpinBox(double degrees); + static const double MAX_ANGLE; - static const double MAX_ANGLE; + intrusive_ptr m_settings; + PageId m_pageId; + UiData m_uiData; + int m_ignoreAutoManualToggle; + int m_ignoreSpinBoxChanges; - intrusive_ptr m_ptrSettings; - PageId m_pageId; - UiData m_uiData; - int m_ignoreAutoManualToggle; - int m_ignoreSpinBoxChanges; + PageSelectionAccessor m_pageSelectionAccessor; - PageSelectionAccessor m_pageSelectionAccessor; + std::list m_connectionList; }; } // namespace deskew #endif // ifndef DESKEW_OPTIONSWIDGET_H_ diff --git a/filters/deskew/Params.cpp b/filters/deskew/Params.cpp index bef39b828..ee648e3e3 100644 --- a/filters/deskew/Params.cpp +++ b/filters/deskew/Params.cpp @@ -17,39 +17,37 @@ */ #include "Params.h" -#include "../../Utils.h" #include +#include "../../Utils.h" namespace deskew { Params::Params(const double deskew_angle_deg, const Dependencies& deps, const AutoManualMode mode) - : m_deskewAngleDeg(deskew_angle_deg), m_deps(deps), m_mode(mode) { -} + : m_deskewAngleDeg(deskew_angle_deg), m_deps(deps), m_mode(mode) {} Params::Params(const QDomElement& deskew_el) - : m_deskewAngleDeg(deskew_el.attribute("angle").toDouble()), - m_deps(deskew_el.namedItem("dependencies").toElement()), - m_mode(deskew_el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO) { -} + : m_deskewAngleDeg(deskew_el.attribute("angle").toDouble()), + m_deps(deskew_el.namedItem("dependencies").toElement()), + m_mode(deskew_el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO) {} Params::~Params() = default; QDomElement Params::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("mode", m_mode == MODE_AUTO ? "auto" : "manual"); - el.setAttribute("angle", Utils::doubleToString(m_deskewAngleDeg)); - el.appendChild(m_deps.toXml(doc, "dependencies")); + QDomElement el(doc.createElement(name)); + el.setAttribute("mode", m_mode == MODE_AUTO ? "auto" : "manual"); + el.setAttribute("angle", Utils::doubleToString(m_deskewAngleDeg)); + el.appendChild(m_deps.toXml(doc, "dependencies")); - return el; + return el; } double Params::deskewAngle() const { - return m_deskewAngleDeg; + return m_deskewAngleDeg; } const Dependencies& Params::dependencies() const { - return m_deps; + return m_deps; } AutoManualMode Params::mode() const { - return m_mode; + return m_mode; } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/Params.h b/filters/deskew/Params.h index 048fc01e2..f66a8e634 100644 --- a/filters/deskew/Params.h +++ b/filters/deskew/Params.h @@ -19,38 +19,38 @@ #ifndef DESKEW_PARAMS_H_ #define DESKEW_PARAMS_H_ -#include "Dependencies.h" -#include "AutoManualMode.h" #include -#include #include +#include +#include "AutoManualMode.h" +#include "Dependencies.h" class QDomDocument; class QDomElement; namespace deskew { class Params { -public: - // Member-wise copying is OK. + public: + // Member-wise copying is OK. - Params(double deskew_angle_deg, const Dependencies& deps, AutoManualMode mode); + Params(double deskew_angle_deg, const Dependencies& deps, AutoManualMode mode); - explicit Params(const QDomElement& deskew_el); + explicit Params(const QDomElement& deskew_el); - ~Params(); + ~Params(); - double deskewAngle() const; + double deskewAngle() const; - const Dependencies& dependencies() const; + const Dependencies& dependencies() const; - AutoManualMode mode() const; + AutoManualMode mode() const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - double m_deskewAngleDeg; - Dependencies m_deps; - AutoManualMode m_mode; + private: + double m_deskewAngleDeg; + Dependencies m_deps; + AutoManualMode m_mode; }; } // namespace deskew #endif // ifndef DESKEW_PARAMS_H_ diff --git a/filters/deskew/Settings.cpp b/filters/deskew/Settings.cpp index 1639201b0..66a7e1974 100644 --- a/filters/deskew/Settings.cpp +++ b/filters/deskew/Settings.cpp @@ -17,89 +17,89 @@ */ #include "Settings.h" -#include "Utils.h" -#include "RelinkablePath.h" #include "AbstractRelinker.h" +#include "RelinkablePath.h" +#include "Utils.h" namespace deskew { Settings::Settings() { - m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { - auto it(m_perPageParams.find(pageId)); - if (it != m_perPageParams.end()) { - const Params& params = it->second; - - return params.deskewAngle(); - } else { - return .0; - }; - }); + m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { + auto it(m_perPageParams.find(pageId)); + if (it != m_perPageParams.end()) { + const Params& params = it->second; + + return params.deskewAngle(); + } else { + return .0; + }; + }); } Settings::~Settings() = default; void Settings::clear() { - QMutexLocker locker(&m_mutex); - m_perPageParams.clear(); - m_deviationProvider.clear(); + QMutexLocker locker(&m_mutex); + m_perPageParams.clear(); + m_deviationProvider.clear(); } void Settings::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); - PerPageParams new_params; - - for (const PerPageParams::value_type& kv : m_perPageParams) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_params.insert(PerPageParams::value_type(new_page_id, kv.second)); - } - - m_perPageParams.swap(new_params); - - m_deviationProvider.clear(); - for (const PerPageParams::value_type& kv : m_perPageParams) { - m_deviationProvider.addOrUpdate(kv.first); - } + QMutexLocker locker(&m_mutex); + PerPageParams new_params; + + for (const PerPageParams::value_type& kv : m_perPageParams) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_params.insert(PerPageParams::value_type(new_page_id, kv.second)); + } + + m_perPageParams.swap(new_params); + + m_deviationProvider.clear(); + for (const PerPageParams::value_type& kv : m_perPageParams) { + m_deviationProvider.addOrUpdate(kv.first); + } } void Settings::setPageParams(const PageId& page_id, const Params& params) { - QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPageParams, page_id, params); - m_deviationProvider.addOrUpdate(page_id); + QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageParams, page_id, params); + m_deviationProvider.addOrUpdate(page_id); } void Settings::clearPageParams(const PageId& page_id) { - QMutexLocker locker(&m_mutex); - m_perPageParams.erase(page_id); - m_deviationProvider.remove(page_id); + QMutexLocker locker(&m_mutex); + m_perPageParams.erase(page_id); + m_deviationProvider.remove(page_id); } std::unique_ptr Settings::getPageParams(const PageId& page_id) const { - QMutexLocker locker(&m_mutex); - - auto it(m_perPageParams.find(page_id)); - if (it != m_perPageParams.end()) { - return std::make_unique(it->second); - } else { - return nullptr; - } + QMutexLocker locker(&m_mutex); + + auto it(m_perPageParams.find(page_id)); + if (it != m_perPageParams.end()) { + return std::make_unique(it->second); + } else { + return nullptr; + } } void Settings::setDegrees(const std::set& pages, const Params& params) { - const QMutexLocker locker(&m_mutex); - for (const PageId& page : pages) { - Utils::mapSetValue(m_perPageParams, page, params); - m_deviationProvider.addOrUpdate(page); - } + const QMutexLocker locker(&m_mutex); + for (const PageId& page : pages) { + Utils::mapSetValue(m_perPageParams, page, params); + m_deviationProvider.addOrUpdate(page); + } } bool Settings::isParamsNull(const PageId& page_id) const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return (m_perPageParams.find(page_id) == m_perPageParams.end()); + return (m_perPageParams.find(page_id) == m_perPageParams.end()); } const DeviationProvider& Settings::deviationProvider() const { - return m_deviationProvider; + return m_deviationProvider; } } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/Settings.h b/filters/deskew/Settings.h index 2a82e5c34..ba3d5e9bf 100644 --- a/filters/deskew/Settings.h +++ b/filters/deskew/Settings.h @@ -19,49 +19,49 @@ #ifndef DESKEW_SETTINGS_H_ #define DESKEW_SETTINGS_H_ -#include "ref_countable.h" -#include "NonCopyable.h" -#include "PageId.h" -#include "Params.h" +#include #include #include -#include #include -#include +#include +#include "NonCopyable.h" +#include "PageId.h" +#include "Params.h" +#include "ref_countable.h" class AbstractRelinker; namespace deskew { class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) + DECLARE_NON_COPYABLE(Settings) -public: - Settings(); + public: + Settings(); - ~Settings() override; + ~Settings() override; - void clear(); + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - void setPageParams(const PageId& page_id, const Params& params); + void setPageParams(const PageId& page_id, const Params& params); - void clearPageParams(const PageId& page_id); + void clearPageParams(const PageId& page_id); - std::unique_ptr getPageParams(const PageId& page_id) const; + std::unique_ptr getPageParams(const PageId& page_id) const; - bool isParamsNull(const PageId& page_id) const; + bool isParamsNull(const PageId& page_id) const; - void setDegrees(const std::set& pages, const Params& params); + void setDegrees(const std::set& pages, const Params& params); - const DeviationProvider& deviationProvider() const; + const DeviationProvider& deviationProvider() const; -private: - typedef std::unordered_map PerPageParams; + private: + typedef std::unordered_map PerPageParams; - mutable QMutex m_mutex; - PerPageParams m_perPageParams; - DeviationProvider m_deviationProvider; + mutable QMutex m_mutex; + PerPageParams m_perPageParams; + DeviationProvider m_deviationProvider; }; } // namespace deskew #endif // ifndef DESKEW_SETTINGS_H_ diff --git a/filters/deskew/Task.cpp b/filters/deskew/Task.cpp index 34e146a7b..dd8bc63c6 100644 --- a/filters/deskew/Task.cpp +++ b/filters/deskew/Task.cpp @@ -18,56 +18,56 @@ #include +#include +#include +#include +#include #include -#include "Task.h" +#include "DebugImages.h" +#include "Dpm.h" #include "Filter.h" +#include "FilterData.h" +#include "FilterUiInterface.h" +#include "ImageView.h" #include "OptionsWidget.h" +#include "Task.h" #include "TaskStatus.h" -#include "DebugImages.h" #include "filters/select_content/Task.h" -#include "FilterUiInterface.h" -#include "ImageView.h" -#include "FilterData.h" -#include "Dpm.h" #include "imageproc/BinaryImage.h" +#include "imageproc/Morphology.h" #include "imageproc/OrthogonalRotation.h" -#include "imageproc/SkewFinder.h" #include "imageproc/RasterOp.h" #include "imageproc/ReduceThreshold.h" -#include "imageproc/UpscaleIntegerTimes.h" #include "imageproc/SeedFill.h" -#include "imageproc/Morphology.h" -#include -#include +#include "imageproc/SkewFinder.h" +#include "imageproc/UpscaleIntegerTimes.h" namespace deskew { using namespace imageproc; class Task::UiUpdater : public FilterResult { -public: - UiUpdater(intrusive_ptr filter, - std::unique_ptr dbg_img, - const QImage& image, - const PageId& page_id, - const ImageTransformation& xform, - const OptionsWidget::UiData& ui_data, - bool batch_processing); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - std::unique_ptr m_ptrDbg; - QImage m_image; - QImage m_downscaledImage; - PageId m_pageId; - ImageTransformation m_xform; - OptionsWidget::UiData m_uiData; - bool m_batchProcessing; + public: + UiUpdater(intrusive_ptr filter, + std::unique_ptr dbg_img, + const QImage& image, + const PageId& page_id, + const ImageTransformation& xform, + const OptionsWidget::UiData& ui_data, + bool batch_processing); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + std::unique_ptr m_dbg; + QImage m_image; + QImage m_downscaledImage; + PageId m_pageId; + ImageTransformation m_xform; + OptionsWidget::UiData m_uiData; + bool m_batchProcessing; }; @@ -78,161 +78,166 @@ Task::Task(intrusive_ptr filter, const PageId& page_id, const bool batch_processing, const bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrSettings(std::move(settings)), - m_ptrImageSettings(std::move(image_settings)), - m_ptrNextTask(std::move(next_task)), - m_pageId(page_id), - m_batchProcessing(batch_processing) { - if (debug) { - m_ptrDbg = std::make_unique(); - } + : m_filter(std::move(filter)), + m_settings(std::move(settings)), + m_imageSettings(std::move(image_settings)), + m_nextTask(std::move(next_task)), + m_pageId(page_id), + m_batchProcessing(batch_processing) { + if (debug) { + m_dbg = std::make_unique(); + } } Task::~Task() = default; FilterResultPtr Task::process(const TaskStatus& status, FilterData data) { - status.throwIfCancelled(); + status.throwIfCancelled(); - const Dependencies deps(data.xform().preCropArea(), data.xform().preRotation()); + const Dependencies deps(data.xform().preCropArea(), data.xform().preRotation()); - std::unique_ptr params(m_ptrSettings->getPageParams(m_pageId)); - updateFilterData(status, data, (!params || !deps.matches(params->dependencies()))); + std::unique_ptr params(m_settings->getPageParams(m_pageId)); + updateFilterData(status, data, (!params || !deps.matches(params->dependencies()))); - OptionsWidget::UiData ui_data; - ui_data.setDependencies(deps); + OptionsWidget::UiData ui_data; + ui_data.setDependencies(deps); - if (params) { - if ((!deps.matches(params->dependencies()) || (params->deskewAngle() != ui_data.effectiveDeskewAngle())) - && (params->mode() == MODE_AUTO)) { - params.reset(); - } else { - ui_data.setEffectiveDeskewAngle(params->deskewAngle()); - ui_data.setMode(params->mode()); + if (params) { + if ((!deps.matches(params->dependencies()) || (params->deskewAngle() != ui_data.effectiveDeskewAngle())) + && (params->mode() == MODE_AUTO)) { + params.reset(); + } else { + ui_data.setEffectiveDeskewAngle(params->deskewAngle()); + ui_data.setMode(params->mode()); - Params new_params(ui_data.effectiveDeskewAngle(), deps, ui_data.mode()); - m_ptrSettings->setPageParams(m_pageId, new_params); - } + Params new_params(ui_data.effectiveDeskewAngle(), deps, ui_data.mode()); + m_settings->setPageParams(m_pageId, new_params); } + } - if (!params) { - const QRectF image_area(data.xform().transformBack().mapRect(data.xform().resultingRect())); - const QRect bounded_image_area(image_area.toRect().intersected(data.origImage().rect())); - - status.throwIfCancelled(); - - if (bounded_image_area.isValid()) { - BinaryImage rotated_image( - orthogonalRotation(BinaryImage(data.grayImage(), bounded_image_area, data.bwThreshold()), - data.xform().preRotation().toDegrees())); - if (m_ptrDbg) { - m_ptrDbg->add(rotated_image, "bw_rotated"); - } - - const QSize unrotated_dpm(Dpm(data.origImage()).toSize()); - const Dpm rotated_dpm(data.xform().preRotation().rotate(unrotated_dpm)); - cleanup(status, rotated_image, Dpi(rotated_dpm)); - if (m_ptrDbg) { - m_ptrDbg->add(rotated_image, "after_cleanup"); - } - - status.throwIfCancelled(); - - SkewFinder skew_finder; - skew_finder.setResolutionRatio((double) rotated_dpm.horizontal() / rotated_dpm.vertical()); - const Skew skew(skew_finder.findSkew(rotated_image)); - - if (skew.confidence() >= skew.GOOD_CONFIDENCE) { - ui_data.setEffectiveDeskewAngle(-skew.angle()); - } else { - ui_data.setEffectiveDeskewAngle(0); - } - ui_data.setMode(MODE_AUTO); - - Params new_params(ui_data.effectiveDeskewAngle(), deps, ui_data.mode()); - m_ptrSettings->setPageParams(m_pageId, new_params); - - status.throwIfCancelled(); - } - } + if (!params) { + const QRectF image_area(data.xform().transformBack().mapRect(data.xform().resultingRect())); + const QRect bounded_image_area(image_area.toRect().intersected(data.origImage().rect())); - ImageTransformation new_xform(data.xform()); - new_xform.setPostRotation(ui_data.effectiveDeskewAngle()); + status.throwIfCancelled(); - if (m_ptrNextTask) { - return m_ptrNextTask->process(status, FilterData(data, new_xform)); - } else { - return make_intrusive(m_ptrFilter, std::move(m_ptrDbg), data.origImage(), m_pageId, new_xform, - ui_data, m_batchProcessing); + if (bounded_image_area.isValid()) { + BinaryImage rotated_image(orthogonalRotation( + BinaryImage(data.isBlackOnWhite() ? data.grayImage() : data.grayImage().inverted(), bounded_image_area, + data.isBlackOnWhite() ? data.bwThreshold() : BinaryThreshold(256 - int(data.bwThreshold()))), + data.xform().preRotation().toDegrees())); + if (m_dbg) { + m_dbg->add(rotated_image, "bw_rotated"); + } + + const QSize unrotated_dpm(Dpm(data.origImage()).toSize()); + const Dpm rotated_dpm(data.xform().preRotation().rotate(unrotated_dpm)); + cleanup(status, rotated_image, Dpi(rotated_dpm)); + if (m_dbg) { + m_dbg->add(rotated_image, "after_cleanup"); + } + + status.throwIfCancelled(); + + SkewFinder skew_finder; + skew_finder.setResolutionRatio((double) rotated_dpm.horizontal() / rotated_dpm.vertical()); + const Skew skew(skew_finder.findSkew(rotated_image)); + + if (skew.confidence() >= skew.GOOD_CONFIDENCE) { + ui_data.setEffectiveDeskewAngle(-skew.angle()); + } else { + ui_data.setEffectiveDeskewAngle(0); + } + ui_data.setMode(MODE_AUTO); + + Params new_params(ui_data.effectiveDeskewAngle(), deps, ui_data.mode()); + m_settings->setPageParams(m_pageId, new_params); + + status.throwIfCancelled(); } + } + + ImageTransformation new_xform(data.xform()); + new_xform.setPostRotation(ui_data.effectiveDeskewAngle()); + + if (m_nextTask) { + return m_nextTask->process(status, FilterData(data, new_xform)); + } else { + return make_intrusive(m_filter, std::move(m_dbg), data.origImage(), m_pageId, new_xform, ui_data, + m_batchProcessing); + } } // Task::process void Task::cleanup(const TaskStatus& status, BinaryImage& image, const Dpi& dpi) { - // We don't have to clean up every piece of garbage. - // The only concern are the horizontal shadows, which we remove here. - - Dpi reduced_dpi(dpi); - BinaryImage reduced_image; - - { - ReduceThreshold reductor(image); - while (reduced_dpi.horizontal() >= 200 && reduced_dpi.vertical() >= 200) { - reductor.reduce(2); - reduced_dpi = Dpi(reduced_dpi.horizontal() / 2, reduced_dpi.vertical() / 2); - } - reduced_image = reductor.image(); + // We don't have to clean up every piece of garbage. + // The only concern are the horizontal shadows, which we remove here. + + Dpi reduced_dpi(dpi); + BinaryImage reduced_image; + + { + ReduceThreshold reductor(image); + while (reduced_dpi.horizontal() >= 200 && reduced_dpi.vertical() >= 200) { + reductor.reduce(2); + reduced_dpi = Dpi(reduced_dpi.horizontal() / 2, reduced_dpi.vertical() / 2); } + reduced_image = reductor.image(); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - const QSize brick(from150dpi(QSize(200, 14), reduced_dpi)); - BinaryImage opened(openBrick(reduced_image, brick, BLACK)); - reduced_image.release(); + const QSize brick(from150dpi(QSize(200, 14), reduced_dpi)); + BinaryImage opened(openBrick(reduced_image, brick, BLACK)); + reduced_image.release(); - status.throwIfCancelled(); + status.throwIfCancelled(); - BinaryImage seed(upscaleIntegerTimes(opened, image.size(), WHITE)); - opened.release(); + BinaryImage seed(upscaleIntegerTimes(opened, image.size(), WHITE)); + opened.release(); - status.throwIfCancelled(); + status.throwIfCancelled(); - BinaryImage garbage(seedFill(seed, image, CONN8)); - seed.release(); + BinaryImage garbage(seedFill(seed, image, CONN8)); + seed.release(); - status.throwIfCancelled(); + status.throwIfCancelled(); - rasterOp>(image, garbage); + rasterOp>(image, garbage); } // Task::cleanup int Task::from150dpi(int size, int target_dpi) { - const int new_size = (size * target_dpi + 75) / 150; - if (new_size < 1) { - return 1; - } + const int new_size = (size * target_dpi + 75) / 150; + if (new_size < 1) { + return 1; + } - return new_size; + return new_size; } QSize Task::from150dpi(const QSize& size, const Dpi& target_dpi) { - const int width = from150dpi(size.width(), target_dpi.horizontal()); - const int height = from150dpi(size.height(), target_dpi.vertical()); + const int width = from150dpi(size.width(), target_dpi.horizontal()); + const int height = from150dpi(size.height(), target_dpi.vertical()); - return QSize(width, height); + return QSize(width, height); } void Task::updateFilterData(const TaskStatus& status, FilterData& data, bool needUpdate) { - const std::unique_ptr params = m_ptrImageSettings->getPageParams(m_pageId); - if (!needUpdate && params) { - data.updateImageParams(*params); - } else { - const GrayImage& img = data.grayImage(); - BinaryImage mask(img.size(), BLACK); - PolygonRasterizer::fillExcept(mask, WHITE, data.xform().resultingPreCropArea(), Qt::WindingFill); - ImageSettings::PageParams new_params(BinaryThreshold::otsuThreshold(GrayscaleHistogram(img, mask))); - - m_ptrImageSettings->setPageParams(m_pageId, new_params); - data.updateImageParams(new_params); + const std::unique_ptr params = m_imageSettings->getPageParams(m_pageId); + if (!needUpdate && params) { + data.updateImageParams(*params); + } else { + const GrayImage& img = data.grayImage(); + BinaryImage mask(img.size(), BLACK); + PolygonRasterizer::fillExcept(mask, WHITE, data.xform().resultingPreCropArea(), Qt::WindingFill); + bool isBlackOnWhite = true; + if (QSettings().value("settings/blackOnWhiteDetection", true).toBool()) { + isBlackOnWhite = BlackOnWhiteEstimator::isBlackOnWhite(data.grayImage(), data.xform(), status, m_dbg.get()); } + ImageSettings::PageParams new_params(BinaryThreshold::otsuThreshold(GrayscaleHistogram(img, mask)), isBlackOnWhite); + + m_imageSettings->setPageParams(m_pageId, new_params); + data.updateImageParams(new_params); + } } /*============================ Task::UiUpdater ==========================*/ @@ -244,34 +249,33 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const ImageTransformation& xform, const OptionsWidget::UiData& ui_data, const bool batch_processing) - : m_ptrFilter(std::move(filter)), - m_ptrDbg(std::move(dbg_img)), - m_image(image), - m_downscaledImage(ImageView::createDownscaledImage(image)), - m_pageId(page_id), - m_xform(xform), - m_uiData(ui_data), - m_batchProcessing(batch_processing) { -} + : m_filter(std::move(filter)), + m_dbg(std::move(dbg_img)), + m_image(image), + m_downscaledImage(ImageView::createDownscaledImage(image)), + m_pageId(page_id), + m_xform(xform), + m_uiData(ui_data), + m_batchProcessing(batch_processing) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(m_uiData); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(m_uiData); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - ui->invalidateThumbnail(m_pageId); + ui->invalidateThumbnail(m_pageId); - if (m_batchProcessing) { - return; - } + if (m_batchProcessing) { + return; + } - auto* view = new ImageView(m_image, m_downscaledImage, m_xform); - ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); + auto* view = new ImageView(m_image, m_downscaledImage, m_xform); + ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_dbg.get()); - QObject::connect(view, SIGNAL(manualDeskewAngleSet(double)), opt_widget, - SLOT(manualDeskewAngleSetExternally(double))); - QObject::connect(opt_widget, SIGNAL(manualDeskewAngleSet(double)), view, - SLOT(manualDeskewAngleSetExternally(double))); + QObject::connect(view, SIGNAL(manualDeskewAngleSet(double)), opt_widget, + SLOT(manualDeskewAngleSetExternally(double))); + QObject::connect(opt_widget, SIGNAL(manualDeskewAngleSet(double)), view, + SLOT(manualDeskewAngleSetExternally(double))); } } // namespace deskew diff --git a/filters/deskew/Task.h b/filters/deskew/Task.h index 410dfbf1a..561e790bc 100644 --- a/filters/deskew/Task.h +++ b/filters/deskew/Task.h @@ -19,12 +19,12 @@ #ifndef DESKEW_TASK_H_ #define DESKEW_TASK_H_ -#include "NonCopyable.h" -#include "ref_countable.h" +#include +#include #include "FilterResult.h" +#include "NonCopyable.h" #include "PageId.h" -#include -#include +#include "ref_countable.h" class TaskStatus; class QImage; @@ -45,39 +45,39 @@ class Filter; class Settings; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(intrusive_ptr filter, - intrusive_ptr settings, - intrusive_ptr image_settings, - intrusive_ptr next_task, - const PageId& page_id, - bool batch_processing, - bool debug); + public: + Task(intrusive_ptr filter, + intrusive_ptr settings, + intrusive_ptr image_settings, + intrusive_ptr next_task, + const PageId& page_id, + bool batch_processing, + bool debug); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, FilterData data); + FilterResultPtr process(const TaskStatus& status, FilterData data); -private: - class UiUpdater; + private: + class UiUpdater; - static void cleanup(const TaskStatus& status, imageproc::BinaryImage& img, const Dpi& dpi); + static void cleanup(const TaskStatus& status, imageproc::BinaryImage& img, const Dpi& dpi); - static int from150dpi(int size, int target_dpi); + static int from150dpi(int size, int target_dpi); - static QSize from150dpi(const QSize& size, const Dpi& target_dpi); + static QSize from150dpi(const QSize& size, const Dpi& target_dpi); - void updateFilterData(const TaskStatus& status, FilterData& data, bool needUpdate); + void updateFilterData(const TaskStatus& status, FilterData& data, bool needUpdate); - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrImageSettings; - intrusive_ptr m_ptrNextTask; - std::unique_ptr m_ptrDbg; - PageId m_pageId; - bool m_batchProcessing; + intrusive_ptr m_filter; + intrusive_ptr m_settings; + intrusive_ptr m_imageSettings; + intrusive_ptr m_nextTask; + std::unique_ptr m_dbg; + PageId m_pageId; + bool m_batchProcessing; }; } // namespace deskew #endif // ifndef DESKEW_TASK_H_ diff --git a/filters/deskew/Thumbnail.cpp b/filters/deskew/Thumbnail.cpp index 0ba34db74..742f7b490 100644 --- a/filters/deskew/Thumbnail.cpp +++ b/filters/deskew/Thumbnail.cpp @@ -26,47 +26,46 @@ Thumbnail::Thumbnail(intrusive_ptr thumbnail_cache, const ImageId& image_id, const ImageTransformation& xform, bool deviant) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), m_deviant(deviant) { -} + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), m_deviant(deviant) {} void Thumbnail::prePaintOverImage(QPainter& painter, const QTransform& image_to_display, const QTransform& thumb_to_display) { - painter.setRenderHint(QPainter::Antialiasing, false); + painter.setRenderHint(QPainter::Antialiasing, false); - QPen pen(QColor(0, 0, 0xd1, 70)); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); + QPen pen(QColor(0, 0, 0xd1, 70)); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); - const QRectF bounding_rect(boundingRect()); + const QRectF bounding_rect(boundingRect()); - const double cell_size = 8; - const double left = bounding_rect.left(); - const double right = bounding_rect.right(); - const double top = bounding_rect.top(); - const double bottom = bounding_rect.bottom(); - const double w = bounding_rect.width(); - const double h = bounding_rect.height(); + const double cell_size = 8; + const double left = bounding_rect.left(); + const double right = bounding_rect.right(); + const double top = bounding_rect.top(); + const double bottom = bounding_rect.bottom(); + const double w = bounding_rect.width(); + const double h = bounding_rect.height(); - const QPointF center(bounding_rect.center()); - QVector lines; - for (double y = center.y(); y > 0.0; y -= cell_size) { - lines.push_back(QLineF(left, y, right, y)); - } - for (double y = center.y(); (y += cell_size) < h;) { - lines.push_back(QLineF(left, y, right, y)); - } - for (double x = center.x(); x > 0.0; x -= cell_size) { - lines.push_back(QLineF(x, top, x, bottom)); - } - for (double x = center.x(); (x += cell_size) < w;) { - lines.push_back(QLineF(x, top, x, bottom)); - } - painter.drawLines(lines); + const QPointF center(bounding_rect.center()); + QVector lines; + for (double y = center.y(); y > 0.0; y -= cell_size) { + lines.push_back(QLineF(left, y, right, y)); + } + for (double y = center.y(); (y += cell_size) < h;) { + lines.push_back(QLineF(left, y, right, y)); + } + for (double x = center.x(); x > 0.0; x -= cell_size) { + lines.push_back(QLineF(x, top, x, bottom)); + } + for (double x = center.x(); (x += cell_size) < w;) { + lines.push_back(QLineF(x, top, x, bottom)); + } + painter.drawLines(lines); - if (m_deviant) { - paintDeviant(painter); - } + if (m_deviant) { + paintDeviant(painter); + } } // Thumbnail::paintOverImage } // namespace deskew \ No newline at end of file diff --git a/filters/deskew/Thumbnail.h b/filters/deskew/Thumbnail.h index a1ea070e2..09dde11ac 100644 --- a/filters/deskew/Thumbnail.h +++ b/filters/deskew/Thumbnail.h @@ -29,19 +29,19 @@ class ImageTransformation; namespace deskew { class Thumbnail : public ThumbnailBase { -public: - Thumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& xform, - bool deviant = false); - - void prePaintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) override; - -private: - bool m_deviant; + public: + Thumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& xform, + bool deviant = false); + + void prePaintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) override; + + private: + bool m_deviant; }; } // namespace deskew #endif diff --git a/filters/deskew/ui/DeskewOptionsWidget.ui b/filters/deskew/ui/DeskewOptionsWidget.ui index ba06cf307..5f7d096ce 100644 --- a/filters/deskew/ui/DeskewOptionsWidget.ui +++ b/filters/deskew/ui/DeskewOptionsWidget.ui @@ -19,6 +19,9 @@ Deskew + + Qt::AlignCenter + diff --git a/filters/fix_orientation/ApplyDialog.cpp b/filters/fix_orientation/ApplyDialog.cpp index e001c61ac..6be1865e7 100644 --- a/filters/fix_orientation/ApplyDialog.cpp +++ b/filters/fix_orientation/ApplyDialog.cpp @@ -17,74 +17,74 @@ */ #include "ApplyDialog.h" -#include "PageSelectionAccessor.h" #include +#include "PageSelectionAccessor.h" namespace fix_orientation { ApplyDialog::ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_selectedRanges(page_selection_accessor.selectedRanges()), - m_curPage(cur_page), - m_pBtnGroup(new QButtonGroup(this)) { - setupUi(this); - m_pBtnGroup->addButton(thisPageOnlyRB); - m_pBtnGroup->addButton(allPagesRB); - m_pBtnGroup->addButton(thisPageAndFollowersRB); - m_pBtnGroup->addButton(selectedPagesRB); - m_pBtnGroup->addButton(everyOtherRB); - m_pBtnGroup->addButton(thisEveryOtherRB); - m_pBtnGroup->addButton(everyOtherSelectedRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - everyOtherSelectedRB->setEnabled(false); - everyOtherSelectedHint->setEnabled(false); - } + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_selectedRanges(page_selection_accessor.selectedRanges()), + m_curPage(cur_page), + m_btnGroup(new QButtonGroup(this)) { + setupUi(this); + m_btnGroup->addButton(thisPageOnlyRB); + m_btnGroup->addButton(allPagesRB); + m_btnGroup->addButton(thisPageAndFollowersRB); + m_btnGroup->addButton(selectedPagesRB); + m_btnGroup->addButton(everyOtherRB); + m_btnGroup->addButton(thisEveryOtherRB); + m_btnGroup->addButton(everyOtherSelectedRB); + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + everyOtherSelectedRB->setEnabled(false); + everyOtherSelectedHint->setEnabled(false); + } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() = default; void ApplyDialog::onSubmit() { - std::set pages; + std::set pages; - // thisPageOnlyRB is intentionally not handled. - if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - emit appliedToAllPages(pages); - accept(); + // thisPageOnlyRB is intentionally not handled. + if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + emit appliedToAllPages(pages); + accept(); - return; - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - emit appliedTo(m_selectedPages); - accept(); + return; + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + emit appliedTo(m_selectedPages); + accept(); - return; - } else if (everyOtherRB->isChecked()) { - m_pages.selectEveryOther(m_curPage).swap(pages); - } else if (thisEveryOtherRB->isChecked()) { - std::set tmp; - m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); - auto it = tmp.begin(); - for (int i = 0; it != tmp.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - } else if (everyOtherSelectedRB->isChecked()) { - assert(m_selectedRanges.size() == 1); - const PageRange& range = m_selectedRanges.front(); - range.selectEveryOther(m_curPage).swap(pages); + return; + } else if (everyOtherRB->isChecked()) { + m_pages.selectEveryOther(m_curPage).swap(pages); + } else if (thisEveryOtherRB->isChecked()) { + std::set tmp; + m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); + auto it = tmp.begin(); + for (int i = 0; it != tmp.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } } + } else if (everyOtherSelectedRB->isChecked()) { + assert(m_selectedRanges.size() == 1); + const PageRange& range = m_selectedRanges.front(); + range.selectEveryOther(m_curPage).swap(pages); + } - emit appliedTo(pages); + emit appliedTo(pages); - // We assume the default connection from accept() to accepted() was removed. - accept(); + // We assume the default connection from accept() to accepted() was removed. + accept(); } // ApplyDialog::onSubmit } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/ApplyDialog.h b/filters/fix_orientation/ApplyDialog.h index dcd25d58a..84ecacde6 100644 --- a/filters/fix_orientation/ApplyDialog.h +++ b/filters/fix_orientation/ApplyDialog.h @@ -19,42 +19,42 @@ #ifndef FIX_ORIENTATION_APPLYDIALOG_H_ #define FIX_ORIENTATION_APPLYDIALOG_H_ -#include "ui_OrientationApplyDialog.h" +#include +#include +#include +#include #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include -#include +#include "ui_OrientationApplyDialog.h" class PageSelectionAccessor; -class QButtonGroup; namespace fix_orientation { class ApplyDialog : public QDialog, private Ui::OrientationApplyDialog { - Q_OBJECT -public: - ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); - ~ApplyDialog() override; + ~ApplyDialog() override; -signals: + signals: - void appliedTo(const std::set& pages); + void appliedTo(const std::set& pages); - void appliedToAllPages(const std::set& pages); + void appliedToAllPages(const std::set& pages); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - std::set m_selectedPages; - std::vector m_selectedRanges; - PageId m_curPage; - QButtonGroup* m_pBtnGroup; + private: + PageSequence m_pages; + std::set m_selectedPages; + std::vector m_selectedRanges; + PageId m_curPage; + QButtonGroup* m_btnGroup; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_APPLYDIALOG_H_ diff --git a/filters/fix_orientation/CMakeLists.txt b/filters/fix_orientation/CMakeLists.txt index a3f058d6d..cbd00ca37 100644 --- a/filters/fix_orientation/CMakeLists.txt +++ b/filters/fix_orientation/CMakeLists.txt @@ -1,27 +1,27 @@ -PROJECT("Fix Orientation Filter") +project("Fix Orientation Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - ImageView.cpp ImageView.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - ApplyDialog.cpp ApplyDialog.h - Settings.cpp Settings.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h +set( + sources + ImageView.cpp ImageView.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + ApplyDialog.cpp ApplyDialog.h + Settings.cpp Settings.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(fix_orientation STATIC ${sources} ${ui_sources}) +add_library(fix_orientation STATIC ${sources} ${ui_sources}) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) \ No newline at end of file +translation_sources(scantailor ${sources} ${ui_files}) \ No newline at end of file diff --git a/filters/fix_orientation/CacheDrivenTask.cpp b/filters/fix_orientation/CacheDrivenTask.cpp index 6bd728ee7..f812c315f 100644 --- a/filters/fix_orientation/CacheDrivenTask.cpp +++ b/filters/fix_orientation/CacheDrivenTask.cpp @@ -19,40 +19,39 @@ #include "CacheDrivenTask.h" #include -#include "Settings.h" -#include "PageInfo.h" #include "ImageTransformation.h" +#include "PageInfo.h" +#include "Settings.h" #include "ThumbnailBase.h" #include "filter_dc/AbstractFilterDataCollector.h" -#include "filter_dc/ThumbnailCollector.h" #include "filter_dc/PageOrientationCollector.h" +#include "filter_dc/ThumbnailCollector.h" #include "filters/page_split/CacheDrivenTask.h" namespace fix_orientation { CacheDrivenTask::CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task) - : m_ptrNextTask(std::move(next_task)), m_ptrSettings(std::move(settings)) { -} + : m_nextTask(std::move(next_task)), m_settings(std::move(settings)) {} CacheDrivenTask::~CacheDrivenTask() = default; void CacheDrivenTask::process(const PageInfo& page_info, AbstractFilterDataCollector* collector) { - const QRectF initial_rect(QPointF(0.0, 0.0), page_info.metadata().size()); - ImageTransformation xform(initial_rect, page_info.metadata().dpi()); - xform.setPreRotation(m_ptrSettings->getRotationFor(page_info.imageId())); + const QRectF initial_rect(QPointF(0.0, 0.0), page_info.metadata().size()); + ImageTransformation xform(initial_rect, page_info.metadata().dpi()); + xform.setPreRotation(m_settings->getRotationFor(page_info.imageId())); - if (auto* col = dynamic_cast(collector)) { - col->process(xform.preRotation()); - } + if (auto* col = dynamic_cast(collector)) { + col->process(xform.preRotation()); + } - if (m_ptrNextTask) { - m_ptrNextTask->process(page_info, collector, xform); + if (m_nextTask) { + m_nextTask->process(page_info, collector, xform); - return; - } + return; + } - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new ThumbnailBase( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); - } + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr( + new ThumbnailBase(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); + } } } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/CacheDrivenTask.h b/filters/fix_orientation/CacheDrivenTask.h index 2dde6babf..606f76f5d 100644 --- a/filters/fix_orientation/CacheDrivenTask.h +++ b/filters/fix_orientation/CacheDrivenTask.h @@ -19,8 +19,8 @@ #ifndef FIX_ORIENTATION_CACHEDRIVENTASK_H_ #define FIX_ORIENTATION_CACHEDRIVENTASK_H_ -#include "NonCopyable.h" #include "CompositeCacheDrivenTask.h" +#include "NonCopyable.h" #include "intrusive_ptr.h" class PageInfo; @@ -34,18 +34,18 @@ namespace fix_orientation { class Settings; class CacheDrivenTask : public CompositeCacheDrivenTask { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); + public: + CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, AbstractFilterDataCollector* collector) override; + void process(const PageInfo& page_info, AbstractFilterDataCollector* collector) override; -private: - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_CACHEDRIVENTASK_H_ diff --git a/filters/fix_orientation/Filter.cpp b/filters/fix_orientation/Filter.cpp index bed31a72b..8f6203cff 100644 --- a/filters/fix_orientation/Filter.cpp +++ b/filters/fix_orientation/Filter.cpp @@ -17,195 +17,194 @@ */ #include "Filter.h" +#include +#include +#include +#include +#include +#include +#include +#include "CacheDrivenTask.h" +#include "CommandLine.h" #include "FilterUiInterface.h" +#include "ImageSettings.h" #include "OptionsWidget.h" -#include "Settings.h" -#include "Task.h" -#include "CacheDrivenTask.h" #include "ProjectReader.h" #include "ProjectWriter.h" +#include "Settings.h" +#include "Task.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" -#include -#include -#include -#include -#include -#include "CommandLine.h" -#include -#include -#include "ImageSettings.h" namespace fix_orientation { Filter::Filter(const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(new Settings), m_ptrImageSettings(new ImageSettings) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); - } + : m_settings(new Settings), m_imageSettings(new ImageSettings) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, page_selection_accessor)); + } } Filter::~Filter() = default; QString Filter::getName() const { - return QCoreApplication::translate("fix_orientation::Filter", "Fix Orientation"); + return QCoreApplication::translate("fix_orientation::Filter", "Fix Orientation"); } PageView Filter::getView() const { - return IMAGE_VIEW; + return IMAGE_VIEW; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); - m_ptrImageSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); + m_imageSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) { - if (m_ptrOptionsWidget.get()) { - const OrthogonalRotation rotation(m_ptrSettings->getRotationFor(page_info.id().imageId())); - m_ptrOptionsWidget->preUpdateUI(page_info.id(), rotation); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); - } + if (m_optionsWidget.get()) { + const OrthogonalRotation rotation(m_settings->getRotationFor(page_info.id().imageId())); + m_optionsWidget->preUpdateUI(page_info.id(), rotation); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); + } } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("fix-orientation")); - writer.enumImages([&](const ImageId& image_id, const int numeric_id) { - this->writeParams(doc, filter_el, image_id, numeric_id); - }); + QDomElement filter_el(doc.createElement("fix-orientation")); + writer.enumImages( + [&](const ImageId& image_id, const int numeric_id) { this->writeParams(doc, filter_el, image_id, numeric_id); }); - saveImageSettings(writer, doc, filter_el); + saveImageSettings(writer, doc, filter_el); - return filter_el; + return filter_el; } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); - - QDomElement filter_el(filters_el.namedItem("fix-orientation").toElement()); - - const QString image_tag_name("image"); - QDomNode node(filter_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != image_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const ImageId image_id(reader.imageId(id)); - if (image_id.isNull()) { - continue; - } - - const OrthogonalRotation rotation(XmlUnmarshaller::rotation(el.namedItem("rotation").toElement())); - - m_ptrSettings->applyRotation(image_id, rotation); + m_settings->clear(); + + QDomElement filter_el(filters_el.namedItem("fix-orientation").toElement()); + + const QString image_tag_name("image"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != image_tag_name) { + continue; + } + QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const ImageId image_id(reader.imageId(id)); + if (image_id.isNull()) { + continue; } - loadImageSettings(reader, filter_el.namedItem("image-settings").toElement()); + const OrthogonalRotation rotation(XmlUnmarshaller::rotation(el.namedItem("rotation").toElement())); + + m_settings->applyRotation(image_id, rotation); + } + + loadImageSettings(reader, filter_el.namedItem("image-settings").toElement()); } // Filter::loadSettings intrusive_ptr Filter::createTask(const PageId& page_id, intrusive_ptr next_task, const bool batch_processing) { - return make_intrusive(page_id, intrusive_ptr(this), m_ptrSettings, m_ptrImageSettings, - std::move(next_task), batch_processing); + return make_intrusive(page_id, intrusive_ptr(this), m_settings, m_imageSettings, std::move(next_task), + batch_processing); } intrusive_ptr Filter::createCacheDrivenTask(intrusive_ptr next_task) { - return make_intrusive(m_ptrSettings, std::move(next_task)); + return make_intrusive(m_settings, std::move(next_task)); } void Filter::writeParams(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, int numeric_id) const { - const OrthogonalRotation rotation(m_ptrSettings->getRotationFor(image_id)); - if (rotation.toDegrees() == 0) { - return; - } + const OrthogonalRotation rotation(m_settings->getRotationFor(image_id)); + if (rotation.toDegrees() == 0) { + return; + } - XmlMarshaller marshaller(doc); + XmlMarshaller marshaller(doc); - QDomElement image_el(doc.createElement("image")); - image_el.setAttribute("id", numeric_id); - image_el.appendChild(marshaller.rotation(rotation, "rotation")); - filter_el.appendChild(image_el); + QDomElement image_el(doc.createElement("image")); + image_el.setAttribute("id", numeric_id); + image_el.appendChild(marshaller.rotation(rotation, "rotation")); + filter_el.appendChild(image_el); } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->isRotationNull(page_info.id().imageId())) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::FixOrientationParams& fixOrientationParams = defaultParams.getFixOrientationParams(); + if (!m_settings->isRotationNull(page_info.id().imageId())) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::FixOrientationParams& fixOrientationParams = defaultParams.getFixOrientationParams(); - m_ptrSettings->applyRotation(page_info.id().imageId(), fixOrientationParams.getImageRotation()); + m_settings->applyRotation(page_info.id().imageId(), fixOrientationParams.getImageRotation()); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); } void Filter::saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const { - QDomElement image_settings_el(doc.createElement("image-settings")); - writer.enumPages([&](const PageId& page_id, const int numeric_id) { - this->writeImageParams(doc, image_settings_el, page_id, numeric_id); - }); + QDomElement image_settings_el(doc.createElement("image-settings")); + writer.enumPages([&](const PageId& page_id, const int numeric_id) { + this->writeImageParams(doc, image_settings_el, page_id, numeric_id); + }); - filter_el.appendChild(image_settings_el); + filter_el.appendChild(image_settings_el); } void Filter::writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const std::unique_ptr params(m_ptrImageSettings->getPageParams(page_id)); - if (!params) { - return; - } + const std::unique_ptr params(m_imageSettings->getPageParams(page_id)); + if (!params) { + return; + } - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); - page_el.appendChild(params->toXml(doc, "image-params")); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); + page_el.appendChild(params->toXml(doc, "image-params")); - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } void Filter::loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el) { - m_ptrImageSettings->clear(); - - const QString page_tag_name("page"); - QDomNode node(image_settings_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const QDomElement params_el(el.namedItem("image-params").toElement()); - if (params_el.isNull()) { - continue; - } - - const ImageSettings::PageParams params(params_el); - m_ptrImageSettings->setPageParams(page_id, params); + m_imageSettings->clear(); + + const QString page_tag_name("page"); + QDomNode node(image_settings_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; + } + + const QDomElement params_el(el.namedItem("image-params").toElement()); + if (params_el.isNull()) { + continue; + } + + const ImageSettings::PageParams params(params_el); + m_imageSettings->setPageParams(page_id, params); + } } } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/Filter.h b/filters/fix_orientation/Filter.h index 64729c2e8..4977cf642 100644 --- a/filters/fix_orientation/Filter.h +++ b/filters/fix_orientation/Filter.h @@ -19,12 +19,12 @@ #ifndef FIX_ORIENTATION_FILTER_H_ #define FIX_ORIENTATION_FILTER_H_ -#include "NonCopyable.h" #include "AbstractFilter.h" -#include "PageView.h" #include "FilterResult.h" -#include "intrusive_ptr.h" +#include "NonCopyable.h" +#include "PageView.h" #include "SafeDeletingQObjectPtr.h" +#include "intrusive_ptr.h" class ImageId; class PageSelectionAccessor; @@ -49,47 +49,47 @@ class Settings; * must be called from the GUI thread only. */ class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) -public: - explicit Filter(const PageSelectionAccessor& page_selection_accessor); + public: + explicit Filter(const PageSelectionAccessor& page_selection_accessor); - ~Filter() override; + ~Filter() override; - QString getName() const override; + QString getName() const override; - PageView getView() const override; + PageView getView() const override; - void performRelinking(const AbstractRelinker& relinker) override; + void performRelinking(const AbstractRelinker& relinker) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; + QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void loadDefaultSettings(const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - intrusive_ptr createTask(const PageId& page_id, - intrusive_ptr next_task, - bool batch_processing); + intrusive_ptr createTask(const PageId& page_id, + intrusive_ptr next_task, + bool batch_processing); - intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); + intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); - OptionsWidget* optionsWidget(); + OptionsWidget* optionsWidget(); -private: - void writeParams(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, int numeric_id) const; + private: + void writeParams(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, int numeric_id) const; - void saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const; + void saveImageSettings(const ProjectWriter& writer, QDomDocument& doc, QDomElement& filter_el) const; - void writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + void writeImageParams(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - void loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el); + void loadImageSettings(const ProjectReader& reader, const QDomElement& image_settings_el); - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrImageSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; + intrusive_ptr m_settings; + intrusive_ptr m_imageSettings; + SafeDeletingQObjectPtr m_optionsWidget; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_FILTER_H_ diff --git a/filters/fix_orientation/ImageView.cpp b/filters/fix_orientation/ImageView.cpp index 4b1643803..dc2bd67eb 100644 --- a/filters/fix_orientation/ImageView.cpp +++ b/filters/fix_orientation/ImageView.cpp @@ -21,24 +21,24 @@ namespace fix_orientation { ImageView::ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform) - : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), - m_dragHandler(*this), - m_zoomHandler(*this), - m_xform(xform) { - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), + m_dragHandler(*this), + m_zoomHandler(*this), + m_xform(xform) { + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); } ImageView::~ImageView() = default; void ImageView::setPreRotation(const OrthogonalRotation rotation) { - if (m_xform.preRotation() == rotation) { - return; - } + if (m_xform.preRotation() == rotation) { + return; + } - m_xform.setPreRotation(rotation); + m_xform.setPreRotation(rotation); - // This should call update() by itself. - updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); + // This should call update() by itself. + updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); } } // namespace fix_orientation diff --git a/filters/fix_orientation/ImageView.h b/filters/fix_orientation/ImageView.h index ce2ee58db..c59f09240 100644 --- a/filters/fix_orientation/ImageView.h +++ b/filters/fix_orientation/ImageView.h @@ -19,28 +19,28 @@ #ifndef FIX_ORIENTATION_IMAGEVIEW_H_ #define FIX_ORIENTATION_IMAGEVIEW_H_ +#include "DragHandler.h" +#include "ImageTransformation.h" #include "ImageViewBase.h" #include "OrthogonalRotation.h" -#include "ImageTransformation.h" -#include "DragHandler.h" #include "ZoomHandler.h" namespace fix_orientation { class ImageView : public ImageViewBase { - Q_OBJECT -public: - ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform); + Q_OBJECT + public: + ImageView(const QImage& image, const QImage& downscaled_image, const ImageTransformation& xform); - ~ImageView() override; + ~ImageView() override; -public slots: + public slots: - void setPreRotation(OrthogonalRotation rotation); + void setPreRotation(OrthogonalRotation rotation); -private: - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; - ImageTransformation m_xform; + private: + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; + ImageTransformation m_xform; }; } // namespace fix_orientation #endif diff --git a/filters/fix_orientation/OptionsWidget.cpp b/filters/fix_orientation/OptionsWidget.cpp index f99f599cb..e19845530 100644 --- a/filters/fix_orientation/OptionsWidget.cpp +++ b/filters/fix_orientation/OptionsWidget.cpp @@ -17,135 +17,139 @@ */ #include "OptionsWidget.h" -#include "Filter.h" -#include "ApplyDialog.h" -#include "Settings.h" -#include "ProjectPages.h" #include #include +#include "ApplyDialog.h" +#include "Filter.h" +#include "ProjectPages.h" +#include "Settings.h" namespace fix_orientation { OptionsWidget::OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), m_pageSelectionAccessor(page_selection_accessor) { - setupUi(this); + : m_settings(std::move(settings)), m_pageSelectionAccessor(page_selection_accessor) { + setupUi(this); - setupUiConnections(); + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::preUpdateUI(const PageId& page_id, const OrthogonalRotation rotation) { - removeUiConnections(); + removeUiConnections(); - m_pageId = page_id; - m_rotation = rotation; - setRotationPixmap(); + m_pageId = page_id; + m_rotation = rotation; + setRotationPixmap(); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::postUpdateUI(const OrthogonalRotation rotation) { - removeUiConnections(); + removeUiConnections(); - setRotation(rotation); + setRotation(rotation); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::rotateLeft() { - OrthogonalRotation rotation(m_rotation); - rotation.prevClockwiseDirection(); - setRotation(rotation); + OrthogonalRotation rotation(m_rotation); + rotation.prevClockwiseDirection(); + setRotation(rotation); } void OptionsWidget::rotateRight() { - OrthogonalRotation rotation(m_rotation); - rotation.nextClockwiseDirection(); - setRotation(rotation); + OrthogonalRotation rotation(m_rotation); + rotation.nextClockwiseDirection(); + setRotation(rotation); } void OptionsWidget::resetRotation() { - setRotation(OrthogonalRotation()); + setRotation(OrthogonalRotation()); } void OptionsWidget::showApplyToDialog() { - auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(appliedTo(const std::set&)), this, SLOT(appliedTo(const std::set&))); - connect(dialog, SIGNAL(appliedToAllPages(const std::set&)), this, - SLOT(appliedToAllPages(const std::set&))); - dialog->show(); + auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(appliedTo(const std::set&)), this, SLOT(appliedTo(const std::set&))); + connect(dialog, SIGNAL(appliedToAllPages(const std::set&)), this, + SLOT(appliedToAllPages(const std::set&))); + dialog->show(); } void OptionsWidget::appliedTo(const std::set& pages) { - if (pages.empty()) { - return; - } + if (pages.empty()) { + return; + } - m_ptrSettings->applyRotation(pages, m_rotation); + m_settings->applyRotation(pages, m_rotation); - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } } void OptionsWidget::appliedToAllPages(const std::set& pages) { - m_ptrSettings->applyRotation(pages, m_rotation); - emit invalidateAllThumbnails(); + m_settings->applyRotation(pages, m_rotation); + emit invalidateAllThumbnails(); } void OptionsWidget::setRotation(const OrthogonalRotation& rotation) { - if (rotation == m_rotation) { - return; - } + if (rotation == m_rotation) { + return; + } - m_rotation = rotation; - setRotationPixmap(); + m_rotation = rotation; + setRotationPixmap(); - m_ptrSettings->applyRotation(m_pageId.imageId(), rotation); + m_settings->applyRotation(m_pageId.imageId(), rotation); - emit rotated(rotation); - emit invalidateThumbnail(m_pageId); + emit rotated(rotation); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::setRotationPixmap() { - const char* path = nullptr; - - switch (m_rotation.toDegrees()) { - case 0: - path = ":/icons/big-up-arrow.png"; - break; - case 90: - path = ":/icons/big-right-arrow.png"; - break; - case 180: - path = ":/icons/big-down-arrow.png"; - break; - case 270: - path = ":/icons/big-left-arrow.png"; - break; - default: - assert(!"Unreachable"); - } - - rotationIndicator->setPixmap(QPixmap(path)); + const char* path = nullptr; + + switch (m_rotation.toDegrees()) { + case 0: + path = ":/icons/big-up-arrow.png"; + break; + case 90: + path = ":/icons/big-right-arrow.png"; + break; + case 180: + path = ":/icons/big-down-arrow.png"; + break; + case 270: + path = ":/icons/big-left-arrow.png"; + break; + default: + assert(!"Unreachable"); + } + + rotationIndicator->setPixmap(QPixmap(path)); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); - connect(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); - connect(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); - connect(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); + CONNECT(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); + CONNECT(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); + CONNECT(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); + CONNECT(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); } +#undef CONNECT + void OptionsWidget::removeUiConnections() { - disconnect(rotateLeftBtn, SIGNAL(clicked()), this, SLOT(rotateLeft())); - disconnect(rotateRightBtn, SIGNAL(clicked()), this, SLOT(rotateRight())); - disconnect(resetBtn, SIGNAL(clicked()), this, SLOT(resetRotation())); - disconnect(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/OptionsWidget.h b/filters/fix_orientation/OptionsWidget.h index bd7ae2596..990e901e5 100644 --- a/filters/fix_orientation/OptionsWidget.h +++ b/filters/fix_orientation/OptionsWidget.h @@ -19,58 +19,61 @@ #ifndef FIX_ORIENTATION_OPTIONSWIDGET_H_ #define FIX_ORIENTATION_OPTIONSWIDGET_H_ -#include "ui_OrientationOptionsWidget.h" +#include #include "FilterOptionsWidget.h" #include "OrthogonalRotation.h" #include "PageId.h" -#include "intrusive_ptr.h" #include "PageSelectionAccessor.h" +#include "intrusive_ptr.h" +#include "ui_OrientationOptionsWidget.h" namespace fix_orientation { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::OrientationOptionsWidget { - Q_OBJECT -public: - OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + + ~OptionsWidget() override; - ~OptionsWidget() override; + void preUpdateUI(const PageId& page_id, OrthogonalRotation rotation); - void preUpdateUI(const PageId& page_id, OrthogonalRotation rotation); + void postUpdateUI(OrthogonalRotation rotation); - void postUpdateUI(OrthogonalRotation rotation); + signals: -signals: + void rotated(OrthogonalRotation rotation); - void rotated(OrthogonalRotation rotation); + private slots: -private slots: + void rotateLeft(); - void rotateLeft(); + void rotateRight(); - void rotateRight(); + void resetRotation(); - void resetRotation(); + void showApplyToDialog(); - void showApplyToDialog(); + void appliedTo(const std::set& pages); - void appliedTo(const std::set& pages); + void appliedToAllPages(const std::set& pages); - void appliedToAllPages(const std::set& pages); + private: + void setRotation(const OrthogonalRotation& rotation); -private: - void setRotation(const OrthogonalRotation& rotation); + void setRotationPixmap(); - void setRotationPixmap(); + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + intrusive_ptr m_settings; + PageSelectionAccessor m_pageSelectionAccessor; + PageId m_pageId; + OrthogonalRotation m_rotation; - intrusive_ptr m_ptrSettings; - PageSelectionAccessor m_pageSelectionAccessor; - PageId m_pageId; - OrthogonalRotation m_rotation; + std::list m_connectionList; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_OPTIONSWIDGET_H_ diff --git a/filters/fix_orientation/Settings.cpp b/filters/fix_orientation/Settings.cpp index 54b9bf647..0b925125c 100644 --- a/filters/fix_orientation/Settings.cpp +++ b/filters/fix_orientation/Settings.cpp @@ -17,9 +17,9 @@ */ #include "Settings.h" -#include "Utils.h" -#include "RelinkablePath.h" #include "AbstractRelinker.h" +#include "RelinkablePath.h" +#include "Utils.h" namespace fix_orientation { Settings::Settings() = default; @@ -27,55 +27,55 @@ Settings::Settings() = default; Settings::~Settings() = default; void Settings::clear() { - QMutexLocker locker(&m_mutex); - m_perImageRotation.clear(); + QMutexLocker locker(&m_mutex); + m_perImageRotation.clear(); } void Settings::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); - PerImageRotation new_rotations; + QMutexLocker locker(&m_mutex); + PerImageRotation new_rotations; - for (const PerImageRotation::value_type& kv : m_perImageRotation) { - const RelinkablePath old_path(kv.first.filePath(), RelinkablePath::File); - ImageId new_image_id(kv.first); - new_image_id.setFilePath(relinker.substitutionPathFor(old_path)); - new_rotations.insert(PerImageRotation::value_type(new_image_id, kv.second)); - } + for (const PerImageRotation::value_type& kv : m_perImageRotation) { + const RelinkablePath old_path(kv.first.filePath(), RelinkablePath::File); + ImageId new_image_id(kv.first); + new_image_id.setFilePath(relinker.substitutionPathFor(old_path)); + new_rotations.insert(PerImageRotation::value_type(new_image_id, kv.second)); + } - m_perImageRotation.swap(new_rotations); + m_perImageRotation.swap(new_rotations); } void Settings::applyRotation(const ImageId& image_id, const OrthogonalRotation rotation) { - QMutexLocker locker(&m_mutex); - setImageRotationLocked(image_id, rotation); + QMutexLocker locker(&m_mutex); + setImageRotationLocked(image_id, rotation); } void Settings::applyRotation(const std::set& pages, const OrthogonalRotation rotation) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - for (const PageId& page : pages) { - setImageRotationLocked(page.imageId(), rotation); - } + for (const PageId& page : pages) { + setImageRotationLocked(page.imageId(), rotation); + } } OrthogonalRotation Settings::getRotationFor(const ImageId& image_id) const { - QMutexLocker locker(&m_mutex); - - auto it(m_perImageRotation.find(image_id)); - if (it != m_perImageRotation.end()) { - return it->second; - } else { - return OrthogonalRotation(); - } + QMutexLocker locker(&m_mutex); + + auto it(m_perImageRotation.find(image_id)); + if (it != m_perImageRotation.end()) { + return it->second; + } else { + return OrthogonalRotation(); + } } void Settings::setImageRotationLocked(const ImageId& image_id, const OrthogonalRotation& rotation) { - Utils::mapSetValue(m_perImageRotation, image_id, rotation); + Utils::mapSetValue(m_perImageRotation, image_id, rotation); } bool Settings::isRotationNull(const ImageId& image_id) const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return (m_perImageRotation.find(image_id) == m_perImageRotation.end()); + return (m_perImageRotation.find(image_id) == m_perImageRotation.end()); } } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/Settings.h b/filters/fix_orientation/Settings.h index 23e78a240..2fc4a2461 100644 --- a/filters/fix_orientation/Settings.h +++ b/filters/fix_orientation/Settings.h @@ -19,45 +19,45 @@ #ifndef FIX_ORIENTATION_SETTINGS_H_ #define FIX_ORIENTATION_SETTINGS_H_ -#include "ref_countable.h" +#include +#include +#include +#include "ImageId.h" #include "NonCopyable.h" #include "OrthogonalRotation.h" -#include "ImageId.h" #include "PageId.h" -#include -#include -#include +#include "ref_countable.h" class AbstractRelinker; namespace fix_orientation { class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) + DECLARE_NON_COPYABLE(Settings) -public: - Settings(); + public: + Settings(); - ~Settings() override; + ~Settings() override; - void clear(); + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - void applyRotation(const ImageId& image_id, OrthogonalRotation rotation); + void applyRotation(const ImageId& image_id, OrthogonalRotation rotation); - void applyRotation(const std::set& pages, OrthogonalRotation rotation); + void applyRotation(const std::set& pages, OrthogonalRotation rotation); - OrthogonalRotation getRotationFor(const ImageId& image_id) const; + OrthogonalRotation getRotationFor(const ImageId& image_id) const; - bool isRotationNull(const ImageId& image_id) const; + bool isRotationNull(const ImageId& image_id) const; -private: - typedef std::unordered_map PerImageRotation; + private: + typedef std::unordered_map PerImageRotation; - void setImageRotationLocked(const ImageId& image_id, const OrthogonalRotation& rotation); + void setImageRotationLocked(const ImageId& image_id, const OrthogonalRotation& rotation); - mutable QMutex m_mutex; - PerImageRotation m_perImageRotation; + mutable QMutex m_mutex; + PerImageRotation m_perImageRotation; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_SETTINGS_H_ diff --git a/filters/fix_orientation/Task.cpp b/filters/fix_orientation/Task.cpp index b4e7bde61..bab99641e 100644 --- a/filters/fix_orientation/Task.cpp +++ b/filters/fix_orientation/Task.cpp @@ -19,40 +19,38 @@ #include #include -#include "Task.h" +#include "Dpm.h" #include "Filter.h" +#include "FilterUiInterface.h" +#include "ImageView.h" #include "OptionsWidget.h" #include "Settings.h" -#include "filters/page_split/Task.h" +#include "Task.h" #include "TaskStatus.h" -#include "ImageView.h" -#include "FilterUiInterface.h" -#include "Dpm.h" +#include "filters/page_split/Task.h" namespace fix_orientation { using imageproc::BinaryThreshold; class Task::UiUpdater : public FilterResult { -public: - UiUpdater(intrusive_ptr filter, - const QImage& image, - const ImageId& image_id, - const ImageTransformation& xform, - bool batch_processing); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - QImage m_image; - QImage m_downscaledImage; - ImageId m_imageId; - ImageTransformation m_xform; - bool m_batchProcessing; + public: + UiUpdater(intrusive_ptr filter, + const QImage& image, + const ImageId& image_id, + const ImageTransformation& xform, + bool batch_processing); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + QImage m_image; + QImage m_downscaledImage; + ImageId m_imageId; + ImageTransformation m_xform; + bool m_batchProcessing; }; @@ -62,43 +60,42 @@ Task::Task(const PageId& page_id, intrusive_ptr image_settings, intrusive_ptr next_task, const bool batch_processing) - : m_ptrFilter(std::move(filter)), - m_ptrNextTask(std::move(next_task)), - m_ptrSettings(std::move(settings)), - m_ptrImageSettings(std::move(image_settings)), - m_pageId(page_id), - m_imageId(m_pageId.imageId()), - m_batchProcessing(batch_processing) { -} + : m_filter(std::move(filter)), + m_nextTask(std::move(next_task)), + m_settings(std::move(settings)), + m_imageSettings(std::move(image_settings)), + m_pageId(page_id), + m_imageId(m_pageId.imageId()), + m_batchProcessing(batch_processing) {} Task::~Task() = default; FilterResultPtr Task::process(const TaskStatus& status, FilterData data) { - // This function is executed from the worker thread. + // This function is executed from the worker thread. - status.throwIfCancelled(); + status.throwIfCancelled(); - updateFilterData(data); + updateFilterData(data); - ImageTransformation xform(data.xform()); - xform.setPreRotation(m_ptrSettings->getRotationFor(m_imageId)); + ImageTransformation xform(data.xform()); + xform.setPreRotation(m_settings->getRotationFor(m_imageId)); - if (m_ptrNextTask) { - return m_ptrNextTask->process(status, FilterData(data, xform)); - } else { - return make_intrusive(m_ptrFilter, data.origImage(), m_imageId, xform, m_batchProcessing); - } + if (m_nextTask) { + return m_nextTask->process(status, FilterData(data, xform)); + } else { + return make_intrusive(m_filter, data.origImage(), m_imageId, xform, m_batchProcessing); + } } void Task::updateFilterData(FilterData& data) { - if (const std::unique_ptr params = m_ptrImageSettings->getPageParams(m_pageId)) { - data.updateImageParams(*params); - } else { - ImageSettings::PageParams new_params(BinaryThreshold::otsuThreshold(data.grayImage())); - - m_ptrImageSettings->setPageParams(m_pageId, new_params); - data.updateImageParams(new_params); - } + if (const std::unique_ptr params = m_imageSettings->getPageParams(m_pageId)) { + data.updateImageParams(*params); + } else { + ImageSettings::PageParams new_params(BinaryThreshold::otsuThreshold(data.grayImage()), true); + + m_imageSettings->setPageParams(m_pageId, new_params); + data.updateImageParams(new_params); + } } /*============================ Task::UiUpdater ========================*/ @@ -108,28 +105,27 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const ImageId& image_id, const ImageTransformation& xform, const bool batch_processing) - : m_ptrFilter(std::move(filter)), - m_image(image), - m_downscaledImage(ImageView::createDownscaledImage(image)), - m_imageId(image_id), - m_xform(xform), - m_batchProcessing(batch_processing) { -} + : m_filter(std::move(filter)), + m_image(image), + m_downscaledImage(ImageView::createDownscaledImage(image)), + m_imageId(image_id), + m_xform(xform), + m_batchProcessing(batch_processing) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(m_xform.preRotation()); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(m_xform.preRotation()); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - ui->invalidateThumbnail(PageId(m_imageId)); + ui->invalidateThumbnail(PageId(m_imageId)); - if (m_batchProcessing) { - return; - } + if (m_batchProcessing) { + return; + } - auto* view = new ImageView(m_image, m_downscaledImage, m_xform); - ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP); - QObject::connect(opt_widget, SIGNAL(rotated(OrthogonalRotation)), view, SLOT(setPreRotation(OrthogonalRotation))); + auto* view = new ImageView(m_image, m_downscaledImage, m_xform); + ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP); + QObject::connect(opt_widget, SIGNAL(rotated(OrthogonalRotation)), view, SLOT(setPreRotation(OrthogonalRotation))); } } // namespace fix_orientation \ No newline at end of file diff --git a/filters/fix_orientation/Task.h b/filters/fix_orientation/Task.h index 563de0c31..1bb75eb10 100644 --- a/filters/fix_orientation/Task.h +++ b/filters/fix_orientation/Task.h @@ -20,11 +20,11 @@ #define FIX_ORIENTATION_TASK_H_ #include -#include "NonCopyable.h" -#include "ref_countable.h" #include "FilterResult.h" -#include "intrusive_ptr.h" #include "ImageId.h" +#include "NonCopyable.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class TaskStatus; class QImage; @@ -38,32 +38,32 @@ class Filter; class Settings; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(const PageId& page_id, - intrusive_ptr filter, - intrusive_ptr settings, - intrusive_ptr image_settings, - intrusive_ptr next_task, - bool batch_processing); + public: + Task(const PageId& page_id, + intrusive_ptr filter, + intrusive_ptr settings, + intrusive_ptr image_settings, + intrusive_ptr next_task, + bool batch_processing); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, FilterData data); + FilterResultPtr process(const TaskStatus& status, FilterData data); -private: - class UiUpdater; + private: + class UiUpdater; - void updateFilterData(FilterData& data); + void updateFilterData(FilterData& data); - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrNextTask; // if null, this task is the final one - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrImageSettings; - PageId m_pageId; - ImageId m_imageId; - bool m_batchProcessing; + intrusive_ptr m_filter; + intrusive_ptr m_nextTask; // if null, this task is the final one + intrusive_ptr m_settings; + intrusive_ptr m_imageSettings; + PageId m_pageId; + ImageId m_imageId; + bool m_batchProcessing; }; } // namespace fix_orientation #endif // ifndef FIX_ORIENTATION_TASK_H_ diff --git a/filters/fix_orientation/ui/OrientationOptionsWidget.ui b/filters/fix_orientation/ui/OrientationOptionsWidget.ui index c96c7d41d..0582c72dd 100644 --- a/filters/fix_orientation/ui/OrientationOptionsWidget.ui +++ b/filters/fix_orientation/ui/OrientationOptionsWidget.ui @@ -19,6 +19,9 @@ Rotate + + Qt::AlignCenter + diff --git a/filters/output/ApplyColorsDialog.cpp b/filters/output/ApplyColorsDialog.cpp index 93568ff0f..ac59620f7 100644 --- a/filters/output/ApplyColorsDialog.cpp +++ b/filters/output/ApplyColorsDialog.cpp @@ -23,45 +23,45 @@ namespace output { ApplyColorsDialog::ApplyColorsDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_curPage(cur_page), - m_pScopeGroup(new QButtonGroup(this)) { - setupUi(this); - m_pScopeGroup->addButton(thisPageRB); - m_pScopeGroup->addButton(allPagesRB); - m_pScopeGroup->addButton(thisPageAndFollowersRB); - m_pScopeGroup->addButton(selectedPagesRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - } + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_curPage(cur_page), + m_scopeGroup(new QButtonGroup(this)) { + setupUi(this); + m_scopeGroup->addButton(thisPageRB); + m_scopeGroup->addButton(allPagesRB); + m_scopeGroup->addButton(thisPageAndFollowersRB); + m_scopeGroup->addButton(selectedPagesRB); + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyColorsDialog::~ApplyColorsDialog() = default; void ApplyColorsDialog::onSubmit() { - std::set pages; + std::set pages; - // thisPageRB is intentionally not handled. - if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - emit accepted(m_selectedPages); - accept(); + // thisPageRB is intentionally not handled. + if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + emit accepted(m_selectedPages); + accept(); - return; - } + return; + } - emit accepted(pages); + emit accepted(pages); - // We assume the default connection from accepted() to accept() - // was removed. - accept(); + // We assume the default connection from accepted() to accept() + // was removed. + accept(); } } // namespace output \ No newline at end of file diff --git a/filters/output/ApplyColorsDialog.h b/filters/output/ApplyColorsDialog.h index 4c525c750..8f2a93aca 100644 --- a/filters/output/ApplyColorsDialog.h +++ b/filters/output/ApplyColorsDialog.h @@ -19,37 +19,37 @@ #ifndef OUTPUT_APPLYCOLORSDIALOG_H_ #define OUTPUT_APPLYCOLORSDIALOG_H_ -#include "ui_OutputApplyColorsDialog.h" +#include +#include +#include #include "PageId.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include +#include "ui_OutputApplyColorsDialog.h" class PageSelectionAccessor; -class QButtonGroup; namespace output { class ApplyColorsDialog : public QDialog, private Ui::OutputApplyColorsDialog { - Q_OBJECT -public: - ApplyColorsDialog(QWidget* parent, const PageId& page_id, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ApplyColorsDialog(QWidget* parent, const PageId& page_id, const PageSelectionAccessor& page_selection_accessor); - ~ApplyColorsDialog() override; + ~ApplyColorsDialog() override; -signals: + signals: - void accepted(const std::set& pages); + void accepted(const std::set& pages); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - std::set m_selectedPages; - PageId m_curPage; - QButtonGroup* m_pScopeGroup; + private: + PageSequence m_pages; + std::set m_selectedPages; + PageId m_curPage; + QButtonGroup* m_scopeGroup; }; } // namespace output #endif // ifndef OUTPUT_APPLYCOLORSDIALOG_H_ diff --git a/filters/output/BinarizationOptionsWidget.h b/filters/output/BinarizationOptionsWidget.h index 541956697..ab4a61f95 100644 --- a/filters/output/BinarizationOptionsWidget.h +++ b/filters/output/BinarizationOptionsWidget.h @@ -2,21 +2,21 @@ #ifndef SCANTAILOR_BINARIZATIONOPTIONSWIDGET_H #define SCANTAILOR_BINARIZATIONOPTIONSWIDGET_H -#include #include +#include namespace output { class BinarizationOptionsWidget : public QWidget { - Q_OBJECT -public: - virtual void preUpdateUI(const PageId& m_pageId) = 0; + Q_OBJECT + public: + virtual void updateUi(const PageId& m_pageId) = 0; -signals: + signals: - /** - * \brief To be emitted by subclasses when their state has changed. - */ - void stateChanged(); + /** + * \brief To be emitted by subclasses when their state has changed. + */ + void stateChanged(); }; } // namespace output diff --git a/filters/output/BlackWhiteOptions.cpp b/filters/output/BlackWhiteOptions.cpp index 3ead784ce..7a9911311 100644 --- a/filters/output/BlackWhiteOptions.cpp +++ b/filters/output/BlackWhiteOptions.cpp @@ -17,264 +17,260 @@ */ #include "BlackWhiteOptions.h" -#include "../../Utils.h" #include #include +#include "../../Utils.h" namespace output { BlackWhiteOptions::BlackWhiteOptions() - : m_thresholdAdjustment(0), - savitzkyGolaySmoothingEnabled(true), - morphologicalSmoothingEnabled(true), - m_normalizeIllumination(true), - windowSize(200), - sauvolaCoef(0.34), - wolfLowerBound(1), - wolfUpperBound(254), - wolfCoef(0.3), - binarizationMethod(OTSU) { -} + : m_thresholdAdjustment(0), + m_savitzkyGolaySmoothingEnabled(true), + m_morphologicalSmoothingEnabled(true), + m_normalizeIllumination(true), + m_windowSize(200), + m_sauvolaCoef(0.34), + m_wolfLowerBound(1), + m_wolfUpperBound(254), + m_wolfCoef(0.3), + m_binarizationMethod(OTSU) {} BlackWhiteOptions::BlackWhiteOptions(const QDomElement& el) - : m_thresholdAdjustment(el.attribute("thresholdAdj").toInt()), - savitzkyGolaySmoothingEnabled(el.attribute("savitzkyGolaySmoothing") == "1"), - morphologicalSmoothingEnabled(el.attribute("morphologicalSmoothing") == "1"), - m_normalizeIllumination(el.attribute("normalizeIlluminationBW") == "1"), - windowSize(el.attribute("windowSize").toInt()), - sauvolaCoef(el.attribute("sauvolaCoef").toDouble()), - wolfLowerBound(el.attribute("wolfLowerBound").toInt()), - wolfUpperBound(el.attribute("wolfUpperBound").toInt()), - wolfCoef(el.attribute("wolfCoef").toDouble()), - binarizationMethod(parseBinarizationMethod(el.attribute("binarizationMethod"))), - colorSegmenterOptions(el.namedItem("color-segmenter-options").toElement()) { -} + : m_thresholdAdjustment(el.attribute("thresholdAdj").toInt()), + m_savitzkyGolaySmoothingEnabled(el.attribute("savitzkyGolaySmoothing") == "1"), + m_morphologicalSmoothingEnabled(el.attribute("morphologicalSmoothing") == "1"), + m_normalizeIllumination(el.attribute("normalizeIlluminationBW") == "1"), + m_windowSize(el.attribute("windowSize").toInt()), + m_sauvolaCoef(el.attribute("sauvolaCoef").toDouble()), + m_wolfLowerBound(el.attribute("wolfLowerBound").toInt()), + m_wolfUpperBound(el.attribute("wolfUpperBound").toInt()), + m_wolfCoef(el.attribute("wolfCoef").toDouble()), + m_binarizationMethod(parseBinarizationMethod(el.attribute("binarizationMethod"))), + m_colorSegmenterOptions(el.namedItem("color-segmenter-options").toElement()) {} QDomElement BlackWhiteOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("thresholdAdj", m_thresholdAdjustment); - el.setAttribute("savitzkyGolaySmoothing", savitzkyGolaySmoothingEnabled ? "1" : "0"); - el.setAttribute("morphologicalSmoothing", morphologicalSmoothingEnabled ? "1" : "0"); - el.setAttribute("normalizeIlluminationBW", m_normalizeIllumination ? "1" : "0"); - el.setAttribute("windowSize", windowSize); - el.setAttribute("sauvolaCoef", Utils::doubleToString(sauvolaCoef)); - el.setAttribute("wolfLowerBound", wolfLowerBound); - el.setAttribute("wolfUpperBound", wolfUpperBound); - el.setAttribute("wolfCoef", Utils::doubleToString(wolfCoef)); - el.setAttribute("binarizationMethod", formatBinarizationMethod(binarizationMethod)); - el.appendChild(colorSegmenterOptions.toXml(doc, "color-segmenter-options")); - - return el; + QDomElement el(doc.createElement(name)); + el.setAttribute("thresholdAdj", m_thresholdAdjustment); + el.setAttribute("savitzkyGolaySmoothing", m_savitzkyGolaySmoothingEnabled ? "1" : "0"); + el.setAttribute("morphologicalSmoothing", m_morphologicalSmoothingEnabled ? "1" : "0"); + el.setAttribute("normalizeIlluminationBW", m_normalizeIllumination ? "1" : "0"); + el.setAttribute("windowSize", m_windowSize); + el.setAttribute("sauvolaCoef", Utils::doubleToString(m_sauvolaCoef)); + el.setAttribute("wolfLowerBound", m_wolfLowerBound); + el.setAttribute("wolfUpperBound", m_wolfUpperBound); + el.setAttribute("wolfCoef", Utils::doubleToString(m_wolfCoef)); + el.setAttribute("binarizationMethod", formatBinarizationMethod(m_binarizationMethod)); + el.appendChild(m_colorSegmenterOptions.toXml(doc, "color-segmenter-options")); + + return el; } bool BlackWhiteOptions::operator==(const BlackWhiteOptions& other) const { - return (m_thresholdAdjustment == other.m_thresholdAdjustment) - && (savitzkyGolaySmoothingEnabled == other.savitzkyGolaySmoothingEnabled) - && (morphologicalSmoothingEnabled == other.morphologicalSmoothingEnabled) - && (m_normalizeIllumination == other.m_normalizeIllumination) && (windowSize == other.windowSize) - && (sauvolaCoef == other.sauvolaCoef) && (wolfLowerBound == other.wolfLowerBound) - && (wolfUpperBound == other.wolfUpperBound) && (wolfCoef == other.wolfCoef) - && (binarizationMethod == other.binarizationMethod) - && (colorSegmenterOptions == other.colorSegmenterOptions); + return (m_thresholdAdjustment == other.m_thresholdAdjustment) + && (m_savitzkyGolaySmoothingEnabled == other.m_savitzkyGolaySmoothingEnabled) + && (m_morphologicalSmoothingEnabled == other.m_morphologicalSmoothingEnabled) + && (m_normalizeIllumination == other.m_normalizeIllumination) && (m_windowSize == other.m_windowSize) + && (m_sauvolaCoef == other.m_sauvolaCoef) && (m_wolfLowerBound == other.m_wolfLowerBound) + && (m_wolfUpperBound == other.m_wolfUpperBound) && (m_wolfCoef == other.m_wolfCoef) + && (m_binarizationMethod == other.m_binarizationMethod) + && (m_colorSegmenterOptions == other.m_colorSegmenterOptions); } bool BlackWhiteOptions::operator!=(const BlackWhiteOptions& other) const { - return !(*this == other); + return !(*this == other); } bool BlackWhiteOptions::isSavitzkyGolaySmoothingEnabled() const { - return savitzkyGolaySmoothingEnabled; + return m_savitzkyGolaySmoothingEnabled; } void BlackWhiteOptions::setSavitzkyGolaySmoothingEnabled(bool savitzkyGolaySmoothingEnabled) { - BlackWhiteOptions::savitzkyGolaySmoothingEnabled = savitzkyGolaySmoothingEnabled; + BlackWhiteOptions::m_savitzkyGolaySmoothingEnabled = savitzkyGolaySmoothingEnabled; } bool BlackWhiteOptions::isMorphologicalSmoothingEnabled() const { - return morphologicalSmoothingEnabled; + return m_morphologicalSmoothingEnabled; } void BlackWhiteOptions::setMorphologicalSmoothingEnabled(bool morphologicalSmoothingEnabled) { - BlackWhiteOptions::morphologicalSmoothingEnabled = morphologicalSmoothingEnabled; + BlackWhiteOptions::m_morphologicalSmoothingEnabled = morphologicalSmoothingEnabled; } int BlackWhiteOptions::getWindowSize() const { - return windowSize; + return m_windowSize; } void BlackWhiteOptions::setWindowSize(int windowSize) { - BlackWhiteOptions::windowSize = windowSize; + BlackWhiteOptions::m_windowSize = windowSize; } double BlackWhiteOptions::getSauvolaCoef() const { - return sauvolaCoef; + return m_sauvolaCoef; } void BlackWhiteOptions::setSauvolaCoef(double sauvolaCoef) { - BlackWhiteOptions::sauvolaCoef = sauvolaCoef; + BlackWhiteOptions::m_sauvolaCoef = sauvolaCoef; } int BlackWhiteOptions::getWolfLowerBound() const { - return wolfLowerBound; + return m_wolfLowerBound; } void BlackWhiteOptions::setWolfLowerBound(int wolfLowerBound) { - BlackWhiteOptions::wolfLowerBound = wolfLowerBound; + BlackWhiteOptions::m_wolfLowerBound = wolfLowerBound; } int BlackWhiteOptions::getWolfUpperBound() const { - return wolfUpperBound; + return m_wolfUpperBound; } void BlackWhiteOptions::setWolfUpperBound(int wolfUpperBound) { - BlackWhiteOptions::wolfUpperBound = wolfUpperBound; + BlackWhiteOptions::m_wolfUpperBound = wolfUpperBound; } double BlackWhiteOptions::getWolfCoef() const { - return wolfCoef; + return m_wolfCoef; } void BlackWhiteOptions::setWolfCoef(double wolfCoef) { - BlackWhiteOptions::wolfCoef = wolfCoef; + BlackWhiteOptions::m_wolfCoef = wolfCoef; } BinarizationMethod BlackWhiteOptions::getBinarizationMethod() const { - return binarizationMethod; + return m_binarizationMethod; } void BlackWhiteOptions::setBinarizationMethod(BinarizationMethod binarizationMethod) { - BlackWhiteOptions::binarizationMethod = binarizationMethod; + BlackWhiteOptions::m_binarizationMethod = binarizationMethod; } BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str) { - if (str == "wolf") { - return WOLF; - } else if (str == "sauvola") { - return SAUVOLA; - } else { - return OTSU; - } + if (str == "wolf") { + return WOLF; + } else if (str == "sauvola") { + return SAUVOLA; + } else { + return OTSU; + } } QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) { - QString str = ""; - switch (type) { - case OTSU: - str = "otsu"; - break; - case SAUVOLA: - str = "sauvola"; - break; - case WOLF: - str = "wolf"; - break; - } - - return str; + QString str = ""; + switch (type) { + case OTSU: + str = "otsu"; + break; + case SAUVOLA: + str = "sauvola"; + break; + case WOLF: + str = "wolf"; + break; + } + + return str; } int BlackWhiteOptions::thresholdAdjustment() const { - return m_thresholdAdjustment; + return m_thresholdAdjustment; } void BlackWhiteOptions::setThresholdAdjustment(int val) { - m_thresholdAdjustment = val; + m_thresholdAdjustment = val; } bool BlackWhiteOptions::normalizeIllumination() const { - return m_normalizeIllumination; + return m_normalizeIllumination; } void BlackWhiteOptions::setNormalizeIllumination(bool val) { - m_normalizeIllumination = val; + m_normalizeIllumination = val; } const BlackWhiteOptions::ColorSegmenterOptions& BlackWhiteOptions::getColorSegmenterOptions() const { - return colorSegmenterOptions; + return m_colorSegmenterOptions; } void BlackWhiteOptions::setColorSegmenterOptions( - const BlackWhiteOptions::ColorSegmenterOptions& colorSegmenterOptions) { - BlackWhiteOptions::colorSegmenterOptions = colorSegmenterOptions; + const BlackWhiteOptions::ColorSegmenterOptions& colorSegmenterOptions) { + BlackWhiteOptions::m_colorSegmenterOptions = colorSegmenterOptions; } /*=============================== BlackWhiteOptions::ColorSegmenterOptions ==================================*/ BlackWhiteOptions::ColorSegmenterOptions::ColorSegmenterOptions() - : enabled(false), - noiseReduction(7), - redThresholdAdjustment(0), - greenThresholdAdjustment(0), - blueThresholdAdjustment(0) { -} + : m_isEnabled(false), + m_noiseReduction(7), + m_redThresholdAdjustment(0), + m_greenThresholdAdjustment(0), + m_blueThresholdAdjustment(0) {} BlackWhiteOptions::ColorSegmenterOptions::ColorSegmenterOptions(const QDomElement& el) - : enabled(el.attribute("enabled") == "1"), - noiseReduction(el.attribute("noiseReduction").toInt()), - redThresholdAdjustment(el.attribute("redThresholdAdjustment").toInt()), - greenThresholdAdjustment(el.attribute("greenThresholdAdjustment").toInt()), - blueThresholdAdjustment(el.attribute("blueThresholdAdjustment").toInt()) { -} + : m_isEnabled(el.attribute("enabled") == "1"), + m_noiseReduction(el.attribute("noiseReduction").toInt()), + m_redThresholdAdjustment(el.attribute("redThresholdAdjustment").toInt()), + m_greenThresholdAdjustment(el.attribute("greenThresholdAdjustment").toInt()), + m_blueThresholdAdjustment(el.attribute("blueThresholdAdjustment").toInt()) {} QDomElement BlackWhiteOptions::ColorSegmenterOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("enabled", enabled ? "1" : "0"); - el.setAttribute("noiseReduction", noiseReduction); - el.setAttribute("redThresholdAdjustment", redThresholdAdjustment); - el.setAttribute("greenThresholdAdjustment", greenThresholdAdjustment); - el.setAttribute("blueThresholdAdjustment", blueThresholdAdjustment); + QDomElement el(doc.createElement(name)); + el.setAttribute("enabled", m_isEnabled ? "1" : "0"); + el.setAttribute("noiseReduction", m_noiseReduction); + el.setAttribute("redThresholdAdjustment", m_redThresholdAdjustment); + el.setAttribute("greenThresholdAdjustment", m_greenThresholdAdjustment); + el.setAttribute("blueThresholdAdjustment", m_blueThresholdAdjustment); - return el; + return el; } bool BlackWhiteOptions::ColorSegmenterOptions::operator==(const BlackWhiteOptions::ColorSegmenterOptions& other) const { - return (enabled == other.enabled) && (noiseReduction == other.noiseReduction) - && (redThresholdAdjustment == other.redThresholdAdjustment) - && (greenThresholdAdjustment == other.greenThresholdAdjustment) - && (blueThresholdAdjustment == other.blueThresholdAdjustment); + return (m_isEnabled == other.m_isEnabled) && (m_noiseReduction == other.m_noiseReduction) + && (m_redThresholdAdjustment == other.m_redThresholdAdjustment) + && (m_greenThresholdAdjustment == other.m_greenThresholdAdjustment) + && (m_blueThresholdAdjustment == other.m_blueThresholdAdjustment); } bool BlackWhiteOptions::ColorSegmenterOptions::operator!=(const BlackWhiteOptions::ColorSegmenterOptions& other) const { - return !(*this == other); + return !(*this == other); } bool BlackWhiteOptions::ColorSegmenterOptions::isEnabled() const { - return enabled; + return m_isEnabled; } void BlackWhiteOptions::ColorSegmenterOptions::setEnabled(bool enabled) { - ColorSegmenterOptions::enabled = enabled; + ColorSegmenterOptions::m_isEnabled = enabled; } int BlackWhiteOptions::ColorSegmenterOptions::getNoiseReduction() const { - return noiseReduction; + return m_noiseReduction; } void BlackWhiteOptions::ColorSegmenterOptions::setNoiseReduction(int noiseReduction) { - ColorSegmenterOptions::noiseReduction = noiseReduction; + ColorSegmenterOptions::m_noiseReduction = noiseReduction; } int BlackWhiteOptions::ColorSegmenterOptions::getRedThresholdAdjustment() const { - return redThresholdAdjustment; + return m_redThresholdAdjustment; } void BlackWhiteOptions::ColorSegmenterOptions::setRedThresholdAdjustment(int redThresholdAdjustment) { - ColorSegmenterOptions::redThresholdAdjustment = redThresholdAdjustment; + ColorSegmenterOptions::m_redThresholdAdjustment = redThresholdAdjustment; } int BlackWhiteOptions::ColorSegmenterOptions::getGreenThresholdAdjustment() const { - return greenThresholdAdjustment; + return m_greenThresholdAdjustment; } void BlackWhiteOptions::ColorSegmenterOptions::setGreenThresholdAdjustment(int greenThresholdAdjustment) { - ColorSegmenterOptions::greenThresholdAdjustment = greenThresholdAdjustment; + ColorSegmenterOptions::m_greenThresholdAdjustment = greenThresholdAdjustment; } int BlackWhiteOptions::ColorSegmenterOptions::getBlueThresholdAdjustment() const { - return blueThresholdAdjustment; + return m_blueThresholdAdjustment; } void BlackWhiteOptions::ColorSegmenterOptions::setBlueThresholdAdjustment(int blueThresholdAdjustment) { - ColorSegmenterOptions::blueThresholdAdjustment = blueThresholdAdjustment; + ColorSegmenterOptions::m_blueThresholdAdjustment = blueThresholdAdjustment; } } // namespace output \ No newline at end of file diff --git a/filters/output/BlackWhiteOptions.h b/filters/output/BlackWhiteOptions.h index cbe09cc32..4cab6834d 100644 --- a/filters/output/BlackWhiteOptions.h +++ b/filters/output/BlackWhiteOptions.h @@ -27,119 +27,119 @@ namespace output { enum BinarizationMethod { OTSU, SAUVOLA, WOLF }; class BlackWhiteOptions { -public: - class ColorSegmenterOptions { - public: - ColorSegmenterOptions(); + public: + class ColorSegmenterOptions { + public: + ColorSegmenterOptions(); - explicit ColorSegmenterOptions(const QDomElement& el); + explicit ColorSegmenterOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const ColorSegmenterOptions& other) const; + bool operator==(const ColorSegmenterOptions& other) const; - bool operator!=(const ColorSegmenterOptions& other) const; + bool operator!=(const ColorSegmenterOptions& other) const; - bool isEnabled() const; + bool isEnabled() const; - void setEnabled(bool enabled); + void setEnabled(bool enabled); - int getNoiseReduction() const; + int getNoiseReduction() const; - void setNoiseReduction(int noiseReduction); + void setNoiseReduction(int noiseReduction); - int getRedThresholdAdjustment() const; + int getRedThresholdAdjustment() const; - void setRedThresholdAdjustment(int redThresholdAdjustment); + void setRedThresholdAdjustment(int redThresholdAdjustment); - int getGreenThresholdAdjustment() const; + int getGreenThresholdAdjustment() const; - void setGreenThresholdAdjustment(int greenThresholdAdjustment); + void setGreenThresholdAdjustment(int greenThresholdAdjustment); - int getBlueThresholdAdjustment() const; + int getBlueThresholdAdjustment() const; - void setBlueThresholdAdjustment(int blueThresholdAdjustment); + void setBlueThresholdAdjustment(int blueThresholdAdjustment); - private: - bool enabled; - int noiseReduction; - int redThresholdAdjustment; - int greenThresholdAdjustment; - int blueThresholdAdjustment; - }; + private: + bool m_isEnabled; + int m_noiseReduction; + int m_redThresholdAdjustment; + int m_greenThresholdAdjustment; + int m_blueThresholdAdjustment; + }; -public: - BlackWhiteOptions(); + public: + BlackWhiteOptions(); - explicit BlackWhiteOptions(const QDomElement& el); + explicit BlackWhiteOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const BlackWhiteOptions& other) const; + bool operator==(const BlackWhiteOptions& other) const; - bool operator!=(const BlackWhiteOptions& other) const; + bool operator!=(const BlackWhiteOptions& other) const; - int thresholdAdjustment() const; + int thresholdAdjustment() const; - void setThresholdAdjustment(int val); + void setThresholdAdjustment(int val); - bool normalizeIllumination() const; + bool normalizeIllumination() const; - void setNormalizeIllumination(bool val); + void setNormalizeIllumination(bool val); - bool isSavitzkyGolaySmoothingEnabled() const; + bool isSavitzkyGolaySmoothingEnabled() const; - void setSavitzkyGolaySmoothingEnabled(bool savitzkyGolaySmoothingEnabled); + void setSavitzkyGolaySmoothingEnabled(bool savitzkyGolaySmoothingEnabled); - bool isMorphologicalSmoothingEnabled() const; + bool isMorphologicalSmoothingEnabled() const; - void setMorphologicalSmoothingEnabled(bool morphologicalSmoothingEnabled); + void setMorphologicalSmoothingEnabled(bool morphologicalSmoothingEnabled); - int getWindowSize() const; + int getWindowSize() const; - void setWindowSize(int windowSize); + void setWindowSize(int windowSize); - double getSauvolaCoef() const; + double getSauvolaCoef() const; - void setSauvolaCoef(double sauvolaCoef); + void setSauvolaCoef(double sauvolaCoef); - int getWolfLowerBound() const; + int getWolfLowerBound() const; - void setWolfLowerBound(int wolfLowerBound); + void setWolfLowerBound(int wolfLowerBound); - int getWolfUpperBound() const; + int getWolfUpperBound() const; - void setWolfUpperBound(int wolfUpperBound); + void setWolfUpperBound(int wolfUpperBound); - double getWolfCoef() const; + double getWolfCoef() const; - void setWolfCoef(double wolfCoef); + void setWolfCoef(double wolfCoef); - BinarizationMethod getBinarizationMethod() const; + BinarizationMethod getBinarizationMethod() const; - void setBinarizationMethod(BinarizationMethod binarizationMethod); + void setBinarizationMethod(BinarizationMethod binarizationMethod); - const ColorSegmenterOptions& getColorSegmenterOptions() const; + const ColorSegmenterOptions& getColorSegmenterOptions() const; - void setColorSegmenterOptions(const ColorSegmenterOptions& colorSegmenterOptions); + void setColorSegmenterOptions(const ColorSegmenterOptions& colorSegmenterOptions); -private: - static BinarizationMethod parseBinarizationMethod(const QString& str); + private: + static BinarizationMethod parseBinarizationMethod(const QString& str); - static QString formatBinarizationMethod(BinarizationMethod type); + static QString formatBinarizationMethod(BinarizationMethod type); - int m_thresholdAdjustment; - bool savitzkyGolaySmoothingEnabled; - bool morphologicalSmoothingEnabled; - bool m_normalizeIllumination; - int windowSize; - double sauvolaCoef; - int wolfLowerBound; - int wolfUpperBound; - double wolfCoef; - BinarizationMethod binarizationMethod; - ColorSegmenterOptions colorSegmenterOptions; + int m_thresholdAdjustment; + bool m_savitzkyGolaySmoothingEnabled; + bool m_morphologicalSmoothingEnabled; + bool m_normalizeIllumination; + int m_windowSize; + double m_sauvolaCoef; + int m_wolfLowerBound; + int m_wolfUpperBound; + double m_wolfCoef; + BinarizationMethod m_binarizationMethod; + ColorSegmenterOptions m_colorSegmenterOptions; }; } // namespace output #endif // ifndef OUTPUT_BLACK_WHITE_OPTIONS_H_ diff --git a/filters/output/CMakeLists.txt b/filters/output/CMakeLists.txt index d6fd39ae0..169f88970 100644 --- a/filters/output/CMakeLists.txt +++ b/filters/output/CMakeLists.txt @@ -1,70 +1,70 @@ -PROJECT("Output Filter") +project("Output Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - ApplyColorsDialog.cpp ApplyColorsDialog.h - ChangeDpiDialog.cpp ChangeDpiDialog.h - ImageView.cpp ImageView.h - ImageViewTab.h - TabbedImageView.cpp TabbedImageView.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h - OutputGenerator.cpp OutputGenerator.h - OutputMargins.h - Settings.cpp Settings.h - Thumbnail.cpp Thumbnail.h - Utils.cpp Utils.h - Params.cpp Params.h - BlackWhiteOptions.cpp BlackWhiteOptions.h - ColorCommonOptions.cpp ColorCommonOptions.h - RenderParams.cpp RenderParams.h - ColorParams.cpp ColorParams.h - OutputImageParams.cpp OutputImageParams.h - OutputFileParams.cpp OutputFileParams.h - OutputParams.cpp OutputParams.h - PictureLayerProperty.cpp PictureLayerProperty.h - ZoneCategoryProperty.cpp ZoneCategoryProperty.h - PictureZonePropFactory.cpp PictureZonePropFactory.h - PictureZonePropDialog.cpp PictureZonePropDialog.h - PictureZoneComparator.cpp PictureZoneComparator.h - PictureZoneEditor.cpp PictureZoneEditor.h - FillColorProperty.cpp FillColorProperty.h - FillZonePropFactory.cpp FillZonePropFactory.h - FillZoneComparator.cpp FillZoneComparator.h - FillZoneEditor.cpp FillZoneEditor.h - ColorPickupInteraction.cpp ColorPickupInteraction.h - DespeckleState.cpp DespeckleState.h - DespeckleView.cpp DespeckleView.h - DespeckleVisualization.cpp DespeckleVisualization.h - DespeckleLevel.cpp DespeckleLevel.h - DewarpingView.cpp DewarpingView.h - DewarpingOptions.cpp DewarpingOptions.h - ChangeDewarpingDialog.cpp ChangeDewarpingDialog.h - DepthPerception.cpp DepthPerception.h - SplitImage.cpp SplitImage.h - SplittingOptions.cpp SplittingOptions.h - OtsuBinarizationOptionsWidget.cpp OtsuBinarizationOptionsWidget.h - SauvolaBinarizationOptionsWidget.cpp SauvolaBinarizationOptionsWidget.h - WolfBinarizationOptionsWidget.cpp WolfBinarizationOptionsWidget.h - BinarizationOptionsWidget.h - PictureShapeOptions.cpp PictureShapeOptions.h - OutputProcessingParams.cpp OutputProcessingParams.h) +set( + sources + ApplyColorsDialog.cpp ApplyColorsDialog.h + ChangeDpiDialog.cpp ChangeDpiDialog.h + ImageView.cpp ImageView.h + ImageViewTab.h + TabbedImageView.cpp TabbedImageView.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h + OutputGenerator.cpp OutputGenerator.h + OutputMargins.h + Settings.cpp Settings.h + Thumbnail.cpp Thumbnail.h + Utils.cpp Utils.h + Params.cpp Params.h + BlackWhiteOptions.cpp BlackWhiteOptions.h + ColorCommonOptions.cpp ColorCommonOptions.h + RenderParams.cpp RenderParams.h + ColorParams.cpp ColorParams.h + OutputImageParams.cpp OutputImageParams.h + OutputFileParams.cpp OutputFileParams.h + OutputParams.cpp OutputParams.h + PictureLayerProperty.cpp PictureLayerProperty.h + ZoneCategoryProperty.cpp ZoneCategoryProperty.h + PictureZonePropFactory.cpp PictureZonePropFactory.h + PictureZonePropDialog.cpp PictureZonePropDialog.h + PictureZoneComparator.cpp PictureZoneComparator.h + PictureZoneEditor.cpp PictureZoneEditor.h + FillColorProperty.cpp FillColorProperty.h + FillZonePropFactory.cpp FillZonePropFactory.h + FillZoneComparator.cpp FillZoneComparator.h + FillZoneEditor.cpp FillZoneEditor.h + ColorPickupInteraction.cpp ColorPickupInteraction.h + DespeckleState.cpp DespeckleState.h + DespeckleView.cpp DespeckleView.h + DespeckleVisualization.cpp DespeckleVisualization.h + DespeckleLevel.cpp DespeckleLevel.h + DewarpingView.cpp DewarpingView.h + DewarpingOptions.cpp DewarpingOptions.h + ChangeDewarpingDialog.cpp ChangeDewarpingDialog.h + DepthPerception.cpp DepthPerception.h + SplitImage.cpp SplitImage.h + SplittingOptions.cpp SplittingOptions.h + OtsuBinarizationOptionsWidget.cpp OtsuBinarizationOptionsWidget.h + SauvolaBinarizationOptionsWidget.cpp SauvolaBinarizationOptionsWidget.h + WolfBinarizationOptionsWidget.cpp WolfBinarizationOptionsWidget.h + BinarizationOptionsWidget.h + PictureShapeOptions.cpp PictureShapeOptions.h + OutputProcessingParams.cpp OutputProcessingParams.h) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(output STATIC ${sources} ${ui_sources}) -ADD_DEPENDENCIES(output toplevel_ui_sources) +add_library(output STATIC ${sources} ${ui_sources}) +add_dependencies(output toplevel_ui_sources) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) \ No newline at end of file +translation_sources(scantailor ${sources} ${ui_files}) \ No newline at end of file diff --git a/filters/output/CacheDrivenTask.cpp b/filters/output/CacheDrivenTask.cpp index 1a4575595..4af492b08 100644 --- a/filters/output/CacheDrivenTask.cpp +++ b/filters/output/CacheDrivenTask.cpp @@ -17,24 +17,23 @@ */ #include "CacheDrivenTask.h" -#include "OutputGenerator.h" -#include "PictureZoneComparator.h" +#include +#include +#include #include "FillZoneComparator.h" -#include "Thumbnail.h" #include "IncompleteThumbnail.h" +#include "OutputGenerator.h" #include "PageInfo.h" +#include "PictureZoneComparator.h" +#include "RenderParams.h" +#include "Thumbnail.h" #include "Utils.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" -#include "RenderParams.h" -#include -#include -#include namespace output { CacheDrivenTask::CacheDrivenTask(intrusive_ptr settings, const OutputFileNameGenerator& out_file_name_gen) - : m_ptrSettings(std::move(settings)), m_outFileNameGen(out_file_name_gen) { -} + : m_settings(std::move(settings)), m_outFileNameGen(out_file_name_gen) {} CacheDrivenTask::~CacheDrivenTask() = default; @@ -42,110 +41,109 @@ void CacheDrivenTask::process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform, const QPolygonF& content_rect_phys) { - if (auto* thumb_col = dynamic_cast(collector)) { - const QString out_file_path(m_outFileNameGen.filePathFor(page_info.id())); - const QFileInfo out_file_info(out_file_path); - const QString foreground_dir(Utils::foregroundDir(m_outFileNameGen.outDir())); - const QString background_dir(Utils::backgroundDir(m_outFileNameGen.outDir())); - const QString original_background_dir(Utils::originalBackgroundDir(m_outFileNameGen.outDir())); - const QString foreground_file_path(QDir(foreground_dir).absoluteFilePath(out_file_info.fileName())); - const QString background_file_path(QDir(background_dir).absoluteFilePath(out_file_info.fileName())); - const QString original_background_file_path( - QDir(original_background_dir).absoluteFilePath(out_file_info.fileName())); - const QFileInfo foreground_file_info(foreground_file_path); - const QFileInfo background_file_info(background_file_path); - const QFileInfo original_background_file_info(original_background_file_path); - - const Params params(m_ptrSettings->getParams(page_info.id())); - RenderParams render_params(params.colorParams(), params.splittingOptions()); - - ImageTransformation new_xform(xform); - new_xform.postScaleToDpi(params.outputDpi()); - - bool need_reprocess = false; - - do { // Just to be able to break from it. - std::unique_ptr stored_output_params(m_ptrSettings->getOutputParams(page_info.id())); - - if (!stored_output_params) { - need_reprocess = true; - break; - } - - const OutputGenerator generator(params.outputDpi(), params.colorParams(), params.splittingOptions(), - params.pictureShapeOptions(), params.dewarpingOptions(), - m_ptrSettings->getOutputProcessingParams(page_info.id()), - params.despeckleLevel(), new_xform, content_rect_phys); - const OutputImageParams new_output_image_params( - generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), - params.colorParams(), params.splittingOptions(), params.dewarpingOptions(), - params.distortionModel(), params.depthPerception(), params.despeckleLevel(), - params.pictureShapeOptions(), m_ptrSettings->getOutputProcessingParams(page_info.id())); - - if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { - need_reprocess = true; - break; - } - - const ZoneSet new_picture_zones(m_ptrSettings->pictureZonesForPage(page_info.id())); - if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { - need_reprocess = true; - break; - } - - const ZoneSet new_fill_zones(m_ptrSettings->fillZonesForPage(page_info.id())); - if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { - need_reprocess = true; - break; - } - - const QFileInfo out_file_info(out_file_path); - - if (!render_params.splitOutput()) { - if (!out_file_info.exists()) { - need_reprocess = true; - break; - } - - if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { - need_reprocess = true; - break; - } - } else { - if (!foreground_file_info.exists() || !background_file_info.exists()) { - need_reprocess = true; - break; - } - if (!(stored_output_params->foregroundFileParams().matches(OutputFileParams(foreground_file_info))) - || !(stored_output_params->backgroundFileParams().matches( - OutputFileParams(background_file_info)))) { - need_reprocess = true; - break; - } - - if (render_params.originalBackground()) { - if (!original_background_file_info.exists()) { - need_reprocess = true; - break; - } - if (!(stored_output_params->originalBackgroundFileParams().matches( - OutputFileParams(original_background_file_info)))) { - need_reprocess = true; - break; - } - } - } - } while (false); - - if (need_reprocess) { - thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform))); - } else { - const ImageTransformation out_xform(new_xform.resultingRect(), params.outputDpi()); - - thumb_col->processThumbnail(std::unique_ptr(new Thumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), ImageId(out_file_path), out_xform))); + if (auto* thumb_col = dynamic_cast(collector)) { + const QString out_file_path(m_outFileNameGen.filePathFor(page_info.id())); + const QFileInfo out_file_info(out_file_path); + const QString foreground_dir(Utils::foregroundDir(m_outFileNameGen.outDir())); + const QString background_dir(Utils::backgroundDir(m_outFileNameGen.outDir())); + const QString original_background_dir(Utils::originalBackgroundDir(m_outFileNameGen.outDir())); + const QString foreground_file_path(QDir(foreground_dir).absoluteFilePath(out_file_info.fileName())); + const QString background_file_path(QDir(background_dir).absoluteFilePath(out_file_info.fileName())); + const QString original_background_file_path( + QDir(original_background_dir).absoluteFilePath(out_file_info.fileName())); + const QFileInfo foreground_file_info(foreground_file_path); + const QFileInfo background_file_info(background_file_path); + const QFileInfo original_background_file_info(original_background_file_path); + + const Params params(m_settings->getParams(page_info.id())); + RenderParams render_params(params.colorParams(), params.splittingOptions()); + + ImageTransformation new_xform(xform); + new_xform.postScaleToDpi(params.outputDpi()); + + bool need_reprocess = false; + + do { // Just to be able to break from it. + std::unique_ptr stored_output_params(m_settings->getOutputParams(page_info.id())); + + if (!stored_output_params) { + need_reprocess = true; + break; + } + + const OutputGenerator generator(params.outputDpi(), params.colorParams(), params.splittingOptions(), + params.pictureShapeOptions(), params.dewarpingOptions(), + m_settings->getOutputProcessingParams(page_info.id()), params.despeckleLevel(), + new_xform, content_rect_phys); + const OutputImageParams new_output_image_params( + generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), + params.colorParams(), params.splittingOptions(), params.dewarpingOptions(), params.distortionModel(), + params.depthPerception(), params.despeckleLevel(), params.pictureShapeOptions(), + m_settings->getOutputProcessingParams(page_info.id()), params.isBlackOnWhite()); + + if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { + need_reprocess = true; + break; + } + + const ZoneSet new_picture_zones(m_settings->pictureZonesForPage(page_info.id())); + if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { + need_reprocess = true; + break; + } + + const ZoneSet new_fill_zones(m_settings->fillZonesForPage(page_info.id())); + if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { + need_reprocess = true; + break; + } + + const QFileInfo out_file_info(out_file_path); + + if (!render_params.splitOutput()) { + if (!out_file_info.exists()) { + need_reprocess = true; + break; + } + + if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { + need_reprocess = true; + break; + } + } else { + if (!foreground_file_info.exists() || !background_file_info.exists()) { + need_reprocess = true; + break; } + if (!(stored_output_params->foregroundFileParams().matches(OutputFileParams(foreground_file_info))) + || !(stored_output_params->backgroundFileParams().matches(OutputFileParams(background_file_info)))) { + need_reprocess = true; + break; + } + + if (render_params.originalBackground()) { + if (!original_background_file_info.exists()) { + need_reprocess = true; + break; + } + if (!(stored_output_params->originalBackgroundFileParams().matches( + OutputFileParams(original_background_file_info)))) { + need_reprocess = true; + break; + } + } + } + } while (false); + + if (need_reprocess) { + thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform))); + } else { + const ImageTransformation out_xform(new_xform.resultingRect(), params.outputDpi()); + + thumb_col->processThumbnail(std::unique_ptr(new Thumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), ImageId(out_file_path), out_xform))); } + } } // CacheDrivenTask::process } // namespace output \ No newline at end of file diff --git a/filters/output/CacheDrivenTask.h b/filters/output/CacheDrivenTask.h index e5dbb6734..e3eb4aeb9 100644 --- a/filters/output/CacheDrivenTask.h +++ b/filters/output/CacheDrivenTask.h @@ -20,9 +20,9 @@ #define OUTPUT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" -#include "ref_countable.h" -#include "intrusive_ptr.h" #include "OutputFileNameGenerator.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class QPolygonF; class PageInfo; @@ -33,21 +33,21 @@ namespace output { class Settings; class CacheDrivenTask : public ref_countable { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr settings, const OutputFileNameGenerator& out_file_name_gen); + public: + CacheDrivenTask(intrusive_ptr settings, const OutputFileNameGenerator& out_file_name_gen); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, - AbstractFilterDataCollector* collector, - const ImageTransformation& xform, - const QPolygonF& content_rect_phys); + void process(const PageInfo& page_info, + AbstractFilterDataCollector* collector, + const ImageTransformation& xform, + const QPolygonF& content_rect_phys); -private: - intrusive_ptr m_ptrSettings; - OutputFileNameGenerator m_outFileNameGen; + private: + intrusive_ptr m_settings; + OutputFileNameGenerator m_outFileNameGen; }; } // namespace output #endif // ifndef OUTPUT_CACHEDRIVENTASK_H_ diff --git a/filters/output/ChangeDewarpingDialog.cpp b/filters/output/ChangeDewarpingDialog.cpp index 1a25e9ef1..b58288166 100644 --- a/filters/output/ChangeDewarpingDialog.cpp +++ b/filters/output/ChangeDewarpingDialog.cpp @@ -17,83 +17,83 @@ */ #include "ChangeDewarpingDialog.h" +#include #include "PageSelectionAccessor.h" #include "QtSignalForwarder.h" -#include namespace output { ChangeDewarpingDialog::ChangeDewarpingDialog(QWidget* parent, const PageId& cur_page, const DewarpingOptions& dewarpingOptions, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_curPage(cur_page), - m_dewarpingMode(dewarpingOptions.dewarpingMode()), - m_dewarpingOptions(dewarpingOptions), - m_pScopeGroup(new QButtonGroup(this)) { - using namespace boost::lambda; - - ui.setupUi(this); - m_pScopeGroup->addButton(ui.thisPageRB); - m_pScopeGroup->addButton(ui.allPagesRB); - m_pScopeGroup->addButton(ui.thisPageAndFollowersRB); - m_pScopeGroup->addButton(ui.selectedPagesRB); - if (m_selectedPages.size() <= 1) { - ui.selectedPagesWidget->setEnabled(false); - } - - switch (dewarpingOptions.dewarpingMode()) { - case OFF: - ui.offRB->setChecked(true); - break; - case AUTO: - ui.autoRB->setChecked(true); - break; - case MARGINAL: - ui.marginalRB->setChecked(true); - break; - case MANUAL: - ui.manualRB->setChecked(true); - break; - } - - ui.dewarpingPostDeskewCB->setChecked(dewarpingOptions.needPostDeskew()); - // No, we don't leak memory here. - new QtSignalForwarder(ui.offRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = OFF); - new QtSignalForwarder(ui.autoRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = AUTO); - new QtSignalForwarder(ui.manualRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = MANUAL); - new QtSignalForwarder(ui.marginalRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = MARGINAL); - - connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_curPage(cur_page), + m_dewarpingMode(dewarpingOptions.dewarpingMode()), + m_dewarpingOptions(dewarpingOptions), + m_scopeGroup(new QButtonGroup(this)) { + using namespace boost::lambda; + + ui.setupUi(this); + m_scopeGroup->addButton(ui.thisPageRB); + m_scopeGroup->addButton(ui.allPagesRB); + m_scopeGroup->addButton(ui.thisPageAndFollowersRB); + m_scopeGroup->addButton(ui.selectedPagesRB); + if (m_selectedPages.size() <= 1) { + ui.selectedPagesWidget->setEnabled(false); + } + + switch (dewarpingOptions.dewarpingMode()) { + case OFF: + ui.offRB->setChecked(true); + break; + case AUTO: + ui.autoRB->setChecked(true); + break; + case MARGINAL: + ui.marginalRB->setChecked(true); + break; + case MANUAL: + ui.manualRB->setChecked(true); + break; + } + + ui.dewarpingPostDeskewCB->setChecked(dewarpingOptions.needPostDeskew()); + // No, we don't leak memory here. + new QtSignalForwarder(ui.offRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = OFF); + new QtSignalForwarder(ui.autoRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = AUTO); + new QtSignalForwarder(ui.manualRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = MANUAL); + new QtSignalForwarder(ui.marginalRB, SIGNAL(clicked(bool)), var(m_dewarpingMode) = MARGINAL); + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ChangeDewarpingDialog::~ChangeDewarpingDialog() = default; void ChangeDewarpingDialog::onSubmit() { - std::set pages; - - m_dewarpingOptions.setDewarpingMode(m_dewarpingMode); - m_dewarpingOptions.setPostDeskew(ui.dewarpingPostDeskewCB->isChecked()); - - if (ui.thisPageRB->isChecked()) { - pages.insert(m_curPage); - } else if (ui.allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (ui.thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (ui.selectedPagesRB->isChecked()) { - emit accepted(m_selectedPages, m_dewarpingOptions); - accept(); + std::set pages; + + m_dewarpingOptions.setDewarpingMode(m_dewarpingMode); + m_dewarpingOptions.setPostDeskew(ui.dewarpingPostDeskewCB->isChecked()); + + if (ui.thisPageRB->isChecked()) { + pages.insert(m_curPage); + } else if (ui.allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (ui.thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (ui.selectedPagesRB->isChecked()) { + emit accepted(m_selectedPages, m_dewarpingOptions); + accept(); - return; - } + return; + } - emit accepted(pages, m_dewarpingOptions); + emit accepted(pages, m_dewarpingOptions); - // We assume the default connection from accepted() to accept() - // was removed. - accept(); + // We assume the default connection from accepted() to accept() + // was removed. + accept(); } } // namespace output \ No newline at end of file diff --git a/filters/output/ChangeDewarpingDialog.h b/filters/output/ChangeDewarpingDialog.h index de4f2c31b..abe28ebe8 100644 --- a/filters/output/ChangeDewarpingDialog.h +++ b/filters/output/ChangeDewarpingDialog.h @@ -19,45 +19,45 @@ #ifndef OUTPUT_CHANGE_DEWARPING_DIALOG_H_ #define OUTPUT_CHANGE_DEWARPING_DIALOG_H_ -#include "ui_OutputChangeDewarpingDialog.h" +#include +#include +#include +#include #include "DewarpingOptions.h" #include "PageId.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include -#include +#include "ui_OutputChangeDewarpingDialog.h" class PageSelectionAccessor; -class QButtonGroup; namespace output { class ChangeDewarpingDialog : public QDialog { - Q_OBJECT -public: - ChangeDewarpingDialog(QWidget* parent, - const PageId& cur_page, - const DewarpingOptions& dewarpingOptions, - const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ChangeDewarpingDialog(QWidget* parent, + const PageId& cur_page, + const DewarpingOptions& dewarpingOptions, + const PageSelectionAccessor& page_selection_accessor); - ~ChangeDewarpingDialog() override; + ~ChangeDewarpingDialog() override; -signals: + signals: - void accepted(const std::set& pages, const DewarpingOptions& dewarpingOptions); + void accepted(const std::set& pages, const DewarpingOptions& dewarpingOptions); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - Ui::OutputChangeDewarpingDialog ui; - PageSequence m_pages; - std::set m_selectedPages; - PageId m_curPage; - DewarpingMode m_dewarpingMode; - DewarpingOptions m_dewarpingOptions; - QButtonGroup* m_pScopeGroup; + private: + Ui::OutputChangeDewarpingDialog ui; + PageSequence m_pages; + std::set m_selectedPages; + PageId m_curPage; + DewarpingMode m_dewarpingMode; + DewarpingOptions m_dewarpingOptions; + QButtonGroup* m_scopeGroup; }; } // namespace output #endif // ifndef OUTPUT_CHANGE_DEWARPING_DIALOG_H_ diff --git a/filters/output/ChangeDpiDialog.cpp b/filters/output/ChangeDpiDialog.cpp index 92e26635e..69a49c126 100644 --- a/filters/output/ChangeDpiDialog.cpp +++ b/filters/output/ChangeDpiDialog.cpp @@ -17,123 +17,123 @@ */ #include "ChangeDpiDialog.h" -#include "PageSelectionAccessor.h" -#include #include +#include +#include "PageSelectionAccessor.h" namespace output { ChangeDpiDialog::ChangeDpiDialog(QWidget* parent, const Dpi& dpi, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_curPage(cur_page), - m_pScopeGroup(new QButtonGroup(this)) { - setupUi(this); - m_pScopeGroup->addButton(thisPageRB); - m_pScopeGroup->addButton(allPagesRB); - m_pScopeGroup->addButton(thisPageAndFollowersRB); - m_pScopeGroup->addButton(selectedPagesRB); - if (m_selectedPages.size() <= 1) { - selectedPagesWidget->setEnabled(false); - } - - dpiSelector->setValidator(new QIntValidator(dpiSelector)); - - static const int common_dpis[] = {300, 400, 600}; - - const int requested_dpi = std::max(dpi.horizontal(), dpi.vertical()); - m_customDpiString = QString::number(requested_dpi); - - int selected_index = -1; - for (const int cdpi : common_dpis) { - if (cdpi == requested_dpi) { - selected_index = dpiSelector->count(); - } - const QString cdpi_str(QString::number(cdpi)); - dpiSelector->addItem(cdpi_str, cdpi_str); - } - - m_customItemIdx = dpiSelector->count(); - dpiSelector->addItem(tr("Custom"), m_customDpiString); - - if (selected_index != -1) { - dpiSelector->setCurrentIndex(selected_index); - } else { - dpiSelector->setCurrentIndex(m_customItemIdx); - dpiSelector->setEditable(true); - dpiSelector->lineEdit()->setText(m_customDpiString); - // It looks like we need to set a new validator - // every time we make the combo box editable. - dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_curPage(cur_page), + m_scopeGroup(new QButtonGroup(this)) { + setupUi(this); + m_scopeGroup->addButton(thisPageRB); + m_scopeGroup->addButton(allPagesRB); + m_scopeGroup->addButton(thisPageAndFollowersRB); + m_scopeGroup->addButton(selectedPagesRB); + if (m_selectedPages.size() <= 1) { + selectedPagesWidget->setEnabled(false); + } + + dpiSelector->setValidator(new QIntValidator(dpiSelector)); + + static const int common_dpis[] = {300, 400, 600}; + + const int requested_dpi = std::max(dpi.horizontal(), dpi.vertical()); + m_customDpiString = QString::number(requested_dpi); + + int selected_index = -1; + for (const int cdpi : common_dpis) { + if (cdpi == requested_dpi) { + selected_index = dpiSelector->count(); } - - connect(dpiSelector, SIGNAL(activated(int)), this, SLOT(dpiSelectionChanged(int))); - connect(dpiSelector, SIGNAL(editTextChanged(const QString&)), this, SLOT(dpiEditTextChanged(const QString&))); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + const QString cdpi_str(QString::number(cdpi)); + dpiSelector->addItem(cdpi_str, cdpi_str); + } + + m_customItemIdx = dpiSelector->count(); + dpiSelector->addItem(tr("Custom"), m_customDpiString); + + if (selected_index != -1) { + dpiSelector->setCurrentIndex(selected_index); + } else { + dpiSelector->setCurrentIndex(m_customItemIdx); + dpiSelector->setEditable(true); + dpiSelector->lineEdit()->setText(m_customDpiString); + // It looks like we need to set a new validator + // every time we make the combo box editable. + dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); + } + + connect(dpiSelector, SIGNAL(activated(int)), this, SLOT(dpiSelectionChanged(int))); + connect(dpiSelector, SIGNAL(editTextChanged(const QString&)), this, SLOT(dpiEditTextChanged(const QString&))); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ChangeDpiDialog::~ChangeDpiDialog() = default; void ChangeDpiDialog::dpiSelectionChanged(const int index) { - dpiSelector->setEditable(index == m_customItemIdx); - if (index == m_customItemIdx) { - dpiSelector->setEditText(m_customDpiString); - dpiSelector->lineEdit()->selectAll(); - // It looks like we need to set a new validator - // every time we make the combo box editable. - dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); - } + dpiSelector->setEditable(index == m_customItemIdx); + if (index == m_customItemIdx) { + dpiSelector->setEditText(m_customDpiString); + dpiSelector->lineEdit()->selectAll(); + // It looks like we need to set a new validator + // every time we make the combo box editable. + dpiSelector->setValidator(new QIntValidator(0, 9999, dpiSelector)); + } } void ChangeDpiDialog::dpiEditTextChanged(const QString& text) { - if (dpiSelector->currentIndex() == m_customItemIdx) { - m_customDpiString = text; - } + if (dpiSelector->currentIndex() == m_customItemIdx) { + m_customDpiString = text; + } } void ChangeDpiDialog::onSubmit() { - const QString dpi_str(dpiSelector->currentText()); - if (dpi_str.isEmpty()) { - QMessageBox::warning(this, tr("Error"), tr("DPI is not set.")); + const QString dpi_str(dpiSelector->currentText()); + if (dpi_str.isEmpty()) { + QMessageBox::warning(this, tr("Error"), tr("DPI is not set.")); - return; - } + return; + } - const int dpi = dpi_str.toInt(); - if (dpi < 72) { - QMessageBox::warning(this, tr("Error"), tr("DPI is too low!")); + const int dpi = dpi_str.toInt(); + if (dpi < 72) { + QMessageBox::warning(this, tr("Error"), tr("DPI is too low!")); - return; - } + return; + } - if (dpi > 1200) { - QMessageBox::warning(this, tr("Error"), tr("DPI is too high!")); + if (dpi > 1200) { + QMessageBox::warning(this, tr("Error"), tr("DPI is too high!")); - return; - } + return; + } - std::set pages; + std::set pages; - if (thisPageRB->isChecked()) { - pages.insert(m_curPage); - } else if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - emit accepted(m_selectedPages, Dpi(dpi, dpi)); - accept(); + if (thisPageRB->isChecked()) { + pages.insert(m_curPage); + } else if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + emit accepted(m_selectedPages, Dpi(dpi, dpi)); + accept(); - return; - } + return; + } - emit accepted(pages, Dpi(dpi, dpi)); + emit accepted(pages, Dpi(dpi, dpi)); - // We assume the default connection from accepted() to accept() - // was removed. - accept(); + // We assume the default connection from accepted() to accept() + // was removed. + accept(); } // ChangeDpiDialog::onSubmit } // namespace output \ No newline at end of file diff --git a/filters/output/ChangeDpiDialog.h b/filters/output/ChangeDpiDialog.h index 7ce6f58ac..16322df13 100644 --- a/filters/output/ChangeDpiDialog.h +++ b/filters/output/ChangeDpiDialog.h @@ -19,48 +19,48 @@ #ifndef OUTPUT_CHANGEDPIDIALOG_H_ #define OUTPUT_CHANGEDPIDIALOG_H_ -#include "ui_OutputChangeDpiDialog.h" -#include "PageId.h" -#include "PageSequence.h" -#include "intrusive_ptr.h" +#include #include #include #include +#include "PageId.h" +#include "PageSequence.h" +#include "intrusive_ptr.h" +#include "ui_OutputChangeDpiDialog.h" class PageSelectionAccessor; -class QButtonGroup; class Dpi; namespace output { class ChangeDpiDialog : public QDialog, private Ui::OutputChangeDpiDialog { - Q_OBJECT -public: - ChangeDpiDialog(QWidget* parent, - const Dpi& dpi, - const PageId& cur_page, - const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ChangeDpiDialog(QWidget* parent, + const Dpi& dpi, + const PageId& cur_page, + const PageSelectionAccessor& page_selection_accessor); - ~ChangeDpiDialog() override; + ~ChangeDpiDialog() override; -signals: + signals: - void accepted(const std::set& pages, const Dpi& dpi); + void accepted(const std::set& pages, const Dpi& dpi); -private slots: + private slots: - void dpiSelectionChanged(int index); + void dpiSelectionChanged(int index); - void dpiEditTextChanged(const QString& text); + void dpiEditTextChanged(const QString& text); - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - std::set m_selectedPages; - PageId m_curPage; - QButtonGroup* m_pScopeGroup; - int m_customItemIdx; - QString m_customDpiString; + private: + PageSequence m_pages; + std::set m_selectedPages; + PageId m_curPage; + QButtonGroup* m_scopeGroup; + int m_customItemIdx; + QString m_customDpiString; }; } // namespace output #endif // ifndef OUTPUT_CHANGEDPIDIALOG_H_ diff --git a/filters/output/ColorCommonOptions.cpp b/filters/output/ColorCommonOptions.cpp index 2089e0563..eac549c15 100644 --- a/filters/output/ColorCommonOptions.cpp +++ b/filters/output/ColorCommonOptions.cpp @@ -21,151 +21,159 @@ namespace output { ColorCommonOptions::ColorCommonOptions() - : m_cutMargins(true), m_normalizeIllumination(false), m_fillingColor(FILL_BACKGROUND) { -} + : m_fillMargins(true), m_fillOffcut(true), m_normalizeIllumination(false), m_fillingColor(FILL_BACKGROUND) {} ColorCommonOptions::ColorCommonOptions(const QDomElement& el) - : m_cutMargins(el.attribute("cutMargins") == "1"), - m_normalizeIllumination(el.attribute("normalizeIlluminationColor") == "1"), - m_fillingColor(parseFillingColor(el.attribute("fillingColor"))), - posterizationOptions(el.namedItem("posterization-options").toElement()) { -} + : m_fillMargins(el.attribute("fillMargins") == "1"), + m_fillOffcut(el.attribute("fillOffcut") == "1"), + m_normalizeIllumination(el.attribute("normalizeIlluminationColor") == "1"), + m_fillingColor(parseFillingColor(el.attribute("fillingColor"))), + m_posterizationOptions(el.namedItem("posterization-options").toElement()) {} QDomElement ColorCommonOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("cutMargins", m_cutMargins ? "1" : "0"); - el.setAttribute("normalizeIlluminationColor", m_normalizeIllumination ? "1" : "0"); - el.setAttribute("fillingColor", formatFillingColor(m_fillingColor)); - el.appendChild(posterizationOptions.toXml(doc, "posterization-options")); + QDomElement el(doc.createElement(name)); + el.setAttribute("fillMargins", m_fillMargins ? "1" : "0"); + el.setAttribute("fillOffcut", m_fillOffcut ? "1" : "0"); + el.setAttribute("normalizeIlluminationColor", m_normalizeIllumination ? "1" : "0"); + el.setAttribute("fillingColor", formatFillingColor(m_fillingColor)); + el.appendChild(m_posterizationOptions.toXml(doc, "posterization-options")); - return el; + return el; } bool ColorCommonOptions::operator==(const ColorCommonOptions& other) const { - return (m_normalizeIllumination == other.m_normalizeIllumination) && (m_cutMargins == other.m_cutMargins) - && (m_fillingColor == other.m_fillingColor) && (posterizationOptions == other.posterizationOptions); + return (m_normalizeIllumination == other.m_normalizeIllumination) && (m_fillMargins == other.m_fillMargins) + && (m_fillOffcut == other.m_fillOffcut) && (m_fillingColor == other.m_fillingColor) + && (m_posterizationOptions == other.m_posterizationOptions); } bool ColorCommonOptions::operator!=(const ColorCommonOptions& other) const { - return !(*this == other); + return !(*this == other); } FillingColor ColorCommonOptions::getFillingColor() const { - return m_fillingColor; + return m_fillingColor; } void ColorCommonOptions::setFillingColor(FillingColor fillingColor) { - ColorCommonOptions::m_fillingColor = fillingColor; + ColorCommonOptions::m_fillingColor = fillingColor; } FillingColor ColorCommonOptions::parseFillingColor(const QString& str) { - if (str == "white") { - return FILL_WHITE; - } else { - return FILL_BACKGROUND; - } + if (str == "white") { + return FILL_WHITE; + } else { + return FILL_BACKGROUND; + } } QString ColorCommonOptions::formatFillingColor(FillingColor type) { - QString str = ""; - switch (type) { - case FILL_WHITE: - str = "white"; - break; - case FILL_BACKGROUND: - str = "background"; - break; - } + QString str = ""; + switch (type) { + case FILL_WHITE: + str = "white"; + break; + case FILL_BACKGROUND: + str = "background"; + break; + } - return str; + return str; } -void ColorCommonOptions::setCutMargins(bool val) { - m_cutMargins = val; +void ColorCommonOptions::setFillMargins(bool val) { + m_fillMargins = val; } -bool ColorCommonOptions::cutMargins() const { - return m_cutMargins; +bool ColorCommonOptions::fillMargins() const { + return m_fillMargins; } bool ColorCommonOptions::normalizeIllumination() const { - return m_normalizeIllumination; + return m_normalizeIllumination; } void ColorCommonOptions::setNormalizeIllumination(bool val) { - m_normalizeIllumination = val; + m_normalizeIllumination = val; } const ColorCommonOptions::PosterizationOptions& ColorCommonOptions::getPosterizationOptions() const { - return posterizationOptions; + return m_posterizationOptions; } void ColorCommonOptions::setPosterizationOptions(const ColorCommonOptions::PosterizationOptions& posterizationOptions) { - ColorCommonOptions::posterizationOptions = posterizationOptions; + ColorCommonOptions::m_posterizationOptions = posterizationOptions; +} + +bool ColorCommonOptions::fillOffcut() const { + return m_fillOffcut; +} + +void ColorCommonOptions::setFillOffcut(bool fillOffcut) { + m_fillOffcut = fillOffcut; } /*=============================== ColorCommonOptions::PosterizationOptions ==================================*/ ColorCommonOptions::PosterizationOptions::PosterizationOptions() - : enabled(false), level(4), normalizationEnabled(false), forceBlackAndWhite(true) { -} + : m_isEnabled(false), m_level(4), m_isNormalizationEnabled(false), m_forceBlackAndWhite(true) {} ColorCommonOptions::PosterizationOptions::PosterizationOptions(const QDomElement& el) - : enabled(el.attribute("enabled") == "1"), - level(el.attribute("level").toInt()), - normalizationEnabled(el.attribute("normalizationEnabled") == "1"), - forceBlackAndWhite(el.attribute("forceBlackAndWhite") == "1") { -} + : m_isEnabled(el.attribute("enabled") == "1"), + m_level(el.attribute("level").toInt()), + m_isNormalizationEnabled(el.attribute("normalizationEnabled") == "1"), + m_forceBlackAndWhite(el.attribute("forceBlackAndWhite") == "1") {} QDomElement ColorCommonOptions::PosterizationOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("enabled", enabled ? "1" : "0"); - el.setAttribute("level", level); - el.setAttribute("normalizationEnabled", normalizationEnabled ? "1" : "0"); - el.setAttribute("forceBlackAndWhite", forceBlackAndWhite ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.setAttribute("enabled", m_isEnabled ? "1" : "0"); + el.setAttribute("level", m_level); + el.setAttribute("normalizationEnabled", m_isNormalizationEnabled ? "1" : "0"); + el.setAttribute("forceBlackAndWhite", m_forceBlackAndWhite ? "1" : "0"); - return el; + return el; } bool ColorCommonOptions::PosterizationOptions::operator==(const ColorCommonOptions::PosterizationOptions& other) const { - return (enabled == other.enabled) && (level == other.level) && (normalizationEnabled == other.normalizationEnabled) - && (forceBlackAndWhite == other.forceBlackAndWhite); + return (m_isEnabled == other.m_isEnabled) && (m_level == other.m_level) + && (m_isNormalizationEnabled == other.m_isNormalizationEnabled) + && (m_forceBlackAndWhite == other.m_forceBlackAndWhite); } bool ColorCommonOptions::PosterizationOptions::operator!=(const ColorCommonOptions::PosterizationOptions& other) const { - return !(*this == other); + return !(*this == other); } bool ColorCommonOptions::PosterizationOptions::isEnabled() const { - return enabled; + return m_isEnabled; } void ColorCommonOptions::PosterizationOptions::setEnabled(bool enabled) { - PosterizationOptions::enabled = enabled; + PosterizationOptions::m_isEnabled = enabled; } int ColorCommonOptions::PosterizationOptions::getLevel() const { - return level; + return m_level; } void ColorCommonOptions::PosterizationOptions::setLevel(int level) { - PosterizationOptions::level = level; + PosterizationOptions::m_level = level; } bool ColorCommonOptions::PosterizationOptions::isNormalizationEnabled() const { - return normalizationEnabled; + return m_isNormalizationEnabled; } void ColorCommonOptions::PosterizationOptions::setNormalizationEnabled(bool normalizationEnabled) { - PosterizationOptions::normalizationEnabled = normalizationEnabled; + PosterizationOptions::m_isNormalizationEnabled = normalizationEnabled; } bool ColorCommonOptions::PosterizationOptions::isForceBlackAndWhite() const { - return forceBlackAndWhite; + return m_forceBlackAndWhite; } void ColorCommonOptions::PosterizationOptions::setForceBlackAndWhite(bool forceBlackAndWhite) { - PosterizationOptions::forceBlackAndWhite = forceBlackAndWhite; + PosterizationOptions::m_forceBlackAndWhite = forceBlackAndWhite; } } // namespace output \ No newline at end of file diff --git a/filters/output/ColorCommonOptions.h b/filters/output/ColorCommonOptions.h index 80b920df1..42a271fe3 100644 --- a/filters/output/ColorCommonOptions.h +++ b/filters/output/ColorCommonOptions.h @@ -29,78 +29,83 @@ namespace output { enum FillingColor { FILL_BACKGROUND, FILL_WHITE }; class ColorCommonOptions { -public: - class PosterizationOptions { - public: - PosterizationOptions(); + public: + class PosterizationOptions { + public: + PosterizationOptions(); - explicit PosterizationOptions(const QDomElement& el); + explicit PosterizationOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const PosterizationOptions& other) const; + bool operator==(const PosterizationOptions& other) const; - bool operator!=(const PosterizationOptions& other) const; + bool operator!=(const PosterizationOptions& other) const; - bool isEnabled() const; + bool isEnabled() const; - void setEnabled(bool enabled); + void setEnabled(bool enabled); - int getLevel() const; + int getLevel() const; - void setLevel(int level); + void setLevel(int level); - bool isNormalizationEnabled() const; + bool isNormalizationEnabled() const; - void setNormalizationEnabled(bool normalizationEnabled); + void setNormalizationEnabled(bool normalizationEnabled); - bool isForceBlackAndWhite() const; + bool isForceBlackAndWhite() const; - void setForceBlackAndWhite(bool forceBlackAndWhite); + void setForceBlackAndWhite(bool forceBlackAndWhite); - private: - bool enabled; - int level; - bool normalizationEnabled; - bool forceBlackAndWhite; - }; + private: + bool m_isEnabled; + int m_level; + bool m_isNormalizationEnabled; + bool m_forceBlackAndWhite; + }; - ColorCommonOptions(); + ColorCommonOptions(); - explicit ColorCommonOptions(const QDomElement& el); + explicit ColorCommonOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + bool fillOffcut() const; + + void setFillOffcut(bool fillOffcut); - bool cutMargins() const; + bool fillMargins() const; - void setCutMargins(bool val); + void setFillMargins(bool val); - bool normalizeIllumination() const; + bool normalizeIllumination() const; - void setNormalizeIllumination(bool val); + void setNormalizeIllumination(bool val); - FillingColor getFillingColor() const; + FillingColor getFillingColor() const; - void setFillingColor(FillingColor fillingColor); + void setFillingColor(FillingColor fillingColor); - bool operator==(const ColorCommonOptions& other) const; + bool operator==(const ColorCommonOptions& other) const; - bool operator!=(const ColorCommonOptions& other) const; + bool operator!=(const ColorCommonOptions& other) const; - const PosterizationOptions& getPosterizationOptions() const; + const PosterizationOptions& getPosterizationOptions() const; - void setPosterizationOptions(const PosterizationOptions& posterizationOptions); + void setPosterizationOptions(const PosterizationOptions& posterizationOptions); -private: - static FillingColor parseFillingColor(const QString& str); + private: + static FillingColor parseFillingColor(const QString& str); - static QString formatFillingColor(FillingColor type); + static QString formatFillingColor(FillingColor type); - bool m_cutMargins; - bool m_normalizeIllumination; - FillingColor m_fillingColor; - PosterizationOptions posterizationOptions; + bool m_fillOffcut; + bool m_fillMargins; + bool m_normalizeIllumination; + FillingColor m_fillingColor; + PosterizationOptions m_posterizationOptions; }; } // namespace output #endif // ifndef OUTPUT_COLOR_GRAYSCALE_OPTIONS_H_ diff --git a/filters/output/ColorParams.cpp b/filters/output/ColorParams.cpp index eb15f9217..85d4377f2 100644 --- a/filters/output/ColorParams.cpp +++ b/filters/output/ColorParams.cpp @@ -19,74 +19,72 @@ #include "ColorParams.h" namespace output { -ColorParams::ColorParams() : m_colorMode(BLACK_AND_WHITE) { -} +ColorParams::ColorParams() : m_colorMode(BLACK_AND_WHITE) {} ColorParams::ColorParams(const QDomElement& el) - : m_colorMode(parseColorMode(el.attribute("colorMode"))), - m_colorCommonOptions(el.namedItem("color-or-grayscale").toElement()), - m_bwOptions(el.namedItem("bw").toElement()) { -} + : m_colorMode(parseColorMode(el.attribute("colorMode"))), + m_colorCommonOptions(el.namedItem("color-or-grayscale").toElement()), + m_bwOptions(el.namedItem("bw").toElement()) {} QDomElement ColorParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("colorMode", formatColorMode(m_colorMode)); - el.appendChild(m_colorCommonOptions.toXml(doc, "color-or-grayscale")); - el.appendChild(m_bwOptions.toXml(doc, "bw")); + QDomElement el(doc.createElement(name)); + el.setAttribute("colorMode", formatColorMode(m_colorMode)); + el.appendChild(m_colorCommonOptions.toXml(doc, "color-or-grayscale")); + el.appendChild(m_bwOptions.toXml(doc, "bw")); - return el; + return el; } ColorMode ColorParams::parseColorMode(const QString& str) { - if (str == "bw") { - return BLACK_AND_WHITE; - } else if (str == "colorOrGray") { - return COLOR_GRAYSCALE; - } else if (str == "mixed") { - return MIXED; - } else { - return BLACK_AND_WHITE; - } + if (str == "bw") { + return BLACK_AND_WHITE; + } else if (str == "colorOrGray") { + return COLOR_GRAYSCALE; + } else if (str == "mixed") { + return MIXED; + } else { + return BLACK_AND_WHITE; + } } QString ColorParams::formatColorMode(const ColorMode mode) { - const char* str = ""; - switch (mode) { - case BLACK_AND_WHITE: - str = "bw"; - break; - case COLOR_GRAYSCALE: - str = "colorOrGray"; - break; - case MIXED: - str = "mixed"; - break; - } - - return QString::fromLatin1(str); + const char* str = ""; + switch (mode) { + case BLACK_AND_WHITE: + str = "bw"; + break; + case COLOR_GRAYSCALE: + str = "colorOrGray"; + break; + case MIXED: + str = "mixed"; + break; + } + + return QString::fromLatin1(str); } ColorMode ColorParams::colorMode() const { - return m_colorMode; + return m_colorMode; } void ColorParams::setColorMode(ColorMode mode) { - m_colorMode = mode; + m_colorMode = mode; } const ColorCommonOptions& ColorParams::colorCommonOptions() const { - return m_colorCommonOptions; + return m_colorCommonOptions; } void ColorParams::setColorCommonOptions(const ColorCommonOptions& opt) { - m_colorCommonOptions = opt; + m_colorCommonOptions = opt; } const BlackWhiteOptions& ColorParams::blackWhiteOptions() const { - return m_bwOptions; + return m_bwOptions; } void ColorParams::setBlackWhiteOptions(const BlackWhiteOptions& opt) { - m_bwOptions = opt; + m_bwOptions = opt; } } // namespace output \ No newline at end of file diff --git a/filters/output/ColorParams.h b/filters/output/ColorParams.h index 18b7695bb..28a2e4ff8 100644 --- a/filters/output/ColorParams.h +++ b/filters/output/ColorParams.h @@ -19,8 +19,8 @@ #ifndef OUTPUT_COLORPARAMS_H_ #define OUTPUT_COLORPARAMS_H_ -#include "ColorCommonOptions.h" #include "BlackWhiteOptions.h" +#include "ColorCommonOptions.h" #include "SplittingOptions.h" class QDomDocument; @@ -30,34 +30,34 @@ namespace output { enum ColorMode { BLACK_AND_WHITE, COLOR_GRAYSCALE, MIXED }; class ColorParams { -public: - ColorParams(); + public: + ColorParams(); - explicit ColorParams(const QDomElement& el); + explicit ColorParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - ColorMode colorMode() const; + ColorMode colorMode() const; - void setColorMode(ColorMode mode); + void setColorMode(ColorMode mode); - const ColorCommonOptions& colorCommonOptions() const; + const ColorCommonOptions& colorCommonOptions() const; - void setColorCommonOptions(const ColorCommonOptions& opt); + void setColorCommonOptions(const ColorCommonOptions& opt); - const BlackWhiteOptions& blackWhiteOptions() const; + const BlackWhiteOptions& blackWhiteOptions() const; - void setBlackWhiteOptions(const BlackWhiteOptions& opt); + void setBlackWhiteOptions(const BlackWhiteOptions& opt); -private: - static ColorMode parseColorMode(const QString& str); + private: + static ColorMode parseColorMode(const QString& str); - static QString formatColorMode(ColorMode mode); + static QString formatColorMode(ColorMode mode); - ColorMode m_colorMode; - ColorCommonOptions m_colorCommonOptions; - BlackWhiteOptions m_bwOptions; + ColorMode m_colorMode; + ColorCommonOptions m_colorCommonOptions; + BlackWhiteOptions m_bwOptions; }; } // namespace output #endif // ifndef OUTPUT_COLORPARAMS_H_ diff --git a/filters/output/ColorPickupInteraction.cpp b/filters/output/ColorPickupInteraction.cpp index 61d68cb2a..fc56d3cb7 100644 --- a/filters/output/ColorPickupInteraction.cpp +++ b/filters/output/ColorPickupInteraction.cpp @@ -17,139 +17,139 @@ */ #include "ColorPickupInteraction.h" -#include "ZoneInteractionContext.h" -#include "ImageViewBase.h" -#include "ScopedIncDec.h" #include #include +#include "ImageViewBase.h" +#include "ScopedIncDec.h" +#include "ZoneInteractionContext.h" namespace output { ColorPickupInteraction::ColorPickupInteraction(EditableZoneSet& zones, ZoneInteractionContext& context) - : m_rZones(zones), m_rContext(context), m_dontDrawCircle(0) { - m_interaction.setInteractionStatusTip(tr("Click on an area to pick up its color, or ESC to cancel.")); + : m_zones(zones), m_context(context), m_dontDrawCircle(0) { + m_interaction.setInteractionStatusTip(tr("Click on an area to pick up its color, or ESC to cancel.")); } void ColorPickupInteraction::startInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction) { - typedef FillColorProperty FCP; - m_ptrFillColorProp = zone.properties()->locateOrCreate(); - interaction.capture(m_interaction); + typedef FillColorProperty FCP; + m_fillColorProp = zone.properties()->locateOrCreate(); + interaction.capture(m_interaction); } bool ColorPickupInteraction::isActive(const InteractionState& interaction) const { - return interaction.capturedBy(m_interaction); + return interaction.capturedBy(m_interaction); } void ColorPickupInteraction::onPaint(QPainter& painter, const InteractionState& interaction) { - if (m_dontDrawCircle) { - return; - } + if (m_dontDrawCircle) { + return; + } - painter.setWorldTransform(QTransform()); - painter.setRenderHint(QPainter::Antialiasing, true); + painter.setWorldTransform(QTransform()); + painter.setRenderHint(QPainter::Antialiasing, true); - QPen pen(Qt::red); - pen.setWidthF(1.5); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - painter.drawEllipse(targetBoundingRect()); + QPen pen(Qt::red); + pen.setWidthF(1.5); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawEllipse(targetBoundingRect()); } void ColorPickupInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->buttons() == Qt::LeftButton) { // Left and only left button. - event->accept(); - takeColor(); - switchToDefaultInteraction(); - } + if (event->buttons() == Qt::LeftButton) { // Left and only left button. + event->accept(); + takeColor(); + switchToDefaultInteraction(); + } } void ColorPickupInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - m_rContext.imageView().update(); + m_context.imageView().update(); } void ColorPickupInteraction::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - if (event->key() == Qt::Key_Escape) { - event->accept(); - switchToDefaultInteraction(); - } + if (event->key() == Qt::Key_Escape) { + event->accept(); + switchToDefaultInteraction(); + } } void ColorPickupInteraction::takeColor() { - const ScopedIncDec guard(m_dontDrawCircle); + const ScopedIncDec guard(m_dontDrawCircle); - const QRect rect(targetBoundingRect()); - const QPixmap pixmap(QPixmap::grabWidget(&m_rContext.imageView(), rect)); - if (pixmap.isNull()) { - return; - } - const QImage image(pixmap.toImage().convertToFormat(QImage::Format_RGB32)); + const QRect rect(targetBoundingRect()); + const QPixmap pixmap(QPixmap::grabWidget(&m_context.imageView(), rect)); + if (pixmap.isNull()) { + return; + } + const QImage image(pixmap.toImage().convertToFormat(QImage::Format_RGB32)); - const int width = rect.width(); - const int height = rect.height(); - const int x_center = width / 2; - const int y_center = height / 2; - const int sqdist_threshold = x_center * y_center; - const uint32_t* line = (uint32_t*) image.bits(); - const int stride = image.bytesPerLine() / 4; + const int width = rect.width(); + const int height = rect.height(); + const int x_center = width / 2; + const int y_center = height / 2; + const int sqdist_threshold = x_center * y_center; + const uint32_t* line = (uint32_t*) image.bits(); + const int stride = image.bytesPerLine() / 4; - // We are going to take a median color using the bit-mixing technique. - std::vector bitmixed_colors; - bitmixed_colors.reserve(width * height); - // Take colors from the circle. - for (int y = 0; y < height; ++y, line += stride) { - const int dy = y - y_center; - const int dy_sq = dy * dy; - for (int x = 0; x < width; ++x) { - const int dx = x - x_center; - const int dx_sq = dx * dx; - const int sqdist = dy_sq + dx_sq; - if (sqdist <= sqdist_threshold) { - const uint32_t color = line[x]; - bitmixed_colors.push_back(bitMixColor(color)); - } - } + // We are going to take a median color using the bit-mixing technique. + std::vector bitmixed_colors; + bitmixed_colors.reserve(width * height); + // Take colors from the circle. + for (int y = 0; y < height; ++y, line += stride) { + const int dy = y - y_center; + const int dy_sq = dy * dy; + for (int x = 0; x < width; ++x) { + const int dx = x - x_center; + const int dx_sq = dx * dx; + const int sqdist = dy_sq + dx_sq; + if (sqdist <= sqdist_threshold) { + const uint32_t color = line[x]; + bitmixed_colors.push_back(bitMixColor(color)); + } } + } - if (bitmixed_colors.empty()) { - return; - } + if (bitmixed_colors.empty()) { + return; + } - auto half_pos(bitmixed_colors.begin() + bitmixed_colors.size() / 2); - std::nth_element(bitmixed_colors.begin(), half_pos, bitmixed_colors.end()); - const QColor color(bitUnmixColor(*half_pos)); + auto half_pos(bitmixed_colors.begin() + bitmixed_colors.size() / 2); + std::nth_element(bitmixed_colors.begin(), half_pos, bitmixed_colors.end()); + const QColor color(bitUnmixColor(*half_pos)); - m_ptrFillColorProp->setColor(color); + m_fillColorProp->setColor(color); - // Update default properties. - PropertySet default_props(m_rZones.defaultProperties()); - default_props.locateOrCreate()->setColor(color); - m_rZones.setDefaultProperties(default_props); - m_rZones.commit(); + // Update default properties. + PropertySet default_props(m_zones.defaultProperties()); + default_props.locateOrCreate()->setColor(color); + m_zones.setDefaultProperties(default_props); + m_zones.commit(); } // ColorPickupInteraction::takeColor QRect ColorPickupInteraction::targetBoundingRect() const { - const QPoint mouse_pos(m_rContext.imageView().mapFromGlobal(QCursor::pos())); - QRect rect(0, 0, 15, 15); // Odd width and height are needed for symmetry. - rect.moveCenter(mouse_pos); + const QPoint mouse_pos(m_context.imageView().mapFromGlobal(QCursor::pos())); + QRect rect(0, 0, 15, 15); // Odd width and height are needed for symmetry. + rect.moveCenter(mouse_pos); - return rect; + return rect; } void ColorPickupInteraction::switchToDefaultInteraction() { - m_ptrFillColorProp.reset(); - m_interaction.release(); - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - unlink(); - m_rContext.imageView().update(); + m_fillColorProp.reset(); + m_interaction.release(); + makePeerPreceeder(*m_context.createDefaultInteraction()); + unlink(); + m_context.imageView().update(); } uint32_t ColorPickupInteraction::bitMixColor(const uint32_t color) { - return m_sBitMixingLUT[0][(color >> 16) & 0xff] | m_sBitMixingLUT[1][(color >> 8) & 0xff] - | m_sBitMixingLUT[2][color & 0xff]; + return m_sBitMixingLUT[0][(color >> 16) & 0xff] | m_sBitMixingLUT[1][(color >> 8) & 0xff] + | m_sBitMixingLUT[2][color & 0xff]; } uint32_t ColorPickupInteraction::bitUnmixColor(const uint32_t mixed) { - return m_sBitUnmixingLUT[0][(mixed >> 16) & 0xff] | m_sBitUnmixingLUT[1][(mixed >> 8) & 0xff] - | m_sBitUnmixingLUT[2][mixed & 0xff]; + return m_sBitUnmixingLUT[0][(mixed >> 16) & 0xff] | m_sBitUnmixingLUT[1][(mixed >> 8) & 0xff] + | m_sBitUnmixingLUT[2][mixed & 0xff]; } /** @@ -171,93 +171,93 @@ uint32_t ColorPickupInteraction::bitUnmixColor(const uint32_t mixed) { * \endcode */ const uint32_t ColorPickupInteraction::m_sBitMixingLUT[3][256] - = {{0x00000000, 0x00000004, 0x00000020, 0x00000024, 0x00000100, 0x00000104, 0x00000120, 0x00000124, 0x00000800, - 0x00000804, 0x00000820, 0x00000824, 0x00000900, 0x00000904, 0x00000920, 0x00000924, 0x00004000, 0x00004004, - 0x00004020, 0x00004024, 0x00004100, 0x00004104, 0x00004120, 0x00004124, 0x00004800, 0x00004804, 0x00004820, - 0x00004824, 0x00004900, 0x00004904, 0x00004920, 0x00004924, 0x00020000, 0x00020004, 0x00020020, 0x00020024, - 0x00020100, 0x00020104, 0x00020120, 0x00020124, 0x00020800, 0x00020804, 0x00020820, 0x00020824, 0x00020900, - 0x00020904, 0x00020920, 0x00020924, 0x00024000, 0x00024004, 0x00024020, 0x00024024, 0x00024100, 0x00024104, - 0x00024120, 0x00024124, 0x00024800, 0x00024804, 0x00024820, 0x00024824, 0x00024900, 0x00024904, 0x00024920, - 0x00024924, 0x00100000, 0x00100004, 0x00100020, 0x00100024, 0x00100100, 0x00100104, 0x00100120, 0x00100124, - 0x00100800, 0x00100804, 0x00100820, 0x00100824, 0x00100900, 0x00100904, 0x00100920, 0x00100924, 0x00104000, - 0x00104004, 0x00104020, 0x00104024, 0x00104100, 0x00104104, 0x00104120, 0x00104124, 0x00104800, 0x00104804, - 0x00104820, 0x00104824, 0x00104900, 0x00104904, 0x00104920, 0x00104924, 0x00120000, 0x00120004, 0x00120020, - 0x00120024, 0x00120100, 0x00120104, 0x00120120, 0x00120124, 0x00120800, 0x00120804, 0x00120820, 0x00120824, - 0x00120900, 0x00120904, 0x00120920, 0x00120924, 0x00124000, 0x00124004, 0x00124020, 0x00124024, 0x00124100, - 0x00124104, 0x00124120, 0x00124124, 0x00124800, 0x00124804, 0x00124820, 0x00124824, 0x00124900, 0x00124904, - 0x00124920, 0x00124924, 0x00800000, 0x00800004, 0x00800020, 0x00800024, 0x00800100, 0x00800104, 0x00800120, - 0x00800124, 0x00800800, 0x00800804, 0x00800820, 0x00800824, 0x00800900, 0x00800904, 0x00800920, 0x00800924, - 0x00804000, 0x00804004, 0x00804020, 0x00804024, 0x00804100, 0x00804104, 0x00804120, 0x00804124, 0x00804800, - 0x00804804, 0x00804820, 0x00804824, 0x00804900, 0x00804904, 0x00804920, 0x00804924, 0x00820000, 0x00820004, - 0x00820020, 0x00820024, 0x00820100, 0x00820104, 0x00820120, 0x00820124, 0x00820800, 0x00820804, 0x00820820, - 0x00820824, 0x00820900, 0x00820904, 0x00820920, 0x00820924, 0x00824000, 0x00824004, 0x00824020, 0x00824024, - 0x00824100, 0x00824104, 0x00824120, 0x00824124, 0x00824800, 0x00824804, 0x00824820, 0x00824824, 0x00824900, - 0x00824904, 0x00824920, 0x00824924, 0x00900000, 0x00900004, 0x00900020, 0x00900024, 0x00900100, 0x00900104, - 0x00900120, 0x00900124, 0x00900800, 0x00900804, 0x00900820, 0x00900824, 0x00900900, 0x00900904, 0x00900920, - 0x00900924, 0x00904000, 0x00904004, 0x00904020, 0x00904024, 0x00904100, 0x00904104, 0x00904120, 0x00904124, - 0x00904800, 0x00904804, 0x00904820, 0x00904824, 0x00904900, 0x00904904, 0x00904920, 0x00904924, 0x00920000, - 0x00920004, 0x00920020, 0x00920024, 0x00920100, 0x00920104, 0x00920120, 0x00920124, 0x00920800, 0x00920804, - 0x00920820, 0x00920824, 0x00920900, 0x00920904, 0x00920920, 0x00920924, 0x00924000, 0x00924004, 0x00924020, - 0x00924024, 0x00924100, 0x00924104, 0x00924120, 0x00924124, 0x00924800, 0x00924804, 0x00924820, 0x00924824, - 0x00924900, 0x00924904, 0x00924920, 0x00924924}, - {0x00000000, 0x00000002, 0x00000010, 0x00000012, 0x00000080, 0x00000082, 0x00000090, 0x00000092, 0x00000400, - 0x00000402, 0x00000410, 0x00000412, 0x00000480, 0x00000482, 0x00000490, 0x00000492, 0x00002000, 0x00002002, - 0x00002010, 0x00002012, 0x00002080, 0x00002082, 0x00002090, 0x00002092, 0x00002400, 0x00002402, 0x00002410, - 0x00002412, 0x00002480, 0x00002482, 0x00002490, 0x00002492, 0x00010000, 0x00010002, 0x00010010, 0x00010012, - 0x00010080, 0x00010082, 0x00010090, 0x00010092, 0x00010400, 0x00010402, 0x00010410, 0x00010412, 0x00010480, - 0x00010482, 0x00010490, 0x00010492, 0x00012000, 0x00012002, 0x00012010, 0x00012012, 0x00012080, 0x00012082, - 0x00012090, 0x00012092, 0x00012400, 0x00012402, 0x00012410, 0x00012412, 0x00012480, 0x00012482, 0x00012490, - 0x00012492, 0x00080000, 0x00080002, 0x00080010, 0x00080012, 0x00080080, 0x00080082, 0x00080090, 0x00080092, - 0x00080400, 0x00080402, 0x00080410, 0x00080412, 0x00080480, 0x00080482, 0x00080490, 0x00080492, 0x00082000, - 0x00082002, 0x00082010, 0x00082012, 0x00082080, 0x00082082, 0x00082090, 0x00082092, 0x00082400, 0x00082402, - 0x00082410, 0x00082412, 0x00082480, 0x00082482, 0x00082490, 0x00082492, 0x00090000, 0x00090002, 0x00090010, - 0x00090012, 0x00090080, 0x00090082, 0x00090090, 0x00090092, 0x00090400, 0x00090402, 0x00090410, 0x00090412, - 0x00090480, 0x00090482, 0x00090490, 0x00090492, 0x00092000, 0x00092002, 0x00092010, 0x00092012, 0x00092080, - 0x00092082, 0x00092090, 0x00092092, 0x00092400, 0x00092402, 0x00092410, 0x00092412, 0x00092480, 0x00092482, - 0x00092490, 0x00092492, 0x00400000, 0x00400002, 0x00400010, 0x00400012, 0x00400080, 0x00400082, 0x00400090, - 0x00400092, 0x00400400, 0x00400402, 0x00400410, 0x00400412, 0x00400480, 0x00400482, 0x00400490, 0x00400492, - 0x00402000, 0x00402002, 0x00402010, 0x00402012, 0x00402080, 0x00402082, 0x00402090, 0x00402092, 0x00402400, - 0x00402402, 0x00402410, 0x00402412, 0x00402480, 0x00402482, 0x00402490, 0x00402492, 0x00410000, 0x00410002, - 0x00410010, 0x00410012, 0x00410080, 0x00410082, 0x00410090, 0x00410092, 0x00410400, 0x00410402, 0x00410410, - 0x00410412, 0x00410480, 0x00410482, 0x00410490, 0x00410492, 0x00412000, 0x00412002, 0x00412010, 0x00412012, - 0x00412080, 0x00412082, 0x00412090, 0x00412092, 0x00412400, 0x00412402, 0x00412410, 0x00412412, 0x00412480, - 0x00412482, 0x00412490, 0x00412492, 0x00480000, 0x00480002, 0x00480010, 0x00480012, 0x00480080, 0x00480082, - 0x00480090, 0x00480092, 0x00480400, 0x00480402, 0x00480410, 0x00480412, 0x00480480, 0x00480482, 0x00480490, - 0x00480492, 0x00482000, 0x00482002, 0x00482010, 0x00482012, 0x00482080, 0x00482082, 0x00482090, 0x00482092, - 0x00482400, 0x00482402, 0x00482410, 0x00482412, 0x00482480, 0x00482482, 0x00482490, 0x00482492, 0x00490000, - 0x00490002, 0x00490010, 0x00490012, 0x00490080, 0x00490082, 0x00490090, 0x00490092, 0x00490400, 0x00490402, - 0x00490410, 0x00490412, 0x00490480, 0x00490482, 0x00490490, 0x00490492, 0x00492000, 0x00492002, 0x00492010, - 0x00492012, 0x00492080, 0x00492082, 0x00492090, 0x00492092, 0x00492400, 0x00492402, 0x00492410, 0x00492412, - 0x00492480, 0x00492482, 0x00492490, 0x00492492}, - {0x00000000, 0x00000001, 0x00000008, 0x00000009, 0x00000040, 0x00000041, 0x00000048, 0x00000049, 0x00000200, - 0x00000201, 0x00000208, 0x00000209, 0x00000240, 0x00000241, 0x00000248, 0x00000249, 0x00001000, 0x00001001, - 0x00001008, 0x00001009, 0x00001040, 0x00001041, 0x00001048, 0x00001049, 0x00001200, 0x00001201, 0x00001208, - 0x00001209, 0x00001240, 0x00001241, 0x00001248, 0x00001249, 0x00008000, 0x00008001, 0x00008008, 0x00008009, - 0x00008040, 0x00008041, 0x00008048, 0x00008049, 0x00008200, 0x00008201, 0x00008208, 0x00008209, 0x00008240, - 0x00008241, 0x00008248, 0x00008249, 0x00009000, 0x00009001, 0x00009008, 0x00009009, 0x00009040, 0x00009041, - 0x00009048, 0x00009049, 0x00009200, 0x00009201, 0x00009208, 0x00009209, 0x00009240, 0x00009241, 0x00009248, - 0x00009249, 0x00040000, 0x00040001, 0x00040008, 0x00040009, 0x00040040, 0x00040041, 0x00040048, 0x00040049, - 0x00040200, 0x00040201, 0x00040208, 0x00040209, 0x00040240, 0x00040241, 0x00040248, 0x00040249, 0x00041000, - 0x00041001, 0x00041008, 0x00041009, 0x00041040, 0x00041041, 0x00041048, 0x00041049, 0x00041200, 0x00041201, - 0x00041208, 0x00041209, 0x00041240, 0x00041241, 0x00041248, 0x00041249, 0x00048000, 0x00048001, 0x00048008, - 0x00048009, 0x00048040, 0x00048041, 0x00048048, 0x00048049, 0x00048200, 0x00048201, 0x00048208, 0x00048209, - 0x00048240, 0x00048241, 0x00048248, 0x00048249, 0x00049000, 0x00049001, 0x00049008, 0x00049009, 0x00049040, - 0x00049041, 0x00049048, 0x00049049, 0x00049200, 0x00049201, 0x00049208, 0x00049209, 0x00049240, 0x00049241, - 0x00049248, 0x00049249, 0x00200000, 0x00200001, 0x00200008, 0x00200009, 0x00200040, 0x00200041, 0x00200048, - 0x00200049, 0x00200200, 0x00200201, 0x00200208, 0x00200209, 0x00200240, 0x00200241, 0x00200248, 0x00200249, - 0x00201000, 0x00201001, 0x00201008, 0x00201009, 0x00201040, 0x00201041, 0x00201048, 0x00201049, 0x00201200, - 0x00201201, 0x00201208, 0x00201209, 0x00201240, 0x00201241, 0x00201248, 0x00201249, 0x00208000, 0x00208001, - 0x00208008, 0x00208009, 0x00208040, 0x00208041, 0x00208048, 0x00208049, 0x00208200, 0x00208201, 0x00208208, - 0x00208209, 0x00208240, 0x00208241, 0x00208248, 0x00208249, 0x00209000, 0x00209001, 0x00209008, 0x00209009, - 0x00209040, 0x00209041, 0x00209048, 0x00209049, 0x00209200, 0x00209201, 0x00209208, 0x00209209, 0x00209240, - 0x00209241, 0x00209248, 0x00209249, 0x00240000, 0x00240001, 0x00240008, 0x00240009, 0x00240040, 0x00240041, - 0x00240048, 0x00240049, 0x00240200, 0x00240201, 0x00240208, 0x00240209, 0x00240240, 0x00240241, 0x00240248, - 0x00240249, 0x00241000, 0x00241001, 0x00241008, 0x00241009, 0x00241040, 0x00241041, 0x00241048, 0x00241049, - 0x00241200, 0x00241201, 0x00241208, 0x00241209, 0x00241240, 0x00241241, 0x00241248, 0x00241249, 0x00248000, - 0x00248001, 0x00248008, 0x00248009, 0x00248040, 0x00248041, 0x00248048, 0x00248049, 0x00248200, 0x00248201, - 0x00248208, 0x00248209, 0x00248240, 0x00248241, 0x00248248, 0x00248249, 0x00249000, 0x00249001, 0x00249008, - 0x00249009, 0x00249040, 0x00249041, 0x00249048, 0x00249049, 0x00249200, 0x00249201, 0x00249208, 0x00249209, - 0x00249240, 0x00249241, 0x00249248, 0x00249249}}; + = {{0x00000000, 0x00000004, 0x00000020, 0x00000024, 0x00000100, 0x00000104, 0x00000120, 0x00000124, 0x00000800, + 0x00000804, 0x00000820, 0x00000824, 0x00000900, 0x00000904, 0x00000920, 0x00000924, 0x00004000, 0x00004004, + 0x00004020, 0x00004024, 0x00004100, 0x00004104, 0x00004120, 0x00004124, 0x00004800, 0x00004804, 0x00004820, + 0x00004824, 0x00004900, 0x00004904, 0x00004920, 0x00004924, 0x00020000, 0x00020004, 0x00020020, 0x00020024, + 0x00020100, 0x00020104, 0x00020120, 0x00020124, 0x00020800, 0x00020804, 0x00020820, 0x00020824, 0x00020900, + 0x00020904, 0x00020920, 0x00020924, 0x00024000, 0x00024004, 0x00024020, 0x00024024, 0x00024100, 0x00024104, + 0x00024120, 0x00024124, 0x00024800, 0x00024804, 0x00024820, 0x00024824, 0x00024900, 0x00024904, 0x00024920, + 0x00024924, 0x00100000, 0x00100004, 0x00100020, 0x00100024, 0x00100100, 0x00100104, 0x00100120, 0x00100124, + 0x00100800, 0x00100804, 0x00100820, 0x00100824, 0x00100900, 0x00100904, 0x00100920, 0x00100924, 0x00104000, + 0x00104004, 0x00104020, 0x00104024, 0x00104100, 0x00104104, 0x00104120, 0x00104124, 0x00104800, 0x00104804, + 0x00104820, 0x00104824, 0x00104900, 0x00104904, 0x00104920, 0x00104924, 0x00120000, 0x00120004, 0x00120020, + 0x00120024, 0x00120100, 0x00120104, 0x00120120, 0x00120124, 0x00120800, 0x00120804, 0x00120820, 0x00120824, + 0x00120900, 0x00120904, 0x00120920, 0x00120924, 0x00124000, 0x00124004, 0x00124020, 0x00124024, 0x00124100, + 0x00124104, 0x00124120, 0x00124124, 0x00124800, 0x00124804, 0x00124820, 0x00124824, 0x00124900, 0x00124904, + 0x00124920, 0x00124924, 0x00800000, 0x00800004, 0x00800020, 0x00800024, 0x00800100, 0x00800104, 0x00800120, + 0x00800124, 0x00800800, 0x00800804, 0x00800820, 0x00800824, 0x00800900, 0x00800904, 0x00800920, 0x00800924, + 0x00804000, 0x00804004, 0x00804020, 0x00804024, 0x00804100, 0x00804104, 0x00804120, 0x00804124, 0x00804800, + 0x00804804, 0x00804820, 0x00804824, 0x00804900, 0x00804904, 0x00804920, 0x00804924, 0x00820000, 0x00820004, + 0x00820020, 0x00820024, 0x00820100, 0x00820104, 0x00820120, 0x00820124, 0x00820800, 0x00820804, 0x00820820, + 0x00820824, 0x00820900, 0x00820904, 0x00820920, 0x00820924, 0x00824000, 0x00824004, 0x00824020, 0x00824024, + 0x00824100, 0x00824104, 0x00824120, 0x00824124, 0x00824800, 0x00824804, 0x00824820, 0x00824824, 0x00824900, + 0x00824904, 0x00824920, 0x00824924, 0x00900000, 0x00900004, 0x00900020, 0x00900024, 0x00900100, 0x00900104, + 0x00900120, 0x00900124, 0x00900800, 0x00900804, 0x00900820, 0x00900824, 0x00900900, 0x00900904, 0x00900920, + 0x00900924, 0x00904000, 0x00904004, 0x00904020, 0x00904024, 0x00904100, 0x00904104, 0x00904120, 0x00904124, + 0x00904800, 0x00904804, 0x00904820, 0x00904824, 0x00904900, 0x00904904, 0x00904920, 0x00904924, 0x00920000, + 0x00920004, 0x00920020, 0x00920024, 0x00920100, 0x00920104, 0x00920120, 0x00920124, 0x00920800, 0x00920804, + 0x00920820, 0x00920824, 0x00920900, 0x00920904, 0x00920920, 0x00920924, 0x00924000, 0x00924004, 0x00924020, + 0x00924024, 0x00924100, 0x00924104, 0x00924120, 0x00924124, 0x00924800, 0x00924804, 0x00924820, 0x00924824, + 0x00924900, 0x00924904, 0x00924920, 0x00924924}, + {0x00000000, 0x00000002, 0x00000010, 0x00000012, 0x00000080, 0x00000082, 0x00000090, 0x00000092, 0x00000400, + 0x00000402, 0x00000410, 0x00000412, 0x00000480, 0x00000482, 0x00000490, 0x00000492, 0x00002000, 0x00002002, + 0x00002010, 0x00002012, 0x00002080, 0x00002082, 0x00002090, 0x00002092, 0x00002400, 0x00002402, 0x00002410, + 0x00002412, 0x00002480, 0x00002482, 0x00002490, 0x00002492, 0x00010000, 0x00010002, 0x00010010, 0x00010012, + 0x00010080, 0x00010082, 0x00010090, 0x00010092, 0x00010400, 0x00010402, 0x00010410, 0x00010412, 0x00010480, + 0x00010482, 0x00010490, 0x00010492, 0x00012000, 0x00012002, 0x00012010, 0x00012012, 0x00012080, 0x00012082, + 0x00012090, 0x00012092, 0x00012400, 0x00012402, 0x00012410, 0x00012412, 0x00012480, 0x00012482, 0x00012490, + 0x00012492, 0x00080000, 0x00080002, 0x00080010, 0x00080012, 0x00080080, 0x00080082, 0x00080090, 0x00080092, + 0x00080400, 0x00080402, 0x00080410, 0x00080412, 0x00080480, 0x00080482, 0x00080490, 0x00080492, 0x00082000, + 0x00082002, 0x00082010, 0x00082012, 0x00082080, 0x00082082, 0x00082090, 0x00082092, 0x00082400, 0x00082402, + 0x00082410, 0x00082412, 0x00082480, 0x00082482, 0x00082490, 0x00082492, 0x00090000, 0x00090002, 0x00090010, + 0x00090012, 0x00090080, 0x00090082, 0x00090090, 0x00090092, 0x00090400, 0x00090402, 0x00090410, 0x00090412, + 0x00090480, 0x00090482, 0x00090490, 0x00090492, 0x00092000, 0x00092002, 0x00092010, 0x00092012, 0x00092080, + 0x00092082, 0x00092090, 0x00092092, 0x00092400, 0x00092402, 0x00092410, 0x00092412, 0x00092480, 0x00092482, + 0x00092490, 0x00092492, 0x00400000, 0x00400002, 0x00400010, 0x00400012, 0x00400080, 0x00400082, 0x00400090, + 0x00400092, 0x00400400, 0x00400402, 0x00400410, 0x00400412, 0x00400480, 0x00400482, 0x00400490, 0x00400492, + 0x00402000, 0x00402002, 0x00402010, 0x00402012, 0x00402080, 0x00402082, 0x00402090, 0x00402092, 0x00402400, + 0x00402402, 0x00402410, 0x00402412, 0x00402480, 0x00402482, 0x00402490, 0x00402492, 0x00410000, 0x00410002, + 0x00410010, 0x00410012, 0x00410080, 0x00410082, 0x00410090, 0x00410092, 0x00410400, 0x00410402, 0x00410410, + 0x00410412, 0x00410480, 0x00410482, 0x00410490, 0x00410492, 0x00412000, 0x00412002, 0x00412010, 0x00412012, + 0x00412080, 0x00412082, 0x00412090, 0x00412092, 0x00412400, 0x00412402, 0x00412410, 0x00412412, 0x00412480, + 0x00412482, 0x00412490, 0x00412492, 0x00480000, 0x00480002, 0x00480010, 0x00480012, 0x00480080, 0x00480082, + 0x00480090, 0x00480092, 0x00480400, 0x00480402, 0x00480410, 0x00480412, 0x00480480, 0x00480482, 0x00480490, + 0x00480492, 0x00482000, 0x00482002, 0x00482010, 0x00482012, 0x00482080, 0x00482082, 0x00482090, 0x00482092, + 0x00482400, 0x00482402, 0x00482410, 0x00482412, 0x00482480, 0x00482482, 0x00482490, 0x00482492, 0x00490000, + 0x00490002, 0x00490010, 0x00490012, 0x00490080, 0x00490082, 0x00490090, 0x00490092, 0x00490400, 0x00490402, + 0x00490410, 0x00490412, 0x00490480, 0x00490482, 0x00490490, 0x00490492, 0x00492000, 0x00492002, 0x00492010, + 0x00492012, 0x00492080, 0x00492082, 0x00492090, 0x00492092, 0x00492400, 0x00492402, 0x00492410, 0x00492412, + 0x00492480, 0x00492482, 0x00492490, 0x00492492}, + {0x00000000, 0x00000001, 0x00000008, 0x00000009, 0x00000040, 0x00000041, 0x00000048, 0x00000049, 0x00000200, + 0x00000201, 0x00000208, 0x00000209, 0x00000240, 0x00000241, 0x00000248, 0x00000249, 0x00001000, 0x00001001, + 0x00001008, 0x00001009, 0x00001040, 0x00001041, 0x00001048, 0x00001049, 0x00001200, 0x00001201, 0x00001208, + 0x00001209, 0x00001240, 0x00001241, 0x00001248, 0x00001249, 0x00008000, 0x00008001, 0x00008008, 0x00008009, + 0x00008040, 0x00008041, 0x00008048, 0x00008049, 0x00008200, 0x00008201, 0x00008208, 0x00008209, 0x00008240, + 0x00008241, 0x00008248, 0x00008249, 0x00009000, 0x00009001, 0x00009008, 0x00009009, 0x00009040, 0x00009041, + 0x00009048, 0x00009049, 0x00009200, 0x00009201, 0x00009208, 0x00009209, 0x00009240, 0x00009241, 0x00009248, + 0x00009249, 0x00040000, 0x00040001, 0x00040008, 0x00040009, 0x00040040, 0x00040041, 0x00040048, 0x00040049, + 0x00040200, 0x00040201, 0x00040208, 0x00040209, 0x00040240, 0x00040241, 0x00040248, 0x00040249, 0x00041000, + 0x00041001, 0x00041008, 0x00041009, 0x00041040, 0x00041041, 0x00041048, 0x00041049, 0x00041200, 0x00041201, + 0x00041208, 0x00041209, 0x00041240, 0x00041241, 0x00041248, 0x00041249, 0x00048000, 0x00048001, 0x00048008, + 0x00048009, 0x00048040, 0x00048041, 0x00048048, 0x00048049, 0x00048200, 0x00048201, 0x00048208, 0x00048209, + 0x00048240, 0x00048241, 0x00048248, 0x00048249, 0x00049000, 0x00049001, 0x00049008, 0x00049009, 0x00049040, + 0x00049041, 0x00049048, 0x00049049, 0x00049200, 0x00049201, 0x00049208, 0x00049209, 0x00049240, 0x00049241, + 0x00049248, 0x00049249, 0x00200000, 0x00200001, 0x00200008, 0x00200009, 0x00200040, 0x00200041, 0x00200048, + 0x00200049, 0x00200200, 0x00200201, 0x00200208, 0x00200209, 0x00200240, 0x00200241, 0x00200248, 0x00200249, + 0x00201000, 0x00201001, 0x00201008, 0x00201009, 0x00201040, 0x00201041, 0x00201048, 0x00201049, 0x00201200, + 0x00201201, 0x00201208, 0x00201209, 0x00201240, 0x00201241, 0x00201248, 0x00201249, 0x00208000, 0x00208001, + 0x00208008, 0x00208009, 0x00208040, 0x00208041, 0x00208048, 0x00208049, 0x00208200, 0x00208201, 0x00208208, + 0x00208209, 0x00208240, 0x00208241, 0x00208248, 0x00208249, 0x00209000, 0x00209001, 0x00209008, 0x00209009, + 0x00209040, 0x00209041, 0x00209048, 0x00209049, 0x00209200, 0x00209201, 0x00209208, 0x00209209, 0x00209240, + 0x00209241, 0x00209248, 0x00209249, 0x00240000, 0x00240001, 0x00240008, 0x00240009, 0x00240040, 0x00240041, + 0x00240048, 0x00240049, 0x00240200, 0x00240201, 0x00240208, 0x00240209, 0x00240240, 0x00240241, 0x00240248, + 0x00240249, 0x00241000, 0x00241001, 0x00241008, 0x00241009, 0x00241040, 0x00241041, 0x00241048, 0x00241049, + 0x00241200, 0x00241201, 0x00241208, 0x00241209, 0x00241240, 0x00241241, 0x00241248, 0x00241249, 0x00248000, + 0x00248001, 0x00248008, 0x00248009, 0x00248040, 0x00248041, 0x00248048, 0x00248049, 0x00248200, 0x00248201, + 0x00248208, 0x00248209, 0x00248240, 0x00248241, 0x00248248, 0x00248249, 0x00249000, 0x00249001, 0x00249008, + 0x00249009, 0x00249040, 0x00249041, 0x00249048, 0x00249049, 0x00249200, 0x00249201, 0x00249208, 0x00249209, + 0x00249240, 0x00249241, 0x00249248, 0x00249249}}; /** * Generated like this: @@ -281,91 +281,91 @@ const uint32_t ColorPickupInteraction::m_sBitMixingLUT[3][256] * \endcode */ const uint32_t ColorPickupInteraction::m_sBitUnmixingLUT[3][256] - = {{0x00000000, 0x00002000, 0x00200000, 0x00202000, 0x00000040, 0x00002040, 0x00200040, 0x00202040, 0x00004000, - 0x00006000, 0x00204000, 0x00206000, 0x00004040, 0x00006040, 0x00204040, 0x00206040, 0x00400000, 0x00402000, - 0x00600000, 0x00602000, 0x00400040, 0x00402040, 0x00600040, 0x00602040, 0x00404000, 0x00406000, 0x00604000, - 0x00606000, 0x00404040, 0x00406040, 0x00604040, 0x00606040, 0x00000080, 0x00002080, 0x00200080, 0x00202080, - 0x000000c0, 0x000020c0, 0x002000c0, 0x002020c0, 0x00004080, 0x00006080, 0x00204080, 0x00206080, 0x000040c0, - 0x000060c0, 0x002040c0, 0x002060c0, 0x00400080, 0x00402080, 0x00600080, 0x00602080, 0x004000c0, 0x004020c0, - 0x006000c0, 0x006020c0, 0x00404080, 0x00406080, 0x00604080, 0x00606080, 0x004040c0, 0x004060c0, 0x006040c0, - 0x006060c0, 0x00008000, 0x0000a000, 0x00208000, 0x0020a000, 0x00008040, 0x0000a040, 0x00208040, 0x0020a040, - 0x0000c000, 0x0000e000, 0x0020c000, 0x0020e000, 0x0000c040, 0x0000e040, 0x0020c040, 0x0020e040, 0x00408000, - 0x0040a000, 0x00608000, 0x0060a000, 0x00408040, 0x0040a040, 0x00608040, 0x0060a040, 0x0040c000, 0x0040e000, - 0x0060c000, 0x0060e000, 0x0040c040, 0x0040e040, 0x0060c040, 0x0060e040, 0x00008080, 0x0000a080, 0x00208080, - 0x0020a080, 0x000080c0, 0x0000a0c0, 0x002080c0, 0x0020a0c0, 0x0000c080, 0x0000e080, 0x0020c080, 0x0020e080, - 0x0000c0c0, 0x0000e0c0, 0x0020c0c0, 0x0020e0c0, 0x00408080, 0x0040a080, 0x00608080, 0x0060a080, 0x004080c0, - 0x0040a0c0, 0x006080c0, 0x0060a0c0, 0x0040c080, 0x0040e080, 0x0060c080, 0x0060e080, 0x0040c0c0, 0x0040e0c0, - 0x0060c0c0, 0x0060e0c0, 0x00800000, 0x00802000, 0x00a00000, 0x00a02000, 0x00800040, 0x00802040, 0x00a00040, - 0x00a02040, 0x00804000, 0x00806000, 0x00a04000, 0x00a06000, 0x00804040, 0x00806040, 0x00a04040, 0x00a06040, - 0x00c00000, 0x00c02000, 0x00e00000, 0x00e02000, 0x00c00040, 0x00c02040, 0x00e00040, 0x00e02040, 0x00c04000, - 0x00c06000, 0x00e04000, 0x00e06000, 0x00c04040, 0x00c06040, 0x00e04040, 0x00e06040, 0x00800080, 0x00802080, - 0x00a00080, 0x00a02080, 0x008000c0, 0x008020c0, 0x00a000c0, 0x00a020c0, 0x00804080, 0x00806080, 0x00a04080, - 0x00a06080, 0x008040c0, 0x008060c0, 0x00a040c0, 0x00a060c0, 0x00c00080, 0x00c02080, 0x00e00080, 0x00e02080, - 0x00c000c0, 0x00c020c0, 0x00e000c0, 0x00e020c0, 0x00c04080, 0x00c06080, 0x00e04080, 0x00e06080, 0x00c040c0, - 0x00c060c0, 0x00e040c0, 0x00e060c0, 0x00808000, 0x0080a000, 0x00a08000, 0x00a0a000, 0x00808040, 0x0080a040, - 0x00a08040, 0x00a0a040, 0x0080c000, 0x0080e000, 0x00a0c000, 0x00a0e000, 0x0080c040, 0x0080e040, 0x00a0c040, - 0x00a0e040, 0x00c08000, 0x00c0a000, 0x00e08000, 0x00e0a000, 0x00c08040, 0x00c0a040, 0x00e08040, 0x00e0a040, - 0x00c0c000, 0x00c0e000, 0x00e0c000, 0x00e0e000, 0x00c0c040, 0x00c0e040, 0x00e0c040, 0x00e0e040, 0x00808080, - 0x0080a080, 0x00a08080, 0x00a0a080, 0x008080c0, 0x0080a0c0, 0x00a080c0, 0x00a0a0c0, 0x0080c080, 0x0080e080, - 0x00a0c080, 0x00a0e080, 0x0080c0c0, 0x0080e0c0, 0x00a0c0c0, 0x00a0e0c0, 0x00c08080, 0x00c0a080, 0x00e08080, - 0x00e0a080, 0x00c080c0, 0x00c0a0c0, 0x00e080c0, 0x00e0a0c0, 0x00c0c080, 0x00c0e080, 0x00e0c080, 0x00e0e080, - 0x00c0c0c0, 0x00c0e0c0, 0x00e0c0c0, 0x00e0e0c0}, - {0x00000000, 0x00040000, 0x00000008, 0x00040008, 0x00000800, 0x00040800, 0x00000808, 0x00040808, 0x00080000, - 0x000c0000, 0x00080008, 0x000c0008, 0x00080800, 0x000c0800, 0x00080808, 0x000c0808, 0x00000010, 0x00040010, - 0x00000018, 0x00040018, 0x00000810, 0x00040810, 0x00000818, 0x00040818, 0x00080010, 0x000c0010, 0x00080018, - 0x000c0018, 0x00080810, 0x000c0810, 0x00080818, 0x000c0818, 0x00001000, 0x00041000, 0x00001008, 0x00041008, - 0x00001800, 0x00041800, 0x00001808, 0x00041808, 0x00081000, 0x000c1000, 0x00081008, 0x000c1008, 0x00081800, - 0x000c1800, 0x00081808, 0x000c1808, 0x00001010, 0x00041010, 0x00001018, 0x00041018, 0x00001810, 0x00041810, - 0x00001818, 0x00041818, 0x00081010, 0x000c1010, 0x00081018, 0x000c1018, 0x00081810, 0x000c1810, 0x00081818, - 0x000c1818, 0x00100000, 0x00140000, 0x00100008, 0x00140008, 0x00100800, 0x00140800, 0x00100808, 0x00140808, - 0x00180000, 0x001c0000, 0x00180008, 0x001c0008, 0x00180800, 0x001c0800, 0x00180808, 0x001c0808, 0x00100010, - 0x00140010, 0x00100018, 0x00140018, 0x00100810, 0x00140810, 0x00100818, 0x00140818, 0x00180010, 0x001c0010, - 0x00180018, 0x001c0018, 0x00180810, 0x001c0810, 0x00180818, 0x001c0818, 0x00101000, 0x00141000, 0x00101008, - 0x00141008, 0x00101800, 0x00141800, 0x00101808, 0x00141808, 0x00181000, 0x001c1000, 0x00181008, 0x001c1008, - 0x00181800, 0x001c1800, 0x00181808, 0x001c1808, 0x00101010, 0x00141010, 0x00101018, 0x00141018, 0x00101810, - 0x00141810, 0x00101818, 0x00141818, 0x00181010, 0x001c1010, 0x00181018, 0x001c1018, 0x00181810, 0x001c1810, - 0x00181818, 0x001c1818, 0x00000020, 0x00040020, 0x00000028, 0x00040028, 0x00000820, 0x00040820, 0x00000828, - 0x00040828, 0x00080020, 0x000c0020, 0x00080028, 0x000c0028, 0x00080820, 0x000c0820, 0x00080828, 0x000c0828, - 0x00000030, 0x00040030, 0x00000038, 0x00040038, 0x00000830, 0x00040830, 0x00000838, 0x00040838, 0x00080030, - 0x000c0030, 0x00080038, 0x000c0038, 0x00080830, 0x000c0830, 0x00080838, 0x000c0838, 0x00001020, 0x00041020, - 0x00001028, 0x00041028, 0x00001820, 0x00041820, 0x00001828, 0x00041828, 0x00081020, 0x000c1020, 0x00081028, - 0x000c1028, 0x00081820, 0x000c1820, 0x00081828, 0x000c1828, 0x00001030, 0x00041030, 0x00001038, 0x00041038, - 0x00001830, 0x00041830, 0x00001838, 0x00041838, 0x00081030, 0x000c1030, 0x00081038, 0x000c1038, 0x00081830, - 0x000c1830, 0x00081838, 0x000c1838, 0x00100020, 0x00140020, 0x00100028, 0x00140028, 0x00100820, 0x00140820, - 0x00100828, 0x00140828, 0x00180020, 0x001c0020, 0x00180028, 0x001c0028, 0x00180820, 0x001c0820, 0x00180828, - 0x001c0828, 0x00100030, 0x00140030, 0x00100038, 0x00140038, 0x00100830, 0x00140830, 0x00100838, 0x00140838, - 0x00180030, 0x001c0030, 0x00180038, 0x001c0038, 0x00180830, 0x001c0830, 0x00180838, 0x001c0838, 0x00101020, - 0x00141020, 0x00101028, 0x00141028, 0x00101820, 0x00141820, 0x00101828, 0x00141828, 0x00181020, 0x001c1020, - 0x00181028, 0x001c1028, 0x00181820, 0x001c1820, 0x00181828, 0x001c1828, 0x00101030, 0x00141030, 0x00101038, - 0x00141038, 0x00101830, 0x00141830, 0x00101838, 0x00141838, 0x00181030, 0x001c1030, 0x00181038, 0x001c1038, - 0x00181830, 0x001c1830, 0x00181838, 0x001c1838}, - {0x00000000, 0x00000001, 0x00000100, 0x00000101, 0x00010000, 0x00010001, 0x00010100, 0x00010101, 0x00000002, - 0x00000003, 0x00000102, 0x00000103, 0x00010002, 0x00010003, 0x00010102, 0x00010103, 0x00000200, 0x00000201, - 0x00000300, 0x00000301, 0x00010200, 0x00010201, 0x00010300, 0x00010301, 0x00000202, 0x00000203, 0x00000302, - 0x00000303, 0x00010202, 0x00010203, 0x00010302, 0x00010303, 0x00020000, 0x00020001, 0x00020100, 0x00020101, - 0x00030000, 0x00030001, 0x00030100, 0x00030101, 0x00020002, 0x00020003, 0x00020102, 0x00020103, 0x00030002, - 0x00030003, 0x00030102, 0x00030103, 0x00020200, 0x00020201, 0x00020300, 0x00020301, 0x00030200, 0x00030201, - 0x00030300, 0x00030301, 0x00020202, 0x00020203, 0x00020302, 0x00020303, 0x00030202, 0x00030203, 0x00030302, - 0x00030303, 0x00000004, 0x00000005, 0x00000104, 0x00000105, 0x00010004, 0x00010005, 0x00010104, 0x00010105, - 0x00000006, 0x00000007, 0x00000106, 0x00000107, 0x00010006, 0x00010007, 0x00010106, 0x00010107, 0x00000204, - 0x00000205, 0x00000304, 0x00000305, 0x00010204, 0x00010205, 0x00010304, 0x00010305, 0x00000206, 0x00000207, - 0x00000306, 0x00000307, 0x00010206, 0x00010207, 0x00010306, 0x00010307, 0x00020004, 0x00020005, 0x00020104, - 0x00020105, 0x00030004, 0x00030005, 0x00030104, 0x00030105, 0x00020006, 0x00020007, 0x00020106, 0x00020107, - 0x00030006, 0x00030007, 0x00030106, 0x00030107, 0x00020204, 0x00020205, 0x00020304, 0x00020305, 0x00030204, - 0x00030205, 0x00030304, 0x00030305, 0x00020206, 0x00020207, 0x00020306, 0x00020307, 0x00030206, 0x00030207, - 0x00030306, 0x00030307, 0x00000400, 0x00000401, 0x00000500, 0x00000501, 0x00010400, 0x00010401, 0x00010500, - 0x00010501, 0x00000402, 0x00000403, 0x00000502, 0x00000503, 0x00010402, 0x00010403, 0x00010502, 0x00010503, - 0x00000600, 0x00000601, 0x00000700, 0x00000701, 0x00010600, 0x00010601, 0x00010700, 0x00010701, 0x00000602, - 0x00000603, 0x00000702, 0x00000703, 0x00010602, 0x00010603, 0x00010702, 0x00010703, 0x00020400, 0x00020401, - 0x00020500, 0x00020501, 0x00030400, 0x00030401, 0x00030500, 0x00030501, 0x00020402, 0x00020403, 0x00020502, - 0x00020503, 0x00030402, 0x00030403, 0x00030502, 0x00030503, 0x00020600, 0x00020601, 0x00020700, 0x00020701, - 0x00030600, 0x00030601, 0x00030700, 0x00030701, 0x00020602, 0x00020603, 0x00020702, 0x00020703, 0x00030602, - 0x00030603, 0x00030702, 0x00030703, 0x00000404, 0x00000405, 0x00000504, 0x00000505, 0x00010404, 0x00010405, - 0x00010504, 0x00010505, 0x00000406, 0x00000407, 0x00000506, 0x00000507, 0x00010406, 0x00010407, 0x00010506, - 0x00010507, 0x00000604, 0x00000605, 0x00000704, 0x00000705, 0x00010604, 0x00010605, 0x00010704, 0x00010705, - 0x00000606, 0x00000607, 0x00000706, 0x00000707, 0x00010606, 0x00010607, 0x00010706, 0x00010707, 0x00020404, - 0x00020405, 0x00020504, 0x00020505, 0x00030404, 0x00030405, 0x00030504, 0x00030505, 0x00020406, 0x00020407, - 0x00020506, 0x00020507, 0x00030406, 0x00030407, 0x00030506, 0x00030507, 0x00020604, 0x00020605, 0x00020704, - 0x00020705, 0x00030604, 0x00030605, 0x00030704, 0x00030705, 0x00020606, 0x00020607, 0x00020706, 0x00020707, - 0x00030606, 0x00030607, 0x00030706, 0x00030707}}; + = {{0x00000000, 0x00002000, 0x00200000, 0x00202000, 0x00000040, 0x00002040, 0x00200040, 0x00202040, 0x00004000, + 0x00006000, 0x00204000, 0x00206000, 0x00004040, 0x00006040, 0x00204040, 0x00206040, 0x00400000, 0x00402000, + 0x00600000, 0x00602000, 0x00400040, 0x00402040, 0x00600040, 0x00602040, 0x00404000, 0x00406000, 0x00604000, + 0x00606000, 0x00404040, 0x00406040, 0x00604040, 0x00606040, 0x00000080, 0x00002080, 0x00200080, 0x00202080, + 0x000000c0, 0x000020c0, 0x002000c0, 0x002020c0, 0x00004080, 0x00006080, 0x00204080, 0x00206080, 0x000040c0, + 0x000060c0, 0x002040c0, 0x002060c0, 0x00400080, 0x00402080, 0x00600080, 0x00602080, 0x004000c0, 0x004020c0, + 0x006000c0, 0x006020c0, 0x00404080, 0x00406080, 0x00604080, 0x00606080, 0x004040c0, 0x004060c0, 0x006040c0, + 0x006060c0, 0x00008000, 0x0000a000, 0x00208000, 0x0020a000, 0x00008040, 0x0000a040, 0x00208040, 0x0020a040, + 0x0000c000, 0x0000e000, 0x0020c000, 0x0020e000, 0x0000c040, 0x0000e040, 0x0020c040, 0x0020e040, 0x00408000, + 0x0040a000, 0x00608000, 0x0060a000, 0x00408040, 0x0040a040, 0x00608040, 0x0060a040, 0x0040c000, 0x0040e000, + 0x0060c000, 0x0060e000, 0x0040c040, 0x0040e040, 0x0060c040, 0x0060e040, 0x00008080, 0x0000a080, 0x00208080, + 0x0020a080, 0x000080c0, 0x0000a0c0, 0x002080c0, 0x0020a0c0, 0x0000c080, 0x0000e080, 0x0020c080, 0x0020e080, + 0x0000c0c0, 0x0000e0c0, 0x0020c0c0, 0x0020e0c0, 0x00408080, 0x0040a080, 0x00608080, 0x0060a080, 0x004080c0, + 0x0040a0c0, 0x006080c0, 0x0060a0c0, 0x0040c080, 0x0040e080, 0x0060c080, 0x0060e080, 0x0040c0c0, 0x0040e0c0, + 0x0060c0c0, 0x0060e0c0, 0x00800000, 0x00802000, 0x00a00000, 0x00a02000, 0x00800040, 0x00802040, 0x00a00040, + 0x00a02040, 0x00804000, 0x00806000, 0x00a04000, 0x00a06000, 0x00804040, 0x00806040, 0x00a04040, 0x00a06040, + 0x00c00000, 0x00c02000, 0x00e00000, 0x00e02000, 0x00c00040, 0x00c02040, 0x00e00040, 0x00e02040, 0x00c04000, + 0x00c06000, 0x00e04000, 0x00e06000, 0x00c04040, 0x00c06040, 0x00e04040, 0x00e06040, 0x00800080, 0x00802080, + 0x00a00080, 0x00a02080, 0x008000c0, 0x008020c0, 0x00a000c0, 0x00a020c0, 0x00804080, 0x00806080, 0x00a04080, + 0x00a06080, 0x008040c0, 0x008060c0, 0x00a040c0, 0x00a060c0, 0x00c00080, 0x00c02080, 0x00e00080, 0x00e02080, + 0x00c000c0, 0x00c020c0, 0x00e000c0, 0x00e020c0, 0x00c04080, 0x00c06080, 0x00e04080, 0x00e06080, 0x00c040c0, + 0x00c060c0, 0x00e040c0, 0x00e060c0, 0x00808000, 0x0080a000, 0x00a08000, 0x00a0a000, 0x00808040, 0x0080a040, + 0x00a08040, 0x00a0a040, 0x0080c000, 0x0080e000, 0x00a0c000, 0x00a0e000, 0x0080c040, 0x0080e040, 0x00a0c040, + 0x00a0e040, 0x00c08000, 0x00c0a000, 0x00e08000, 0x00e0a000, 0x00c08040, 0x00c0a040, 0x00e08040, 0x00e0a040, + 0x00c0c000, 0x00c0e000, 0x00e0c000, 0x00e0e000, 0x00c0c040, 0x00c0e040, 0x00e0c040, 0x00e0e040, 0x00808080, + 0x0080a080, 0x00a08080, 0x00a0a080, 0x008080c0, 0x0080a0c0, 0x00a080c0, 0x00a0a0c0, 0x0080c080, 0x0080e080, + 0x00a0c080, 0x00a0e080, 0x0080c0c0, 0x0080e0c0, 0x00a0c0c0, 0x00a0e0c0, 0x00c08080, 0x00c0a080, 0x00e08080, + 0x00e0a080, 0x00c080c0, 0x00c0a0c0, 0x00e080c0, 0x00e0a0c0, 0x00c0c080, 0x00c0e080, 0x00e0c080, 0x00e0e080, + 0x00c0c0c0, 0x00c0e0c0, 0x00e0c0c0, 0x00e0e0c0}, + {0x00000000, 0x00040000, 0x00000008, 0x00040008, 0x00000800, 0x00040800, 0x00000808, 0x00040808, 0x00080000, + 0x000c0000, 0x00080008, 0x000c0008, 0x00080800, 0x000c0800, 0x00080808, 0x000c0808, 0x00000010, 0x00040010, + 0x00000018, 0x00040018, 0x00000810, 0x00040810, 0x00000818, 0x00040818, 0x00080010, 0x000c0010, 0x00080018, + 0x000c0018, 0x00080810, 0x000c0810, 0x00080818, 0x000c0818, 0x00001000, 0x00041000, 0x00001008, 0x00041008, + 0x00001800, 0x00041800, 0x00001808, 0x00041808, 0x00081000, 0x000c1000, 0x00081008, 0x000c1008, 0x00081800, + 0x000c1800, 0x00081808, 0x000c1808, 0x00001010, 0x00041010, 0x00001018, 0x00041018, 0x00001810, 0x00041810, + 0x00001818, 0x00041818, 0x00081010, 0x000c1010, 0x00081018, 0x000c1018, 0x00081810, 0x000c1810, 0x00081818, + 0x000c1818, 0x00100000, 0x00140000, 0x00100008, 0x00140008, 0x00100800, 0x00140800, 0x00100808, 0x00140808, + 0x00180000, 0x001c0000, 0x00180008, 0x001c0008, 0x00180800, 0x001c0800, 0x00180808, 0x001c0808, 0x00100010, + 0x00140010, 0x00100018, 0x00140018, 0x00100810, 0x00140810, 0x00100818, 0x00140818, 0x00180010, 0x001c0010, + 0x00180018, 0x001c0018, 0x00180810, 0x001c0810, 0x00180818, 0x001c0818, 0x00101000, 0x00141000, 0x00101008, + 0x00141008, 0x00101800, 0x00141800, 0x00101808, 0x00141808, 0x00181000, 0x001c1000, 0x00181008, 0x001c1008, + 0x00181800, 0x001c1800, 0x00181808, 0x001c1808, 0x00101010, 0x00141010, 0x00101018, 0x00141018, 0x00101810, + 0x00141810, 0x00101818, 0x00141818, 0x00181010, 0x001c1010, 0x00181018, 0x001c1018, 0x00181810, 0x001c1810, + 0x00181818, 0x001c1818, 0x00000020, 0x00040020, 0x00000028, 0x00040028, 0x00000820, 0x00040820, 0x00000828, + 0x00040828, 0x00080020, 0x000c0020, 0x00080028, 0x000c0028, 0x00080820, 0x000c0820, 0x00080828, 0x000c0828, + 0x00000030, 0x00040030, 0x00000038, 0x00040038, 0x00000830, 0x00040830, 0x00000838, 0x00040838, 0x00080030, + 0x000c0030, 0x00080038, 0x000c0038, 0x00080830, 0x000c0830, 0x00080838, 0x000c0838, 0x00001020, 0x00041020, + 0x00001028, 0x00041028, 0x00001820, 0x00041820, 0x00001828, 0x00041828, 0x00081020, 0x000c1020, 0x00081028, + 0x000c1028, 0x00081820, 0x000c1820, 0x00081828, 0x000c1828, 0x00001030, 0x00041030, 0x00001038, 0x00041038, + 0x00001830, 0x00041830, 0x00001838, 0x00041838, 0x00081030, 0x000c1030, 0x00081038, 0x000c1038, 0x00081830, + 0x000c1830, 0x00081838, 0x000c1838, 0x00100020, 0x00140020, 0x00100028, 0x00140028, 0x00100820, 0x00140820, + 0x00100828, 0x00140828, 0x00180020, 0x001c0020, 0x00180028, 0x001c0028, 0x00180820, 0x001c0820, 0x00180828, + 0x001c0828, 0x00100030, 0x00140030, 0x00100038, 0x00140038, 0x00100830, 0x00140830, 0x00100838, 0x00140838, + 0x00180030, 0x001c0030, 0x00180038, 0x001c0038, 0x00180830, 0x001c0830, 0x00180838, 0x001c0838, 0x00101020, + 0x00141020, 0x00101028, 0x00141028, 0x00101820, 0x00141820, 0x00101828, 0x00141828, 0x00181020, 0x001c1020, + 0x00181028, 0x001c1028, 0x00181820, 0x001c1820, 0x00181828, 0x001c1828, 0x00101030, 0x00141030, 0x00101038, + 0x00141038, 0x00101830, 0x00141830, 0x00101838, 0x00141838, 0x00181030, 0x001c1030, 0x00181038, 0x001c1038, + 0x00181830, 0x001c1830, 0x00181838, 0x001c1838}, + {0x00000000, 0x00000001, 0x00000100, 0x00000101, 0x00010000, 0x00010001, 0x00010100, 0x00010101, 0x00000002, + 0x00000003, 0x00000102, 0x00000103, 0x00010002, 0x00010003, 0x00010102, 0x00010103, 0x00000200, 0x00000201, + 0x00000300, 0x00000301, 0x00010200, 0x00010201, 0x00010300, 0x00010301, 0x00000202, 0x00000203, 0x00000302, + 0x00000303, 0x00010202, 0x00010203, 0x00010302, 0x00010303, 0x00020000, 0x00020001, 0x00020100, 0x00020101, + 0x00030000, 0x00030001, 0x00030100, 0x00030101, 0x00020002, 0x00020003, 0x00020102, 0x00020103, 0x00030002, + 0x00030003, 0x00030102, 0x00030103, 0x00020200, 0x00020201, 0x00020300, 0x00020301, 0x00030200, 0x00030201, + 0x00030300, 0x00030301, 0x00020202, 0x00020203, 0x00020302, 0x00020303, 0x00030202, 0x00030203, 0x00030302, + 0x00030303, 0x00000004, 0x00000005, 0x00000104, 0x00000105, 0x00010004, 0x00010005, 0x00010104, 0x00010105, + 0x00000006, 0x00000007, 0x00000106, 0x00000107, 0x00010006, 0x00010007, 0x00010106, 0x00010107, 0x00000204, + 0x00000205, 0x00000304, 0x00000305, 0x00010204, 0x00010205, 0x00010304, 0x00010305, 0x00000206, 0x00000207, + 0x00000306, 0x00000307, 0x00010206, 0x00010207, 0x00010306, 0x00010307, 0x00020004, 0x00020005, 0x00020104, + 0x00020105, 0x00030004, 0x00030005, 0x00030104, 0x00030105, 0x00020006, 0x00020007, 0x00020106, 0x00020107, + 0x00030006, 0x00030007, 0x00030106, 0x00030107, 0x00020204, 0x00020205, 0x00020304, 0x00020305, 0x00030204, + 0x00030205, 0x00030304, 0x00030305, 0x00020206, 0x00020207, 0x00020306, 0x00020307, 0x00030206, 0x00030207, + 0x00030306, 0x00030307, 0x00000400, 0x00000401, 0x00000500, 0x00000501, 0x00010400, 0x00010401, 0x00010500, + 0x00010501, 0x00000402, 0x00000403, 0x00000502, 0x00000503, 0x00010402, 0x00010403, 0x00010502, 0x00010503, + 0x00000600, 0x00000601, 0x00000700, 0x00000701, 0x00010600, 0x00010601, 0x00010700, 0x00010701, 0x00000602, + 0x00000603, 0x00000702, 0x00000703, 0x00010602, 0x00010603, 0x00010702, 0x00010703, 0x00020400, 0x00020401, + 0x00020500, 0x00020501, 0x00030400, 0x00030401, 0x00030500, 0x00030501, 0x00020402, 0x00020403, 0x00020502, + 0x00020503, 0x00030402, 0x00030403, 0x00030502, 0x00030503, 0x00020600, 0x00020601, 0x00020700, 0x00020701, + 0x00030600, 0x00030601, 0x00030700, 0x00030701, 0x00020602, 0x00020603, 0x00020702, 0x00020703, 0x00030602, + 0x00030603, 0x00030702, 0x00030703, 0x00000404, 0x00000405, 0x00000504, 0x00000505, 0x00010404, 0x00010405, + 0x00010504, 0x00010505, 0x00000406, 0x00000407, 0x00000506, 0x00000507, 0x00010406, 0x00010407, 0x00010506, + 0x00010507, 0x00000604, 0x00000605, 0x00000704, 0x00000705, 0x00010604, 0x00010605, 0x00010704, 0x00010705, + 0x00000606, 0x00000607, 0x00000706, 0x00000707, 0x00010606, 0x00010607, 0x00010706, 0x00010707, 0x00020404, + 0x00020405, 0x00020504, 0x00020505, 0x00030404, 0x00030405, 0x00030504, 0x00030505, 0x00020406, 0x00020407, + 0x00020506, 0x00020507, 0x00030406, 0x00030407, 0x00030506, 0x00030507, 0x00020604, 0x00020605, 0x00020704, + 0x00020705, 0x00030604, 0x00030605, 0x00030704, 0x00030705, 0x00020606, 0x00020607, 0x00020706, 0x00020707, + 0x00030606, 0x00030607, 0x00030706, 0x00030707}}; } // namespace output \ No newline at end of file diff --git a/filters/output/ColorPickupInteraction.h b/filters/output/ColorPickupInteraction.h index 6883509ab..578899ee7 100644 --- a/filters/output/ColorPickupInteraction.h +++ b/filters/output/ColorPickupInteraction.h @@ -19,55 +19,55 @@ #ifndef OUTPUT_COLOR_PICKUP_INTERACTION_H_ #define OUTPUT_COLOR_PICKUP_INTERACTION_H_ -#include "InteractionHandler.h" -#include "InteractionState.h" -#include "EditableZoneSet.h" -#include "FillColorProperty.h" -#include "intrusive_ptr.h" #include #include #include +#include "EditableZoneSet.h" +#include "FillColorProperty.h" +#include "InteractionHandler.h" +#include "InteractionState.h" +#include "intrusive_ptr.h" class ZoneInteractionContext; namespace output { class ColorPickupInteraction : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ColorPickupInteraction) -public: - ColorPickupInteraction(EditableZoneSet& zones, ZoneInteractionContext& context); + Q_DECLARE_TR_FUNCTIONS(ColorPickupInteraction) + public: + ColorPickupInteraction(EditableZoneSet& zones, ZoneInteractionContext& context); - void startInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction); + void startInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction); - bool isActive(const InteractionState& interaction) const; + bool isActive(const InteractionState& interaction) const; -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; -private: - void takeColor(); + private: + void takeColor(); - QRect targetBoundingRect() const; + QRect targetBoundingRect() const; - void switchToDefaultInteraction(); + void switchToDefaultInteraction(); - static uint32_t bitMixColor(uint32_t color); + static uint32_t bitMixColor(uint32_t color); - static uint32_t bitUnmixColor(uint32_t mixed); + static uint32_t bitUnmixColor(uint32_t mixed); - EditableZoneSet& m_rZones; - ZoneInteractionContext& m_rContext; - InteractionState::Captor m_interaction; - intrusive_ptr m_ptrFillColorProp; - int m_dontDrawCircle; + EditableZoneSet& m_zones; + ZoneInteractionContext& m_context; + InteractionState::Captor m_interaction; + intrusive_ptr m_fillColorProp; + int m_dontDrawCircle; - static const uint32_t m_sBitMixingLUT[3][256]; - static const uint32_t m_sBitUnmixingLUT[3][256]; + static const uint32_t m_sBitMixingLUT[3][256]; + static const uint32_t m_sBitUnmixingLUT[3][256]; }; } // namespace output #endif // ifndef OUTPUT_COLOR_PICKUP_INTERACTION_H_ diff --git a/filters/output/DepthPerception.cpp b/filters/output/DepthPerception.cpp index 4d5e8b8bb..11287acb3 100644 --- a/filters/output/DepthPerception.cpp +++ b/filters/output/DepthPerception.cpp @@ -20,43 +20,41 @@ #include "../../Utils.h" namespace output { -DepthPerception::DepthPerception() : m_value(defaultValue()) { -} +DepthPerception::DepthPerception() : m_value(defaultValue()) {} -DepthPerception::DepthPerception(double value) : m_value(qBound(minValue(), value, maxValue())) { -} +DepthPerception::DepthPerception(double value) : m_value(qBound(minValue(), value, maxValue())) {} DepthPerception::DepthPerception(const QString& from_string) { - bool ok = false; - m_value = from_string.toDouble(&ok); - if (!ok) { - m_value = defaultValue(); - } else { - m_value = qBound(minValue(), m_value, maxValue()); - } + bool ok = false; + m_value = from_string.toDouble(&ok); + if (!ok) { + m_value = defaultValue(); + } else { + m_value = qBound(minValue(), m_value, maxValue()); + } } QString DepthPerception::toString() const { - return Utils::doubleToString(m_value); + return Utils::doubleToString(m_value); } void DepthPerception::setValue(double value) { - m_value = qBound(minValue(), value, maxValue()); + m_value = qBound(minValue(), value, maxValue()); } double DepthPerception::value() const { - return m_value; + return m_value; } double DepthPerception::minValue() { - return 1.0; + return 1.0; } double DepthPerception::defaultValue() { - return 2.0; + return 2.0; } double DepthPerception::maxValue() { - return 3.0; + return 3.0; } } // namespace output \ No newline at end of file diff --git a/filters/output/DepthPerception.h b/filters/output/DepthPerception.h index d366b1160..cbcd1793f 100644 --- a/filters/output/DepthPerception.h +++ b/filters/output/DepthPerception.h @@ -26,27 +26,27 @@ namespace output { * \see imageproc::CylindricalSurfaceDewarper */ class DepthPerception { -public: - DepthPerception(); + public: + DepthPerception(); - explicit DepthPerception(double value); + explicit DepthPerception(double value); - explicit DepthPerception(const QString& from_string); + explicit DepthPerception(const QString& from_string); - QString toString() const; + QString toString() const; - void setValue(double value); + void setValue(double value); - double value() const; + double value() const; - static double minValue(); + static double minValue(); - static double defaultValue(); + static double defaultValue(); - static double maxValue(); + static double maxValue(); -private: - double m_value; + private: + double m_value; }; } // namespace output #endif // ifndef OUTPUT_DEPTH_PERCEPTION_H_ diff --git a/filters/output/DespeckleLevel.cpp b/filters/output/DespeckleLevel.cpp index 02f11768e..f5edbbe7a 100644 --- a/filters/output/DespeckleLevel.cpp +++ b/filters/output/DespeckleLevel.cpp @@ -21,29 +21,29 @@ namespace output { QString despeckleLevelToString(const DespeckleLevel level) { - switch (level) { - case DESPECKLE_OFF: - return "off"; - case DESPECKLE_CAUTIOUS: - return "cautious"; - case DESPECKLE_NORMAL: - return "normal"; - case DESPECKLE_AGGRESSIVE: - return "aggressive"; - } + switch (level) { + case DESPECKLE_OFF: + return "off"; + case DESPECKLE_CAUTIOUS: + return "cautious"; + case DESPECKLE_NORMAL: + return "normal"; + case DESPECKLE_AGGRESSIVE: + return "aggressive"; + } - return QString(); + return QString(); } DespeckleLevel despeckleLevelFromString(const QString& str) { - if (str == "off") { - return DESPECKLE_OFF; - } else if (str == "cautious") { - return DESPECKLE_CAUTIOUS; - } else if (str == "aggressive") { - return DESPECKLE_AGGRESSIVE; - } else { - return DESPECKLE_NORMAL; - } + if (str == "off") { + return DESPECKLE_OFF; + } else if (str == "cautious") { + return DESPECKLE_CAUTIOUS; + } else if (str == "aggressive") { + return DESPECKLE_AGGRESSIVE; + } else { + return DESPECKLE_NORMAL; + } } } // namespace output \ No newline at end of file diff --git a/filters/output/DespeckleState.cpp b/filters/output/DespeckleState.cpp index 453eccd53..46f777016 100644 --- a/filters/output/DespeckleState.cpp +++ b/filters/output/DespeckleState.cpp @@ -17,10 +17,10 @@ */ #include "DespeckleState.h" -#include "DespeckleVisualization.h" +#include "DebugImages.h" #include "Despeckle.h" +#include "DespeckleVisualization.h" #include "TaskStatus.h" -#include "DebugImages.h" #include "imageproc/RasterOp.h" using namespace imageproc; @@ -28,86 +28,73 @@ using namespace imageproc; namespace output { DespeckleState::DespeckleState(const QImage& output, const imageproc::BinaryImage& speckles, - DespeckleLevel level, + const double level, const Dpi& dpi) - : m_speckles(speckles), m_dpi(dpi), m_despeckleLevel(level) { - m_everythingMixed = overlaySpeckles(output, speckles); - m_everythingBW = extractBW(m_everythingMixed); + : m_speckles(speckles), m_dpi(dpi), m_despeckleLevel(level) { + m_everythingMixed = overlaySpeckles(output, speckles); + m_everythingBW = extractBW(m_everythingMixed); } DespeckleVisualization DespeckleState::visualize() const { - return DespeckleVisualization(m_everythingMixed, m_speckles, m_dpi); + return DespeckleVisualization(m_everythingMixed, m_speckles, m_dpi); } -DespeckleState DespeckleState::redespeckle(const DespeckleLevel level, - const TaskStatus& status, - DebugImages* dbg) const { - DespeckleState new_state(*this); +DespeckleState DespeckleState::redespeckle(const double level, const TaskStatus& status, DebugImages* dbg) const { + DespeckleState new_state(*this); - if (level == m_despeckleLevel) { - return new_state; - } + if (level == m_despeckleLevel) { + return new_state; + } - new_state.m_despeckleLevel = level; - - Despeckle::Level level2 = Despeckle::NORMAL; - switch (level) { - case DESPECKLE_OFF: - // Null speckles image is equivalent to a white one. - new_state.m_speckles.release(); - - return new_state; - case DESPECKLE_CAUTIOUS: - level2 = Despeckle::CAUTIOUS; - break; - case DESPECKLE_NORMAL: - level2 = Despeckle::NORMAL; - break; - case DESPECKLE_AGGRESSIVE: - level2 = Despeckle::AGGRESSIVE; - break; - } + new_state.m_despeckleLevel = level; - new_state.m_speckles = Despeckle::despeckle(m_everythingBW, m_dpi, level2, status, dbg); + if (level == 0) { + // Null speckles image is equivalent to a white one. + new_state.m_speckles.release(); - status.throwIfCancelled(); + return new_state; + } - rasterOp>(new_state.m_speckles, m_everythingBW); + new_state.m_speckles = Despeckle::despeckle(m_everythingBW, m_dpi, level, status, dbg); - return new_state; + status.throwIfCancelled(); + + rasterOp>(new_state.m_speckles, m_everythingBW); + + return new_state; } // DespeckleState::redespeckle QImage DespeckleState::overlaySpeckles(const QImage& mixed, const imageproc::BinaryImage& speckles) { - QImage result(mixed.convertToFormat(QImage::Format_RGB32)); - if (result.isNull() && !mixed.isNull()) { - throw std::bad_alloc(); - } + QImage result(mixed.convertToFormat(QImage::Format_RGB32)); + if (result.isNull() && !mixed.isNull()) { + throw std::bad_alloc(); + } - if (speckles.isNull()) { - return result; - } + if (speckles.isNull()) { + return result; + } - auto* result_line = (uint32_t*) result.bits(); - const int result_stride = result.bytesPerLine() / 4; + auto* result_line = (uint32_t*) result.bits(); + const int result_stride = result.bytesPerLine() / 4; - const uint32_t* speckles_line = speckles.data(); - const int speckles_stride = speckles.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; + const uint32_t* speckles_line = speckles.data(); + const int speckles_stride = speckles.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; - const int width = result.width(); - const int height = result.height(); + const int width = result.width(); + const int height = result.height(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (speckles_line[x >> 5] & (msb >> (x & 31))) { - result_line[x] = 0xff000000; // opaque black - } - } - result_line += result_stride; - speckles_line += speckles_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (speckles_line[x >> 5] & (msb >> (x & 31))) { + result_line[x] = 0xff000000; // opaque black + } } + result_line += result_stride; + speckles_line += speckles_stride; + } - return result; + return result; } // DespeckleState::overlaySpeckles /** @@ -116,32 +103,32 @@ QImage DespeckleState::overlaySpeckles(const QImage& mixed, const imageproc::Bin * generating output files. */ BinaryImage DespeckleState::extractBW(const QImage& mixed) { - BinaryImage result(mixed.size(), WHITE); - - const auto* mixed_line = (const uint32_t*) mixed.bits(); - const int mixed_stride = mixed.bytesPerLine() / 4; - - uint32_t* result_line = result.data(); - const int result_stride = result.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - const int width = result.width(); - const int height = result.height(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mixed_line[x] == 0xff000000) { - result_line[x >> 5] |= msb >> (x & 31); - } - } - mixed_line += mixed_stride; - result_line += result_stride; + BinaryImage result(mixed.size(), WHITE); + + const auto* mixed_line = (const uint32_t*) mixed.bits(); + const int mixed_stride = mixed.bytesPerLine() / 4; + + uint32_t* result_line = result.data(); + const int result_stride = result.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + const int width = result.width(); + const int height = result.height(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mixed_line[x] == 0xff000000) { + result_line[x >> 5] |= msb >> (x & 31); + } } + mixed_line += mixed_stride; + result_line += result_stride; + } - return result; + return result; } -DespeckleLevel DespeckleState::level() const { - return m_despeckleLevel; +double DespeckleState::level() const { + return m_despeckleLevel; } } // namespace output \ No newline at end of file diff --git a/filters/output/DespeckleState.h b/filters/output/DespeckleState.h index 00984b20b..fdb06f62e 100644 --- a/filters/output/DespeckleState.h +++ b/filters/output/DespeckleState.h @@ -20,10 +20,10 @@ #ifndef OUTPUT_DESPECKLE_STATE_H_ #define OUTPUT_DESPECKLE_STATE_H_ +#include #include "DespeckleLevel.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" -#include class TaskStatus; class DebugImages; @@ -36,50 +36,50 @@ class DespeckleVisualization; * or to re-despeckle with different DespeckleLevel. */ class DespeckleState { - // Member-wise copying is OK. -public: - DespeckleState(const QImage& output, const imageproc::BinaryImage& speckles, DespeckleLevel level, const Dpi& dpi); - - DespeckleLevel level() const; - - DespeckleVisualization visualize() const; - - DespeckleState redespeckle(DespeckleLevel level, const TaskStatus& status, DebugImages* dbg = nullptr) const; - -private: - static QImage overlaySpeckles(const QImage& mixed, const imageproc::BinaryImage& speckles); - - static imageproc::BinaryImage extractBW(const QImage& mixed); - - /** - * This image is the output image produced by OutputGenerator - * with speckles added as black regions. This image is always in RGB32, - * because it only exists for display purposes, namely for being fed to - * DespeckleVisualization. - */ - QImage m_everythingMixed; - - /** - * The B/W part of m_everythingMixed. - */ - imageproc::BinaryImage m_everythingBW; - - /** - * The speckles detected in m_everythingBW. - * This image may be null, which is equivalent to having it all white. - */ - imageproc::BinaryImage m_speckles; - - /** - * The DPI of all 3 above images. - */ - Dpi m_dpi; - - /** - * Despeckling level at which m_speckles was produced from - * m_everythingBW. - */ - DespeckleLevel m_despeckleLevel; + // Member-wise copying is OK. + public: + DespeckleState(const QImage& output, const imageproc::BinaryImage& speckles, double level, const Dpi& dpi); + + double level() const; + + DespeckleVisualization visualize() const; + + DespeckleState redespeckle(double level, const TaskStatus& status, DebugImages* dbg = nullptr) const; + + private: + static QImage overlaySpeckles(const QImage& mixed, const imageproc::BinaryImage& speckles); + + static imageproc::BinaryImage extractBW(const QImage& mixed); + + /** + * This image is the output image produced by OutputGenerator + * with speckles added as black regions. This image is always in RGB32, + * because it only exists for display purposes, namely for being fed to + * DespeckleVisualization. + */ + QImage m_everythingMixed; + + /** + * The B/W part of m_everythingMixed. + */ + imageproc::BinaryImage m_everythingBW; + + /** + * The speckles detected in m_everythingBW. + * This image may be null, which is equivalent to having it all white. + */ + imageproc::BinaryImage m_speckles; + + /** + * The DPI of all 3 above images. + */ + Dpi m_dpi; + + /** + * Despeckling level at which m_speckles was produced from + * m_everythingBW. + */ + double m_despeckleLevel; }; } // namespace output #endif // ifndef OUTPUT_DESPECKLE_STATE_H_ diff --git a/filters/output/DespeckleView.cpp b/filters/output/DespeckleView.cpp index 33f00d4f5..56297292d 100644 --- a/filters/output/DespeckleView.cpp +++ b/filters/output/DespeckleView.cpp @@ -17,81 +17,79 @@ */ #include "DespeckleView.h" -#include "DespeckleVisualization.h" -#include "Despeckle.h" +#include +#include +#include #include "AbstractCommand.h" #include "BackgroundExecutor.h" #include "BackgroundTask.h" -#include "ImageViewBase.h" #include "BasicImageView.h" +#include "DebugImages.h" +#include "Despeckle.h" +#include "DespeckleVisualization.h" +#include "ImageViewBase.h" #include "OutputMargins.h" #include "ProcessingIndicationWidget.h" -#include "DebugImages.h" #include "TabbedDebugImages.h" -#include -#include -#include using namespace imageproc; namespace output { class DespeckleView::TaskCancelException : public std::exception { -public: - const char* what() const throw() override { - return "Task cancelled"; - } + public: + const char* what() const noexcept override { return "Task cancelled"; } }; class DespeckleView::TaskCancelHandle : public TaskStatus, public ref_countable { -public: - void cancel() override; + public: + void cancel() override; - bool isCancelled() const override; + bool isCancelled() const override; - void throwIfCancelled() const override; + void throwIfCancelled() const override; -private: - mutable QAtomicInt m_cancelFlag; + private: + mutable QAtomicInt m_cancelFlag; }; class DespeckleView::DespeckleTask : public AbstractCommand { -public: - DespeckleTask(DespeckleView* owner, - const DespeckleState& despeckle_state, - intrusive_ptr cancel_handle, - DespeckleLevel level, - bool debug); - - BackgroundExecutor::TaskResultPtr operator()() override; - -private: - QPointer m_ptrOwner; - DespeckleState m_despeckleState; - intrusive_ptr m_ptrCancelHandle; - std::unique_ptr m_ptrDbg; - DespeckleLevel m_despeckleLevel; + public: + DespeckleTask(DespeckleView* owner, + const DespeckleState& despeckle_state, + intrusive_ptr cancel_handle, + double level, + bool debug); + + BackgroundExecutor::TaskResultPtr operator()() override; + + private: + QPointer m_owner; + DespeckleState m_despeckleState; + intrusive_ptr m_cancelHandle; + std::unique_ptr m_dbg; + double m_despeckleLevel; }; class DespeckleView::DespeckleResult : public AbstractCommand { -public: - DespeckleResult(QPointer owner, - intrusive_ptr cancel_handle, - const DespeckleState& despeckle_state, - const DespeckleVisualization& visualization, - std::unique_ptr debug_images); - - // This method is called from the main thread. - void operator()() override; - -private: - QPointer m_ptrOwner; - intrusive_ptr m_ptrCancelHandle; - std::unique_ptr m_ptrDbg; - DespeckleState m_despeckleState; - DespeckleVisualization m_visualization; + public: + DespeckleResult(QPointer owner, + intrusive_ptr cancel_handle, + const DespeckleState& despeckle_state, + const DespeckleVisualization& visualization, + std::unique_ptr debug_images); + + // This method is called from the main thread. + void operator()() override; + + private: + QPointer m_owner; + intrusive_ptr m_cancelHandle; + std::unique_ptr m_dbg; + DespeckleState m_despeckleState; + DespeckleVisualization m_visualization; }; @@ -100,117 +98,116 @@ class DespeckleView::DespeckleResult : public AbstractCommand { DespeckleView::DespeckleView(const DespeckleState& despeckle_state, const DespeckleVisualization& visualization, bool debug) - : m_despeckleState(despeckle_state), - m_pProcessingIndicator(new ProcessingIndicationWidget(this)), - m_despeckleLevel(despeckle_state.level()), - m_debug(debug) { - addWidget(m_pProcessingIndicator); - - if (!visualization.isNull()) { - // Create the image view. - std::unique_ptr widget(new BasicImageView(visualization.image(), visualization.downscaledImage())); - setCurrentIndex(addWidget(widget.release())); - emit imageViewCreated(dynamic_cast(widget.get())); - } + : m_despeckleState(despeckle_state), + m_processingIndicator(new ProcessingIndicationWidget(this)), + m_despeckleLevel(despeckle_state.level()), + m_debug(debug) { + addWidget(m_processingIndicator); + + if (!visualization.isNull()) { + // Create the image view. + std::unique_ptr widget(new BasicImageView(visualization.image(), visualization.downscaledImage())); + setCurrentIndex(addWidget(widget.release())); + emit imageViewCreated(dynamic_cast(widget.get())); + } } DespeckleView::~DespeckleView() { - cancelBackgroundTask(); + cancelBackgroundTask(); } -void DespeckleView::despeckleLevelChanged(const DespeckleLevel new_level, bool* handled) { - if (new_level == m_despeckleLevel) { - return; - } +void DespeckleView::despeckleLevelChanged(const double new_level, bool* handled) { + if (new_level == m_despeckleLevel) { + return; + } - m_despeckleLevel = new_level; + m_despeckleLevel = new_level; - if (isVisible()) { - *handled = true; - if (currentWidget() == m_pProcessingIndicator) { - initiateDespeckling(RESUME_ANIMATION); - } else { - initiateDespeckling(RESET_ANIMATION); - } + if (isVisible()) { + *handled = true; + if (currentWidget() == m_processingIndicator) { + initiateDespeckling(RESUME_ANIMATION); + } else { + initiateDespeckling(RESET_ANIMATION); } + } } void DespeckleView::hideEvent(QHideEvent* evt) { - QStackedWidget::hideEvent(evt); - // We don't want background despeckling to continue when user - // switches to another tab. - cancelBackgroundTask(); + QStackedWidget::hideEvent(evt); + // We don't want background despeckling to continue when user + // switches to another tab. + cancelBackgroundTask(); } void DespeckleView::showEvent(QShowEvent* evt) { - QStackedWidget::showEvent(evt); + QStackedWidget::showEvent(evt); - if (currentWidget() == m_pProcessingIndicator) { - initiateDespeckling(RESET_ANIMATION); - } + if (currentWidget() == m_processingIndicator) { + initiateDespeckling(RESET_ANIMATION); + } } void DespeckleView::initiateDespeckling(const AnimationAction anim_action) { - removeImageViewWidget(); - if (anim_action == RESET_ANIMATION) { - m_pProcessingIndicator->resetAnimation(); - } else { - m_pProcessingIndicator->processingRestartedEffect(); - } + removeImageViewWidget(); + if (anim_action == RESET_ANIMATION) { + m_processingIndicator->resetAnimation(); + } else { + m_processingIndicator->processingRestartedEffect(); + } - cancelBackgroundTask(); - m_ptrCancelHandle.reset(new TaskCancelHandle); + cancelBackgroundTask(); + m_cancelHandle.reset(new TaskCancelHandle); - // Note that we are getting rid of m_initialSpeckles, - // as we wouldn't need it any more. + // Note that we are getting rid of m_initialSpeckles, + // as we wouldn't need it any more. - const auto task - = make_intrusive(this, m_despeckleState, m_ptrCancelHandle, m_despeckleLevel, m_debug); - ImageViewBase::backgroundExecutor().enqueueTask(task); + const auto task = make_intrusive(this, m_despeckleState, m_cancelHandle, m_despeckleLevel, m_debug); + ImageViewBase::backgroundExecutor().enqueueTask(task); } void DespeckleView::despeckleDone(const DespeckleState& despeckle_state, const DespeckleVisualization& visualization, DebugImages* dbg) { - assert(!visualization.isNull()); + assert(!visualization.isNull()); - m_despeckleState = despeckle_state; + m_despeckleState = despeckle_state; - removeImageViewWidget(); + removeImageViewWidget(); - std::unique_ptr widget( - new BasicImageView(visualization.image(), visualization.downscaledImage(), OutputMargins())); + std::unique_ptr widget( + new BasicImageView(visualization.image(), visualization.downscaledImage(), OutputMargins())); - if (dbg && !dbg->empty()) { - auto tab_widget = std::make_unique(); - tab_widget->addTab(widget.release(), "Main"); - AutoRemovingFile file; - QString label; - while (!(file = dbg->retrieveNext(&label)).get().isNull()) { - tab_widget->addTab(new DebugImageView(file), label); - } - widget = std::move(tab_widget); + if (dbg && !dbg->empty()) { + auto tab_widget = std::make_unique(); + tab_widget->addTab(widget.release(), "Main"); + AutoRemovingFile file; + QString label; + while (!(file = dbg->retrieveNext(&label)).get().isNull()) { + tab_widget->addTab(new DebugImageView(file), label); } + widget = std::move(tab_widget); + } - setCurrentIndex(addWidget(widget.release())); - emit imageViewCreated(dynamic_cast(widget.get())); + setCurrentIndex(addWidget(widget.release())); + emit imageViewCreated(dynamic_cast(widget.get())); } void DespeckleView::cancelBackgroundTask() { - if (m_ptrCancelHandle) { - m_ptrCancelHandle->cancel(); - m_ptrCancelHandle.reset(); - } + if (m_cancelHandle) { + m_cancelHandle->cancel(); + m_cancelHandle.reset(); + } } void DespeckleView::removeImageViewWidget() { - // Widget 0 is always m_pProcessingIndicator, so we start with 1. - // Also, normally there can't be more than 2 widgets, but just in case ... - while (count() > 1) { - QWidget* wgt = widget(1); - removeWidget(wgt); - delete wgt; - } + // Widget 0 is always m_processingIndicator, so we start with 1. + // Also, normally there can't be more than 2 widgets, but just in case ... + while (count() > 1) { + QWidget* wgt = widget(1); + removeWidget(wgt); + delete wgt; + } } /*============================= DespeckleTask ==========================*/ @@ -218,34 +215,33 @@ void DespeckleView::removeImageViewWidget() { DespeckleView::DespeckleTask::DespeckleTask(DespeckleView* owner, const DespeckleState& despeckle_state, intrusive_ptr cancel_handle, - const DespeckleLevel level, + const double level, const bool debug) - : m_ptrOwner(owner), - m_despeckleState(despeckle_state), - m_ptrCancelHandle(std::move(cancel_handle)), - m_despeckleLevel(level) { - if (debug) { - m_ptrDbg = std::make_unique(); - } + : m_owner(owner), + m_despeckleState(despeckle_state), + m_cancelHandle(std::move(cancel_handle)), + m_despeckleLevel(level) { + if (debug) { + m_dbg = std::make_unique(); + } } BackgroundExecutor::TaskResultPtr DespeckleView::DespeckleTask::operator()() { - try { - m_ptrCancelHandle->throwIfCancelled(); + try { + m_cancelHandle->throwIfCancelled(); - m_despeckleState = m_despeckleState.redespeckle(m_despeckleLevel, *m_ptrCancelHandle, m_ptrDbg.get()); + m_despeckleState = m_despeckleState.redespeckle(m_despeckleLevel, *m_cancelHandle, m_dbg.get()); - m_ptrCancelHandle->throwIfCancelled(); + m_cancelHandle->throwIfCancelled(); - DespeckleVisualization visualization(m_despeckleState.visualize()); + DespeckleVisualization visualization(m_despeckleState.visualize()); - m_ptrCancelHandle->throwIfCancelled(); + m_cancelHandle->throwIfCancelled(); - return make_intrusive(m_ptrOwner, m_ptrCancelHandle, m_despeckleState, visualization, - std::move(m_ptrDbg)); - } catch (const TaskCancelException&) { - return nullptr; - } + return make_intrusive(m_owner, m_cancelHandle, m_despeckleState, visualization, std::move(m_dbg)); + } catch (const TaskCancelException&) { + return nullptr; + } } /*======================== DespeckleResult ===========================*/ @@ -255,36 +251,35 @@ DespeckleView::DespeckleResult::DespeckleResult(QPointer owner, const DespeckleState& despeckle_state, const DespeckleVisualization& visualization, std::unique_ptr debug_images) - : m_ptrOwner(std::move(owner)), - m_ptrCancelHandle(std::move(cancel_handle)), - m_ptrDbg(std::move(debug_images)), - m_despeckleState(despeckle_state), - m_visualization(visualization) { -} + : m_owner(std::move(owner)), + m_cancelHandle(std::move(cancel_handle)), + m_dbg(std::move(debug_images)), + m_despeckleState(despeckle_state), + m_visualization(visualization) {} void DespeckleView::DespeckleResult::operator()() { - if (m_ptrCancelHandle->isCancelled()) { - return; - } + if (m_cancelHandle->isCancelled()) { + return; + } - if (DespeckleView* owner = m_ptrOwner) { - owner->despeckleDone(m_despeckleState, m_visualization, m_ptrDbg.get()); - } + if (DespeckleView* owner = m_owner) { + owner->despeckleDone(m_despeckleState, m_visualization, m_dbg.get()); + } } /*========================= TaskCancelHandle ============================*/ void DespeckleView::TaskCancelHandle::cancel() { - m_cancelFlag.fetchAndStoreRelaxed(1); + m_cancelFlag.fetchAndStoreRelaxed(1); } bool DespeckleView::TaskCancelHandle::isCancelled() const { - return m_cancelFlag.fetchAndAddRelaxed(0) != 0; + return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } void DespeckleView::TaskCancelHandle::throwIfCancelled() const { - if (isCancelled()) { - throw TaskCancelException(); - } + if (isCancelled()) { + throw TaskCancelException(); + } } } // namespace output \ No newline at end of file diff --git a/filters/output/DespeckleView.h b/filters/output/DespeckleView.h index 45281180a..d9fe3b8a3 100644 --- a/filters/output/DespeckleView.h +++ b/filters/output/DespeckleView.h @@ -20,13 +20,13 @@ #ifndef OUTPUT_DESPECKLE_VIEW_H_ #define OUTPUT_DESPECKLE_VIEW_H_ +#include +#include #include "DespeckleLevel.h" #include "DespeckleState.h" -#include "intrusive_ptr.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" -#include -#include +#include "intrusive_ptr.h" class DebugImages; class ProcessingIndicationWidget; @@ -36,55 +36,55 @@ namespace output { class DespeckleVisualization; class DespeckleView : public QStackedWidget { - Q_OBJECT -public: - /** - * \param despeckle_state Describes a particular despeckling. - * \param visualization Optional despeckle visualization. - * If null, it will be reconstructed from \p despeckle_state - * when this widget becomes visible. - * \param debug Indicates whether debugging is turned on. - */ - DespeckleView(const DespeckleState& despeckle_state, const DespeckleVisualization& visualization, bool debug); + Q_OBJECT + public: + /** + * \param despeckle_state Describes a particular despeckling. + * \param visualization Optional despeckle visualization. + * If null, it will be reconstructed from \p despeckle_state + * when this widget becomes visible. + * \param debug Indicates whether debugging is turned on. + */ + DespeckleView(const DespeckleState& despeckle_state, const DespeckleVisualization& visualization, bool debug); - ~DespeckleView() override; + ~DespeckleView() override; -public slots: + public slots: - void despeckleLevelChanged(DespeckleLevel level, bool* handled); + void despeckleLevelChanged(double level, bool* handled); -signals: + signals: - void imageViewCreated(ImageViewBase*); + void imageViewCreated(ImageViewBase*); -protected: - void hideEvent(QHideEvent* evt) override; + protected: + void hideEvent(QHideEvent* evt) override; - void showEvent(QShowEvent* evt) override; + void showEvent(QShowEvent* evt) override; -private: - class TaskCancelException; - class TaskCancelHandle; - class DespeckleTask; - class DespeckleResult; + private: + class TaskCancelException; + class TaskCancelHandle; + class DespeckleTask; + class DespeckleResult; - enum AnimationAction { RESET_ANIMATION, RESUME_ANIMATION }; + enum AnimationAction { RESET_ANIMATION, RESUME_ANIMATION }; - void initiateDespeckling(AnimationAction anim_action); + void initiateDespeckling(AnimationAction anim_action); - void despeckleDone(const DespeckleState& despeckle_state, - const DespeckleVisualization& visualization, - DebugImages* dbg); + void despeckleDone(const DespeckleState& despeckle_state, + const DespeckleVisualization& visualization, + DebugImages* dbg); - void cancelBackgroundTask(); + void cancelBackgroundTask(); - void removeImageViewWidget(); + void removeImageViewWidget(); - DespeckleState m_despeckleState; - intrusive_ptr m_ptrCancelHandle; - ProcessingIndicationWidget* m_pProcessingIndicator; - DespeckleLevel m_despeckleLevel; - bool m_debug; + DespeckleState m_despeckleState; + intrusive_ptr m_cancelHandle; + ProcessingIndicationWidget* m_processingIndicator; + double m_despeckleLevel; + bool m_debug; }; } // namespace output #endif // ifndef OUTPUT_DESPECKLE_VIEW_H_ diff --git a/filters/output/DespeckleVisualization.cpp b/filters/output/DespeckleVisualization.cpp index 6f4a9ce34..f86ef84b9 100644 --- a/filters/output/DespeckleVisualization.cpp +++ b/filters/output/DespeckleVisualization.cpp @@ -17,11 +17,11 @@ */ #include "DespeckleVisualization.h" -#include "ImageViewBase.h" +#include #include "Dpi.h" +#include "ImageViewBase.h" #include "imageproc/BinaryImage.h" #include "imageproc/SEDM.h" -#include using namespace imageproc; @@ -29,73 +29,73 @@ namespace output { DespeckleVisualization::DespeckleVisualization(const QImage& output, const imageproc::BinaryImage& speckles, const Dpi& dpi) { - if (output.isNull()) { - // This can happen in batch processing mode. - return; - } + if (output.isNull()) { + // This can happen in batch processing mode. + return; + } - m_image = output.convertToFormat(QImage::Format_RGB32); + m_image = output.convertToFormat(QImage::Format_RGB32); - if (!speckles.isNull()) { - colorizeSpeckles(m_image, speckles, dpi); - } + if (!speckles.isNull()) { + colorizeSpeckles(m_image, speckles, dpi); + } - m_downscaledImage = ImageViewBase::createDownscaledImage(m_image); + m_downscaledImage = ImageViewBase::createDownscaledImage(m_image); } void DespeckleVisualization::colorizeSpeckles(QImage& image, const imageproc::BinaryImage& speckles, const Dpi& dpi) { - const int w = image.width(); - const int h = image.height(); - auto* image_line = (uint32_t*) image.bits(); - const int image_stride = image.bytesPerLine() / 4; - - const SEDM sedm(speckles, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS); - const uint32_t* sedm_line = sedm.data(); - const int sedm_stride = sedm.stride(); - - const float radius = static_cast(15.0 * std::max(dpi.horizontal(), dpi.vertical()) / 600); - const float sq_radius = radius * radius; - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - const uint32_t sq_dist = sedm_line[x]; - if (sq_dist == 0) { - // Speckle pixel. - image_line[x] = 0xffff0000; // opaque red - continue; - } else if ((image_line[x] & 0x00ffffff) == 0x0) { - // Non-speckle black pixel. - continue; - } - - const float alpha_upper_bound = 0.8f; - const float scale = alpha_upper_bound / sq_radius; - const float alpha = alpha_upper_bound - scale * sq_dist; - if (alpha > 0) { - const float alpha2 = 1.0f - alpha; - const float overlay_r = 255; - const float overlay_g = 0; - const float overlay_b = 0; - const float r = overlay_r * alpha + qRed(image_line[x]) * alpha2; - const float g = overlay_g * alpha + qGreen(image_line[x]) * alpha2; - const float b = overlay_b * alpha + qBlue(image_line[x]) * alpha2; - image_line[x] = qRgb(int(r), int(g), int(b)); - } - } - sedm_line += sedm_stride; - image_line += image_stride; + const int w = image.width(); + const int h = image.height(); + auto* image_line = (uint32_t*) image.bits(); + const int image_stride = image.bytesPerLine() / 4; + + const SEDM sedm(speckles, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS); + const uint32_t* sedm_line = sedm.data(); + const int sedm_stride = sedm.stride(); + + const float radius = static_cast(15.0 * std::max(dpi.horizontal(), dpi.vertical()) / 600); + const float sq_radius = radius * radius; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const uint32_t sq_dist = sedm_line[x]; + if (sq_dist == 0) { + // Speckle pixel. + image_line[x] = 0xffff0000; // opaque red + continue; + } else if ((image_line[x] & 0x00ffffff) == 0x0) { + // Non-speckle black pixel. + continue; + } + + const float alpha_upper_bound = 0.8f; + const float scale = alpha_upper_bound / sq_radius; + const float alpha = alpha_upper_bound - scale * sq_dist; + if (alpha > 0) { + const float alpha2 = 1.0f - alpha; + const float overlay_r = 255; + const float overlay_g = 0; + const float overlay_b = 0; + const float r = overlay_r * alpha + qRed(image_line[x]) * alpha2; + const float g = overlay_g * alpha + qGreen(image_line[x]) * alpha2; + const float b = overlay_b * alpha + qBlue(image_line[x]) * alpha2; + image_line[x] = qRgb(int(r), int(g), int(b)); + } } + sedm_line += sedm_stride; + image_line += image_stride; + } } bool DespeckleVisualization::isNull() const { - return m_image.isNull(); + return m_image.isNull(); } const QImage& DespeckleVisualization::image() const { - return m_image; + return m_image; } const QImage& DespeckleVisualization::downscaledImage() const { - return m_downscaledImage; + return m_downscaledImage; } } // namespace output \ No newline at end of file diff --git a/filters/output/DespeckleVisualization.h b/filters/output/DespeckleVisualization.h index 82f0fe260..d99a1c83f 100644 --- a/filters/output/DespeckleVisualization.h +++ b/filters/output/DespeckleVisualization.h @@ -30,33 +30,33 @@ class BinaryImage; namespace output { class DespeckleVisualization { -public: - /* - * Constructs a null visualization. - */ - DespeckleVisualization() = default; + public: + /* + * Constructs a null visualization. + */ + DespeckleVisualization() = default; - /** - * \param output The output file, as produced by OutputGenerator::process(). - * If this one is null, the visualization will be null as well. - * \param speckles Speckles detected in the image. - * If this one is null, it is considered no speckles were detected. - * \param dpi Dots-per-inch of both images. - */ - DespeckleVisualization(const QImage& output, const imageproc::BinaryImage& speckles, const Dpi& dpi); + /** + * \param output The output file, as produced by OutputGenerator::process(). + * If this one is null, the visualization will be null as well. + * \param speckles Speckles detected in the image. + * If this one is null, it is considered no speckles were detected. + * \param dpi Dots-per-inch of both images. + */ + DespeckleVisualization(const QImage& output, const imageproc::BinaryImage& speckles, const Dpi& dpi); - bool isNull() const; + bool isNull() const; - const QImage& image() const; + const QImage& image() const; - const QImage& downscaledImage() const; + const QImage& downscaledImage() const; -private: - static void colorizeSpeckles(QImage& image, const imageproc::BinaryImage& speckles, const Dpi& dpi); + private: + static void colorizeSpeckles(QImage& image, const imageproc::BinaryImage& speckles, const Dpi& dpi); - QImage m_image; - QImage m_downscaledImage; + QImage m_image; + QImage m_downscaledImage; }; } // namespace output #endif // ifndef OUTPUT_DESPECKLE_VISUALIZATION_H_ diff --git a/filters/output/DewarpingOptions.cpp b/filters/output/DewarpingOptions.cpp index 958b1d231..a65b36985 100644 --- a/filters/output/DewarpingOptions.cpp +++ b/filters/output/DewarpingOptions.cpp @@ -20,71 +20,70 @@ #include namespace output { -DewarpingOptions::DewarpingOptions(DewarpingMode mode, bool needPostDeskew) : m_mode(mode), postDeskew(needPostDeskew) { -} +DewarpingOptions::DewarpingOptions(DewarpingMode mode, bool needPostDeskew) + : m_mode(mode), m_needPostDeskew(needPostDeskew) {} DewarpingOptions::DewarpingOptions(const QDomElement& el) - : m_mode(parseDewarpingMode(el.attribute("mode"))), postDeskew(el.attribute("postDeskew", "1") == "1") { -} + : m_mode(parseDewarpingMode(el.attribute("mode"))), m_needPostDeskew(el.attribute("postDeskew", "1") == "1") {} QDomElement DewarpingOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("mode", formatDewarpingMode(m_mode)); - el.setAttribute("postDeskew", postDeskew ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.setAttribute("mode", formatDewarpingMode(m_mode)); + el.setAttribute("postDeskew", m_needPostDeskew ? "1" : "0"); - return el; + return el; } bool DewarpingOptions::operator==(const DewarpingOptions& other) const { - return (m_mode == other.m_mode) && (postDeskew == other.postDeskew); + return (m_mode == other.m_mode) && (m_needPostDeskew == other.m_needPostDeskew); } bool DewarpingOptions::operator!=(const DewarpingOptions& other) const { - return !(*this == other); + return !(*this == other); } void DewarpingOptions::setDewarpingMode(DewarpingMode m_mode) { - DewarpingOptions::m_mode = m_mode; + DewarpingOptions::m_mode = m_mode; } void DewarpingOptions::setPostDeskew(bool postDeskew) { - DewarpingOptions::postDeskew = postDeskew; + DewarpingOptions::m_needPostDeskew = postDeskew; } bool DewarpingOptions::needPostDeskew() const { - return postDeskew; + return m_needPostDeskew; } DewarpingMode DewarpingOptions::dewarpingMode() const { - return m_mode; + return m_mode; } DewarpingMode DewarpingOptions::parseDewarpingMode(const QString& str) { - if (str == "auto") { - return AUTO; - } else if (str == "manual") { - return MANUAL; - } else if (str == "marginal") { - return MARGINAL; - } else { - return OFF; - } + if (str == "auto") { + return AUTO; + } else if (str == "manual") { + return MANUAL; + } else if (str == "marginal") { + return MARGINAL; + } else { + return OFF; + } } QString DewarpingOptions::formatDewarpingMode(DewarpingMode mode) { - switch (mode) { - case OFF: - return "off"; - case AUTO: - return "auto"; - case MANUAL: - return "manual"; - case MARGINAL: - return "marginal"; - } - - assert(!"Unreachable"); - - return QString(); + switch (mode) { + case OFF: + return "off"; + case AUTO: + return "auto"; + case MANUAL: + return "manual"; + case MARGINAL: + return "marginal"; + } + + assert(!"Unreachable"); + + return QString(); } } // namespace output \ No newline at end of file diff --git a/filters/output/DewarpingOptions.h b/filters/output/DewarpingOptions.h index 09de95f87..23763b0eb 100644 --- a/filters/output/DewarpingOptions.h +++ b/filters/output/DewarpingOptions.h @@ -26,32 +26,32 @@ namespace output { enum DewarpingMode { OFF, AUTO, MANUAL, MARGINAL }; class DewarpingOptions { -public: - explicit DewarpingOptions(DewarpingMode mode = OFF, bool needPostDeskew = true); + public: + explicit DewarpingOptions(DewarpingMode mode = OFF, bool needPostDeskew = true); - explicit DewarpingOptions(const QDomElement& el); + explicit DewarpingOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const DewarpingOptions& other) const; + bool operator==(const DewarpingOptions& other) const; - bool operator!=(const DewarpingOptions& other) const; + bool operator!=(const DewarpingOptions& other) const; - DewarpingMode dewarpingMode() const; + DewarpingMode dewarpingMode() const; - void setDewarpingMode(DewarpingMode m_mode); + void setDewarpingMode(DewarpingMode m_mode); - bool needPostDeskew() const; + bool needPostDeskew() const; - void setPostDeskew(bool postDeskew); + void setPostDeskew(bool postDeskew); - static DewarpingMode parseDewarpingMode(const QString& str); + static DewarpingMode parseDewarpingMode(const QString& str); - static QString formatDewarpingMode(DewarpingMode mode); + static QString formatDewarpingMode(DewarpingMode mode); -private: - DewarpingMode m_mode; - bool postDeskew; + private: + DewarpingMode m_mode; + bool m_needPostDeskew; }; } // namespace output #endif diff --git a/filters/output/DewarpingView.cpp b/filters/output/DewarpingView.cpp index c86e14b1b..288f42c2c 100644 --- a/filters/output/DewarpingView.cpp +++ b/filters/output/DewarpingView.cpp @@ -17,18 +17,18 @@ */ #include "DewarpingView.h" +#include +#include +#include +#include #include "ImagePresentation.h" #include "ToLineProjector.h" #include "dewarping/CylindricalSurfaceDewarper.h" -#include "spfit/SplineFitter.h" +#include "imageproc/Constants.h" #include "spfit/ConstraintSet.h" -#include "spfit/PolylineModelShape.h" #include "spfit/LinearForceBalancer.h" -#include "imageproc/Constants.h" -#include -#include -#include -#include +#include "spfit/PolylineModelShape.h" +#include "spfit/SplineFitter.h" namespace output { using namespace imageproc; @@ -42,67 +42,67 @@ DewarpingView::DewarpingView(const QImage& image, DewarpingOptions dewarping_options, const dewarping::DistortionModel& distortion_model, const DepthPerception& depth_perception) - : ImageViewBase(image, downscaled_image, ImagePresentation(image_to_virt, virt_display_area)), - m_pageId(page_id), - m_virtDisplayArea(virt_display_area), - m_dewarpingOptions(dewarping_options), - m_distortionModel(distortion_model), - m_depthPerception(depth_perception), - m_dragHandler(*this), - m_zoomHandler(*this) { - setMouseTracking(true); - - const QPolygonF source_content_rect(virtualToImage().map(virt_content_rect)); - - XSpline top_spline(m_distortionModel.topCurve().xspline()); - XSpline bottom_spline(m_distortionModel.bottomCurve().xspline()); - if (top_spline.numControlPoints() < 2) { - const std::vector& polyline = m_distortionModel.topCurve().polyline(); - - XSpline new_top_spline; - if (polyline.size() < 2) { - initNewSpline(new_top_spline, source_content_rect[0], source_content_rect[1], &dewarping_options); - } else { - initNewSpline(new_top_spline, polyline.front(), polyline.back(), &dewarping_options); - fitSpline(new_top_spline, polyline); - } - - top_spline.swap(new_top_spline); + : ImageViewBase(image, downscaled_image, ImagePresentation(image_to_virt, virt_display_area)), + m_pageId(page_id), + m_virtDisplayArea(virt_display_area), + m_dewarpingOptions(dewarping_options), + m_distortionModel(distortion_model), + m_depthPerception(depth_perception), + m_dragHandler(*this), + m_zoomHandler(*this) { + setMouseTracking(true); + + const QPolygonF source_content_rect(virtualToImage().map(virt_content_rect)); + + XSpline top_spline(m_distortionModel.topCurve().xspline()); + XSpline bottom_spline(m_distortionModel.bottomCurve().xspline()); + if (top_spline.numControlPoints() < 2) { + const std::vector& polyline = m_distortionModel.topCurve().polyline(); + + XSpline new_top_spline; + if (polyline.size() < 2) { + initNewSpline(new_top_spline, source_content_rect[0], source_content_rect[1], &dewarping_options); + } else { + initNewSpline(new_top_spline, polyline.front(), polyline.back(), &dewarping_options); + fitSpline(new_top_spline, polyline); } - if (bottom_spline.numControlPoints() < 2) { - const std::vector& polyline = m_distortionModel.bottomCurve().polyline(); - - XSpline new_bottom_spline; - if (polyline.size() < 2) { - initNewSpline(new_bottom_spline, source_content_rect[3], source_content_rect[2], &dewarping_options); - } else { - initNewSpline(new_bottom_spline, polyline.front(), polyline.back(), &dewarping_options); - fitSpline(new_bottom_spline, polyline); - } - bottom_spline.swap(new_bottom_spline); - } + top_spline.swap(new_top_spline); + } + if (bottom_spline.numControlPoints() < 2) { + const std::vector& polyline = m_distortionModel.bottomCurve().polyline(); - m_topSpline.setSpline(top_spline); - m_bottomSpline.setSpline(bottom_spline); - - InteractiveXSpline* splines[2] = {&m_topSpline, &m_bottomSpline}; - int curve_idx = -1; - for (InteractiveXSpline* spline : splines) { - ++curve_idx; - spline->setModifiedCallback(boost::bind(&DewarpingView::curveModified, this, curve_idx)); - spline->setDragFinishedCallback(boost::bind(&DewarpingView::dragFinished, this)); - spline->setStorageTransform(boost::bind(&DewarpingView::sourceToWidget, this, _1), - boost::bind(&DewarpingView::widgetToSource, this, _1)); - makeLastFollower(*spline); + XSpline new_bottom_spline; + if (polyline.size() < 2) { + initNewSpline(new_bottom_spline, source_content_rect[3], source_content_rect[2], &dewarping_options); + } else { + initNewSpline(new_bottom_spline, polyline.front(), polyline.back(), &dewarping_options); + fitSpline(new_bottom_spline, polyline); } - m_distortionModel.setTopCurve(dewarping::Curve(m_topSpline.spline())); - m_distortionModel.setBottomCurve(dewarping::Curve(m_bottomSpline.spline())); - - rootInteractionHandler().makeLastFollower(*this); - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + bottom_spline.swap(new_bottom_spline); + } + + m_topSpline.setSpline(top_spline); + m_bottomSpline.setSpline(bottom_spline); + + InteractiveXSpline* splines[2] = {&m_topSpline, &m_bottomSpline}; + int curve_idx = -1; + for (InteractiveXSpline* spline : splines) { + ++curve_idx; + spline->setModifiedCallback(boost::bind(&DewarpingView::curveModified, this, curve_idx)); + spline->setDragFinishedCallback(boost::bind(&DewarpingView::dragFinished, this)); + spline->setStorageTransform(boost::bind(&DewarpingView::sourceToWidget, this, _1), + boost::bind(&DewarpingView::widgetToSource, this, _1)); + makeLastFollower(*spline); + } + + m_distortionModel.setTopCurve(dewarping::Curve(m_topSpline.spline())); + m_distortionModel.setBottomCurve(dewarping::Curve(m_bottomSpline.spline())); + + rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); } DewarpingView::~DewarpingView() = default; @@ -110,132 +110,132 @@ DewarpingView::~DewarpingView() = default; void DewarpingView::initNewSpline(XSpline& spline, const QPointF& p1, const QPointF& p2, - const DewarpingOptions* p_dewarpingOptions) { - const QLineF line(p1, p2); - spline.appendControlPoint(line.p1(), 0); - if ((*p_dewarpingOptions).dewarpingMode() == AUTO) { - spline.appendControlPoint(line.pointAt(1.0 / 4.0), 1); - spline.appendControlPoint(line.pointAt(2.0 / 4.0), 1); - spline.appendControlPoint(line.pointAt(3.0 / 4.0), 1); - } - spline.appendControlPoint(line.p2(), 0); + const DewarpingOptions* dewarpingOptions) { + const QLineF line(p1, p2); + spline.appendControlPoint(line.p1(), 0); + if ((*dewarpingOptions).dewarpingMode() == AUTO) { + spline.appendControlPoint(line.pointAt(1.0 / 4.0), 1); + spline.appendControlPoint(line.pointAt(2.0 / 4.0), 1); + spline.appendControlPoint(line.pointAt(3.0 / 4.0), 1); + } + spline.appendControlPoint(line.p2(), 0); } void DewarpingView::fitSpline(XSpline& spline, const std::vector& polyline) { - using namespace spfit; - - SplineFitter fitter(&spline); - const PolylineModelShape model_shape(polyline); - - ConstraintSet constraints(&spline); - constraints.constrainSplinePoint(0.0, polyline.front()); - constraints.constrainSplinePoint(1.0, polyline.back()); - fitter.setConstraints(constraints); - - FittableSpline::SamplingParams sampling_params; - sampling_params.maxDistBetweenSamples = 10; - fitter.setSamplingParams(sampling_params); - - int iterations_remaining = 20; - LinearForceBalancer balancer(0.8); - balancer.setTargetRatio(0.1); - balancer.setIterationsToTarget(iterations_remaining - 1); - - for (; iterations_remaining > 0; --iterations_remaining, balancer.nextIteration()) { - fitter.addAttractionForces(model_shape); - fitter.addInternalForce(spline.controlPointsAttractionForce()); - - double internal_force_weight = balancer.calcInternalForceWeight(fitter.internalForce(), fitter.externalForce()); - const OptimizationResult res(fitter.optimize(internal_force_weight)); - if (dewarping::Curve::splineHasLoops(spline)) { - fitter.undoLastStep(); - break; - } + using namespace spfit; + + SplineFitter fitter(&spline); + const PolylineModelShape model_shape(polyline); + + ConstraintSet constraints(&spline); + constraints.constrainSplinePoint(0.0, polyline.front()); + constraints.constrainSplinePoint(1.0, polyline.back()); + fitter.setConstraints(constraints); + + FittableSpline::SamplingParams sampling_params; + sampling_params.maxDistBetweenSamples = 10; + fitter.setSamplingParams(sampling_params); + + int iterations_remaining = 20; + LinearForceBalancer balancer(0.8); + balancer.setTargetRatio(0.1); + balancer.setIterationsToTarget(iterations_remaining - 1); + + for (; iterations_remaining > 0; --iterations_remaining, balancer.nextIteration()) { + fitter.addAttractionForces(model_shape); + fitter.addInternalForce(spline.controlPointsAttractionForce()); + + double internal_force_weight = balancer.calcInternalForceWeight(fitter.internalForce(), fitter.externalForce()); + const OptimizationResult res(fitter.optimize(internal_force_weight)); + if (dewarping::Curve::splineHasLoops(spline)) { + fitter.undoLastStep(); + break; + } - if (res.improvementPercentage() < 0.5) { - break; - } + if (res.improvementPercentage() < 0.5) { + break; } + } } // DewarpingView::fitSpline void DewarpingView::depthPerceptionChanged(double val) { - m_depthPerception.setValue(val); - update(); + m_depthPerception.setValue(val); + update(); } void DewarpingView::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setRenderHint(QPainter::Antialiasing); - - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0xff, 0xff, 0xff, 150)); // Translucent white. - painter.drawPolygon(virtMarginArea(0)); // Left margin. - painter.drawPolygon(virtMarginArea(1)); // Right margin. - painter.setWorldTransform(imageToVirtual() * painter.worldTransform()); - painter.setBrush(Qt::NoBrush); - - QPen grid_pen; - grid_pen.setColor(Qt::blue); - grid_pen.setCosmetic(true); - grid_pen.setWidthF(1.2); - - painter.setPen(grid_pen); - painter.setBrush(Qt::NoBrush); - - const int num_vert_grid_lines = 30; - const int num_hor_grid_lines = 30; - - bool valid_model = m_distortionModel.isValid(); - - if (valid_model) { - try { - std::vector> curves(num_hor_grid_lines); - - dewarping::CylindricalSurfaceDewarper dewarper(m_distortionModel.topCurve().polyline(), - m_distortionModel.bottomCurve().polyline(), - m_depthPerception.value()); - dewarping::CylindricalSurfaceDewarper::State state; - - for (int j = 0; j < num_vert_grid_lines; ++j) { - const double x = j / (num_vert_grid_lines - 1.0); - const dewarping::CylindricalSurfaceDewarper::Generatrix gtx(dewarper.mapGeneratrix(x, state)); - const QPointF gtx_p0(gtx.imgLine.pointAt(gtx.pln2img(0))); - const QPointF gtx_p1(gtx.imgLine.pointAt(gtx.pln2img(1))); - painter.drawLine(gtx_p0, gtx_p1); - for (int i = 0; i < num_hor_grid_lines; ++i) { - const double y = i / (num_hor_grid_lines - 1.0); - curves[i].push_back(gtx.imgLine.pointAt(gtx.pln2img(y))); - } - } - - for (const QVector& curve : curves) { - painter.drawPolyline(curve); - } - } catch (const std::runtime_error&) { - // Still probably a bad model, even though DistortionModel::isValid() was true. - valid_model = false; + painter.setRenderHint(QPainter::Antialiasing); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0xff, 0xff, 0xff, 150)); // Translucent white. + painter.drawPolygon(virtMarginArea(0)); // Left margin. + painter.drawPolygon(virtMarginArea(1)); // Right margin. + painter.setWorldTransform(imageToVirtual() * painter.worldTransform()); + painter.setBrush(Qt::NoBrush); + + QPen grid_pen; + grid_pen.setColor(Qt::blue); + grid_pen.setCosmetic(true); + grid_pen.setWidthF(1.2); + + painter.setPen(grid_pen); + painter.setBrush(Qt::NoBrush); + + const int num_vert_grid_lines = 30; + const int num_hor_grid_lines = 30; + + bool valid_model = m_distortionModel.isValid(); + + if (valid_model) { + try { + std::vector> curves(num_hor_grid_lines); + + dewarping::CylindricalSurfaceDewarper dewarper(m_distortionModel.topCurve().polyline(), + m_distortionModel.bottomCurve().polyline(), + m_depthPerception.value()); + dewarping::CylindricalSurfaceDewarper::State state; + + for (int j = 0; j < num_vert_grid_lines; ++j) { + const double x = j / (num_vert_grid_lines - 1.0); + const dewarping::CylindricalSurfaceDewarper::Generatrix gtx(dewarper.mapGeneratrix(x, state)); + const QPointF gtx_p0(gtx.imgLine.pointAt(gtx.pln2img(0))); + const QPointF gtx_p1(gtx.imgLine.pointAt(gtx.pln2img(1))); + painter.drawLine(gtx_p0, gtx_p1); + for (int i = 0; i < num_hor_grid_lines; ++i) { + const double y = i / (num_hor_grid_lines - 1.0); + curves[i].push_back(gtx.imgLine.pointAt(gtx.pln2img(y))); } - } // valid_model - if (!valid_model) { - // Just draw the frame. - const dewarping::Curve& top_curve = m_distortionModel.topCurve(); - const dewarping::Curve& bottom_curve = m_distortionModel.bottomCurve(); - painter.drawLine(top_curve.polyline().front(), bottom_curve.polyline().front()); - painter.drawLine(top_curve.polyline().back(), bottom_curve.polyline().back()); - painter.drawPolyline(QVector::fromStdVector(top_curve.polyline())); - painter.drawPolyline(QVector::fromStdVector(bottom_curve.polyline())); + } + + for (const QVector& curve : curves) { + painter.drawPolyline(curve); + } + } catch (const std::runtime_error&) { + // Still probably a bad model, even though DistortionModel::isValid() was true. + valid_model = false; } - - paintXSpline(painter, interaction, m_topSpline); - paintXSpline(painter, interaction, m_bottomSpline); + } // valid_model + if (!valid_model) { + // Just draw the frame. + const dewarping::Curve& top_curve = m_distortionModel.topCurve(); + const dewarping::Curve& bottom_curve = m_distortionModel.bottomCurve(); + painter.drawLine(top_curve.polyline().front(), bottom_curve.polyline().front()); + painter.drawLine(top_curve.polyline().back(), bottom_curve.polyline().back()); + painter.drawPolyline(QVector::fromStdVector(top_curve.polyline())); + painter.drawPolyline(QVector::fromStdVector(bottom_curve.polyline())); + } + + paintXSpline(painter, interaction, m_topSpline); + paintXSpline(painter, interaction, m_bottomSpline); } // DewarpingView::onPaint void DewarpingView::paintXSpline(QPainter& painter, const InteractionState& interaction, const InteractiveXSpline& ispline) { - const XSpline& spline = ispline.spline(); + const XSpline& spline = ispline.spline(); - painter.save(); - painter.setBrush(Qt::NoBrush); + painter.save(); + painter.setBrush(Qt::NoBrush); #if 0 // No point in drawing the curve itself - we already draw the grid. painter.setWorldTransform(imageToVirtual() * virtualToWidget()); @@ -248,112 +248,112 @@ void DewarpingView::paintXSpline(QPainter& painter, const std::vector polyline(spline.toPolyline()); painter.drawPolyline(&polyline[0], polyline.size()); #endif - // Drawing cosmetic points in transformed coordinates seems unreliable, - // so let's draw them in widget coordinates. - painter.setWorldMatrixEnabled(false); - - QPen existing_point_pen(Qt::red); - existing_point_pen.setWidthF(4.0); - existing_point_pen.setCosmetic(true); - painter.setPen(existing_point_pen); - - const int num_control_points = spline.numControlPoints(); - for (int i = 0; i < num_control_points; ++i) { - painter.drawPoint(sourceToWidget(spline.controlPointPosition(i))); - } - - QPointF pt; - if (ispline.curveIsProximityLeader(interaction, &pt)) { - QPen new_point_pen(existing_point_pen); - new_point_pen.setColor(QColor(0x00ffff)); - painter.setPen(new_point_pen); - painter.drawPoint(pt); - } - - painter.restore(); + // Drawing cosmetic points in transformed coordinates seems unreliable, + // so let's draw them in widget coordinates. + painter.setWorldMatrixEnabled(false); + + QPen existing_point_pen(Qt::red); + existing_point_pen.setWidthF(4.0); + existing_point_pen.setCosmetic(true); + painter.setPen(existing_point_pen); + + const int num_control_points = spline.numControlPoints(); + for (int i = 0; i < num_control_points; ++i) { + painter.drawPoint(sourceToWidget(spline.controlPointPosition(i))); + } + + QPointF pt; + if (ispline.curveIsProximityLeader(interaction, &pt)) { + QPen new_point_pen(existing_point_pen); + new_point_pen.setColor(QColor(0x00ffff)); + painter.setPen(new_point_pen); + painter.drawPoint(pt); + } + + painter.restore(); } // DewarpingView::paintXSpline void DewarpingView::curveModified(int curve_idx) { - if (curve_idx == 0) { - m_distortionModel.setTopCurve(dewarping::Curve(m_topSpline.spline())); - } else { - m_distortionModel.setBottomCurve(dewarping::Curve(m_bottomSpline.spline())); - } - update(); + if (curve_idx == 0) { + m_distortionModel.setTopCurve(dewarping::Curve(m_topSpline.spline())); + } else { + m_distortionModel.setBottomCurve(dewarping::Curve(m_bottomSpline.spline())); + } + update(); } void DewarpingView::dragFinished() { - if ((m_dewarpingOptions.dewarpingMode() == AUTO) || (m_dewarpingOptions.dewarpingMode() == MARGINAL)) { - m_dewarpingOptions.setDewarpingMode(MANUAL); - } - emit distortionModelChanged(m_distortionModel); + if ((m_dewarpingOptions.dewarpingMode() == AUTO) || (m_dewarpingOptions.dewarpingMode() == MARGINAL)) { + m_dewarpingOptions.setDewarpingMode(MANUAL); + } + emit distortionModelChanged(m_distortionModel); } /** Source image coordinates to widget coordinates. */ QPointF DewarpingView::sourceToWidget(const QPointF& pt) const { - return virtualToWidget().map(imageToVirtual().map(pt)); + return virtualToWidget().map(imageToVirtual().map(pt)); } /** Widget coordinates to source image coordinates. */ QPointF DewarpingView::widgetToSource(const QPointF& pt) const { - return virtualToImage().map(widgetToVirtual().map(pt)); + return virtualToImage().map(widgetToVirtual().map(pt)); } QPolygonF DewarpingView::virtMarginArea(int margin_idx) const { - const dewarping::Curve& top_curve = m_distortionModel.topCurve(); - const dewarping::Curve& bottom_curve = m_distortionModel.bottomCurve(); - - QLineF vert_boundary; // From top to bottom, that's important! - if (margin_idx == 0) { // Left margin. - vert_boundary.setP1(top_curve.polyline().front()); - vert_boundary.setP2(bottom_curve.polyline().front()); - } else { // Right margin. - vert_boundary.setP1(top_curve.polyline().back()); - vert_boundary.setP2(bottom_curve.polyline().back()); + const dewarping::Curve& top_curve = m_distortionModel.topCurve(); + const dewarping::Curve& bottom_curve = m_distortionModel.bottomCurve(); + + QLineF vert_boundary; // From top to bottom, that's important! + if (margin_idx == 0) { // Left margin. + vert_boundary.setP1(top_curve.polyline().front()); + vert_boundary.setP2(bottom_curve.polyline().front()); + } else { // Right margin. + vert_boundary.setP1(top_curve.polyline().back()); + vert_boundary.setP2(bottom_curve.polyline().back()); + } + + vert_boundary = imageToVirtual().map(vert_boundary); + + QLineF normal; + if (margin_idx == 0) { // Left margin. + normal = QLineF(vert_boundary.p2(), vert_boundary.p1()).normalVector(); + } else { // Right margin. + normal = vert_boundary.normalVector(); + } + + // Project every vertex in the m_virtDisplayArea polygon + // to vert_line and to its normal, keeping track min and max values. + double min = NumericTraits::max(); + double max = NumericTraits::min(); + double normal_max = max; + const ToLineProjector vert_line_projector(vert_boundary); + const ToLineProjector normal_projector(normal); + for (const QPointF& pt : m_virtDisplayArea) { + const double p1 = vert_line_projector.projectionScalar(pt); + if (p1 < min) { + min = p1; } - - vert_boundary = imageToVirtual().map(vert_boundary); - - QLineF normal; - if (margin_idx == 0) { // Left margin. - normal = QLineF(vert_boundary.p2(), vert_boundary.p1()).normalVector(); - } else { // Right margin. - normal = vert_boundary.normalVector(); + if (p1 > max) { + max = p1; } - // Project every vertex in the m_virtDisplayArea polygon - // to vert_line and to its normal, keeping track min and max values. - double min = NumericTraits::max(); - double max = NumericTraits::min(); - double normal_max = max; - const ToLineProjector vert_line_projector(vert_boundary); - const ToLineProjector normal_projector(normal); - for (const QPointF& pt : m_virtDisplayArea) { - const double p1 = vert_line_projector.projectionScalar(pt); - if (p1 < min) { - min = p1; - } - if (p1 > max) { - max = p1; - } - - const double p2 = normal_projector.projectionScalar(pt); - if (p2 > normal_max) { - normal_max = p2; - } + const double p2 = normal_projector.projectionScalar(pt); + if (p2 > normal_max) { + normal_max = p2; } + } - // Workaround clipping bugs in QPolygon::intersected(). - min -= 1.0; - max += 1.0; - normal_max += 1.0; + // Workaround clipping bugs in QPolygon::intersected(). + min -= 1.0; + max += 1.0; + normal_max += 1.0; - QPolygonF poly; - poly << vert_boundary.pointAt(min); - poly << vert_boundary.pointAt(max); - poly << vert_boundary.pointAt(max) + normal.pointAt(normal_max) - normal.p1(); - poly << vert_boundary.pointAt(min) + normal.pointAt(normal_max) - normal.p1(); + QPolygonF poly; + poly << vert_boundary.pointAt(min); + poly << vert_boundary.pointAt(max); + poly << vert_boundary.pointAt(max) + normal.pointAt(normal_max) - normal.p1(); + poly << vert_boundary.pointAt(min) + normal.pointAt(normal_max) - normal.p1(); - return m_virtDisplayArea.intersected(poly); + return m_virtDisplayArea.intersected(poly); } // DewarpingView::virtMarginArea } // namespace output \ No newline at end of file diff --git a/filters/output/DewarpingView.h b/filters/output/DewarpingView.h index edbe3d549..ddaa92a40 100644 --- a/filters/output/DewarpingView.h +++ b/filters/output/DewarpingView.h @@ -19,79 +19,79 @@ #ifndef OUTPUT_DEWARPING_VIEW_H_ #define OUTPUT_DEWARPING_VIEW_H_ -#include "ImageViewBase.h" +#include +#include +#include +#include +#include +#include "DepthPerception.h" +#include "DewarpingOptions.h" +#include "DragHandler.h" #include "ImagePixmapUnion.h" +#include "ImageViewBase.h" #include "InteractionHandler.h" #include "InteractiveXSpline.h" -#include "DragHandler.h" +#include "PageId.h" +#include "Settings.h" #include "ZoomHandler.h" -#include "DewarpingOptions.h" #include "dewarping/DistortionModel.h" -#include "DepthPerception.h" -#include "Settings.h" -#include "PageId.h" -#include -#include -#include -#include -#include namespace output { class DewarpingView : public ImageViewBase, protected InteractionHandler { - Q_OBJECT -public: - DewarpingView(const QImage& image, - const ImagePixmapUnion& downscaled_image, - const QTransform& source_to_virt, - const QPolygonF& virt_display_area, - const QRectF& virt_content_rect, - const PageId& page_id, - DewarpingOptions dewarping_options, - const dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception); + Q_OBJECT + public: + DewarpingView(const QImage& image, + const ImagePixmapUnion& downscaled_image, + const QTransform& source_to_virt, + const QPolygonF& virt_display_area, + const QRectF& virt_content_rect, + const PageId& page_id, + DewarpingOptions dewarping_options, + const dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception); - ~DewarpingView() override; + ~DewarpingView() override; -signals: + signals: - void distortionModelChanged(const dewarping::DistortionModel& model); + void distortionModelChanged(const dewarping::DistortionModel& model); -public slots: + public slots: - void depthPerceptionChanged(double val); + void depthPerceptionChanged(double val); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; -private: - static void initNewSpline(XSpline& spline, - const QPointF& p1, - const QPointF& p2, - const DewarpingOptions* p_dewarpingOptions = nullptr); + private: + static void initNewSpline(XSpline& spline, + const QPointF& p1, + const QPointF& p2, + const DewarpingOptions* dewarpingOptions = nullptr); - static void fitSpline(XSpline& spline, const std::vector& polyline); + static void fitSpline(XSpline& spline, const std::vector& polyline); - void paintXSpline(QPainter& painter, const InteractionState& interaction, const InteractiveXSpline& ispline); + void paintXSpline(QPainter& painter, const InteractionState& interaction, const InteractiveXSpline& ispline); - void curveModified(int curve_idx); + void curveModified(int curve_idx); - void dragFinished(); + void dragFinished(); - QPointF sourceToWidget(const QPointF& pt) const; + QPointF sourceToWidget(const QPointF& pt) const; - QPointF widgetToSource(const QPointF& pt) const; + QPointF widgetToSource(const QPointF& pt) const; - QPolygonF virtMarginArea(int margin_idx) const; + QPolygonF virtMarginArea(int margin_idx) const; - PageId m_pageId; - QPolygonF m_virtDisplayArea; - DewarpingOptions m_dewarpingOptions; - dewarping::DistortionModel m_distortionModel; - DepthPerception m_depthPerception; - InteractiveXSpline m_topSpline; - InteractiveXSpline m_bottomSpline; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + PageId m_pageId; + QPolygonF m_virtDisplayArea; + DewarpingOptions m_dewarpingOptions; + dewarping::DistortionModel m_distortionModel; + DepthPerception m_depthPerception; + InteractiveXSpline m_topSpline; + InteractiveXSpline m_bottomSpline; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; }; } // namespace output #endif // ifndef OUTPUT_DEWARPING_VIEW_H_ diff --git a/filters/output/FillColorProperty.cpp b/filters/output/FillColorProperty.cpp index 1d3b7c1c1..4ed92c18a 100644 --- a/filters/output/FillColorProperty.cpp +++ b/filters/output/FillColorProperty.cpp @@ -17,51 +17,49 @@ */ #include "FillColorProperty.h" -#include "PropertyFactory.h" #include +#include "PropertyFactory.h" namespace output { const char FillColorProperty::m_propertyName[] = "FillColorProperty"; -FillColorProperty::FillColorProperty(const QDomElement& el) : m_rgb(rgbFromString(el.attribute("color"))) { -} +FillColorProperty::FillColorProperty(const QDomElement& el) : m_rgb(rgbFromString(el.attribute("color"))) {} void FillColorProperty::registerIn(PropertyFactory& factory) { - factory.registerProperty(m_propertyName, &FillColorProperty::construct); + factory.registerProperty(m_propertyName, &FillColorProperty::construct); } intrusive_ptr FillColorProperty::clone() const { - return make_intrusive(*this); + return make_intrusive(*this); } QDomElement FillColorProperty::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("type", m_propertyName); - el.setAttribute("color", rgbToString(m_rgb)); + QDomElement el(doc.createElement(name)); + el.setAttribute("type", m_propertyName); + el.setAttribute("color", rgbToString(m_rgb)); - return el; + return el; } intrusive_ptr FillColorProperty::construct(const QDomElement& el) { - return make_intrusive(el); + return make_intrusive(el); } QRgb FillColorProperty::rgbFromString(const QString& str) { - return QColor(str).rgb(); + return QColor(str).rgb(); } QString FillColorProperty::rgbToString(QRgb rgb) { - return QColor(rgb).name(); + return QColor(rgb).name(); } -FillColorProperty::FillColorProperty(const QColor& color) : m_rgb(color.rgb()) { -} +FillColorProperty::FillColorProperty(const QColor& color) : m_rgb(color.rgb()) {} QColor FillColorProperty::color() const { - return QColor(m_rgb); + return QColor(m_rgb); } void FillColorProperty::setColor(const QColor& color) { - m_rgb = color.rgb(); + m_rgb = color.rgb(); } } // namespace output \ No newline at end of file diff --git a/filters/output/FillColorProperty.h b/filters/output/FillColorProperty.h index e04ed4839..d827edbcc 100644 --- a/filters/output/FillColorProperty.h +++ b/filters/output/FillColorProperty.h @@ -19,10 +19,10 @@ #ifndef OUTPUT_FILL_COLOR_PROPERTY_H_ #define OUTPUT_FILL_COLOR_PROPERTY_H_ -#include "Property.h" -#include "intrusive_ptr.h" #include #include +#include "Property.h" +#include "intrusive_ptr.h" class PropertyFactory; class QDomDocument; @@ -31,31 +31,31 @@ class QString; namespace output { class FillColorProperty : public Property { -public: - explicit FillColorProperty(const QColor& color = Qt::white); + public: + explicit FillColorProperty(const QColor& color = Qt::white); - explicit FillColorProperty(const QDomElement& el); + explicit FillColorProperty(const QDomElement& el); - static void registerIn(PropertyFactory& factory); + static void registerIn(PropertyFactory& factory); - intrusive_ptr clone() const override; + intrusive_ptr clone() const override; - QDomElement toXml(QDomDocument& doc, const QString& name) const override; + QDomElement toXml(QDomDocument& doc, const QString& name) const override; - QColor color() const; + QColor color() const; - void setColor(const QColor& color); + void setColor(const QColor& color); -private: - static intrusive_ptr construct(const QDomElement& el); + private: + static intrusive_ptr construct(const QDomElement& el); - static QRgb rgbFromString(const QString& str); + static QRgb rgbFromString(const QString& str); - static QString rgbToString(QRgb rgb); + static QString rgbToString(QRgb rgb); - static const char m_propertyName[]; - QRgb m_rgb; + static const char m_propertyName[]; + QRgb m_rgb; }; } // namespace output #endif // ifndef OUTPUT_FILL_COLOR_PROPERTY_H_ diff --git a/filters/output/FillZoneComparator.cpp b/filters/output/FillZoneComparator.cpp index 54fe93e1d..e9ef4a3da 100644 --- a/filters/output/FillZoneComparator.cpp +++ b/filters/output/FillZoneComparator.cpp @@ -17,38 +17,38 @@ */ #include "FillZoneComparator.h" -#include "ZoneSet.h" #include "FillColorProperty.h" +#include "ZoneSet.h" #include "imageproc/PolygonUtils.h" using namespace imageproc; namespace output { bool FillZoneComparator::equal(const ZoneSet& lhs, const ZoneSet& rhs) { - ZoneSet::const_iterator lhs_it(lhs.begin()); - ZoneSet::const_iterator rhs_it(rhs.begin()); - const ZoneSet::const_iterator lhs_end(lhs.end()); - const ZoneSet::const_iterator rhs_end(rhs.end()); - for (; lhs_it != lhs_end && rhs_it != rhs_end; ++lhs_it, ++rhs_it) { - if (!equal(*lhs_it, *rhs_it)) { - return false; - } + ZoneSet::const_iterator lhs_it(lhs.begin()); + ZoneSet::const_iterator rhs_it(rhs.begin()); + const ZoneSet::const_iterator lhs_end(lhs.end()); + const ZoneSet::const_iterator rhs_end(rhs.end()); + for (; lhs_it != lhs_end && rhs_it != rhs_end; ++lhs_it, ++rhs_it) { + if (!equal(*lhs_it, *rhs_it)) { + return false; } + } - return lhs_it == lhs_end && rhs_it == rhs_end; + return lhs_it == lhs_end && rhs_it == rhs_end; } bool FillZoneComparator::equal(const Zone& lhs, const Zone& rhs) { - if (!PolygonUtils::fuzzyCompare(lhs.spline().toPolygon(), rhs.spline().toPolygon())) { - return false; - } + if (!PolygonUtils::fuzzyCompare(lhs.spline().toPolygon(), rhs.spline().toPolygon())) { + return false; + } - return equal(lhs.properties(), rhs.properties()); + return equal(lhs.properties(), rhs.properties()); } bool FillZoneComparator::equal(const PropertySet& lhs, const PropertySet& rhs) { - typedef FillColorProperty FCP; + typedef FillColorProperty FCP; - return lhs.locateOrDefault()->color() == rhs.locateOrDefault()->color(); + return lhs.locateOrDefault()->color() == rhs.locateOrDefault()->color(); } } // namespace output \ No newline at end of file diff --git a/filters/output/FillZoneComparator.h b/filters/output/FillZoneComparator.h index 71b4bc19f..12d528102 100644 --- a/filters/output/FillZoneComparator.h +++ b/filters/output/FillZoneComparator.h @@ -25,12 +25,12 @@ class PropertySet; namespace output { class FillZoneComparator { -public: - static bool equal(const ZoneSet& lhs, const ZoneSet& rhs); + public: + static bool equal(const ZoneSet& lhs, const ZoneSet& rhs); - static bool equal(const Zone& lhs, const Zone& rhs); + static bool equal(const Zone& lhs, const Zone& rhs); - static bool equal(const PropertySet& lhs, const PropertySet& rhs); + static bool equal(const PropertySet& lhs, const PropertySet& rhs); }; } // namespace output #endif diff --git a/filters/output/FillZoneEditor.cpp b/filters/output/FillZoneEditor.cpp index 45de5ef2d..8df82814c 100644 --- a/filters/output/FillZoneEditor.cpp +++ b/filters/output/FillZoneEditor.cpp @@ -17,31 +17,30 @@ */ #include "FillZoneEditor.h" -#include "ZoneContextMenuInteraction.h" -#include "Zone.h" -#include "ZoneSet.h" -#include "Settings.h" -#include "ImageTransformation.h" -#include "ImagePresentation.h" -#include "OutputMargins.h" -#include #include +#include #include #include +#include "ImagePresentation.h" +#include "ImageTransformation.h" +#include "OutputMargins.h" +#include "Settings.h" +#include "Zone.h" +#include "ZoneContextMenuInteraction.h" +#include "ZoneSet.h" namespace output { class FillZoneEditor::MenuCustomizer { -private: - typedef ZoneContextMenuInteraction::StandardMenuItems StdMenuItems; + private: + typedef ZoneContextMenuInteraction::StandardMenuItems StdMenuItems; -public: - explicit MenuCustomizer(FillZoneEditor* editor) : m_pEditor(editor) { - } + public: + explicit MenuCustomizer(FillZoneEditor* editor) : m_editor(editor) {} - std::vector operator()(const EditableZoneSet::Zone& zone, const StdMenuItems& std_items); + std::vector operator()(const EditableZoneSet::Zone& zone, const StdMenuItems& std_items); -private: - FillZoneEditor* m_pEditor; + private: + FillZoneEditor* m_editor; }; @@ -51,139 +50,135 @@ FillZoneEditor::FillZoneEditor(const QImage& image, const boost::function& image_to_orig, const PageId& page_id, intrusive_ptr settings) - : ImageViewBase(image, - downscaled_version, - ImagePresentation(QTransform(), QRectF(image.rect())), - OutputMargins()), - m_colorAdapter(colorAdapterFor(image)), - m_context(*this, m_zones), - m_colorPickupInteraction(m_zones, m_context), - m_dragHandler(*this), - m_zoomHandler(*this), - m_origToImage(orig_to_image), - m_imageToOrig(image_to_orig), - m_pageId(page_id), - m_ptrSettings(std::move(settings)) { - m_zones.setDefaultProperties(m_ptrSettings->defaultFillZoneProperties()); - - setMouseTracking(true); - - m_context.setContextMenuInteractionCreator(boost::bind(&FillZoneEditor::createContextMenuInteraction, this, _1)); - - connect(&m_zones, SIGNAL(committed()), SLOT(commitZones())); - - makeLastFollower(*m_context.createDefaultInteraction()); - - rootInteractionHandler().makeLastFollower(*this); - - // We want these handlers after zone interaction handlers, - // as some of those have their own drag and zoom handlers, - // which need to get events before these standard ones. - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); - - for (const Zone& zone : m_ptrSettings->fillZonesForPage(page_id)) { - auto spline = make_intrusive(zone.spline().transformed(m_origToImage)); - m_zones.addZone(spline, zone.properties()); - } + : ImageViewBase(image, downscaled_version, ImagePresentation(QTransform(), QRectF(image.rect())), OutputMargins()), + m_colorAdapter(colorAdapterFor(image)), + m_context(*this, m_zones), + m_colorPickupInteraction(m_zones, m_context), + m_dragHandler(*this), + m_zoomHandler(*this), + m_origToImage(orig_to_image), + m_imageToOrig(image_to_orig), + m_pageId(page_id), + m_settings(std::move(settings)) { + m_zones.setDefaultProperties(m_settings->defaultFillZoneProperties()); + + setMouseTracking(true); + + m_context.setContextMenuInteractionCreator(boost::bind(&FillZoneEditor::createContextMenuInteraction, this, _1)); + + connect(&m_zones, SIGNAL(committed()), SLOT(commitZones())); + + makeLastFollower(*m_context.createDefaultInteraction()); + + rootInteractionHandler().makeLastFollower(*this); + + // We want these handlers after zone interaction handlers, + // as some of those have their own drag and zoom handlers, + // which need to get events before these standard ones. + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); + + for (const Zone& zone : m_settings->fillZonesForPage(page_id)) { + auto spline = make_intrusive(zone.spline().transformed(m_origToImage)); + m_zones.addZone(spline, zone.properties()); + } } FillZoneEditor::~FillZoneEditor() { - m_ptrSettings->setDefaultFillZoneProperties(m_zones.defaultProperties()); + m_settings->setDefaultFillZoneProperties(m_zones.defaultProperties()); } void FillZoneEditor::onPaint(QPainter& painter, const InteractionState& interaction) { - if (m_colorPickupInteraction.isActive(interaction)) { - return; - } + if (m_colorPickupInteraction.isActive(interaction)) { + return; + } - painter.setRenderHint(QPainter::Antialiasing, false); + painter.setRenderHint(QPainter::Antialiasing, false); - painter.setPen(Qt::NoPen); + painter.setPen(Qt::NoPen); - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - for (const EditableZoneSet::Zone& zone : m_zones) { - typedef FillColorProperty FCP; - const QColor color(zone.properties()->locateOrDefault()->color()); - painter.setBrush(m_colorAdapter(color)); - painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); - } + for (const EditableZoneSet::Zone& zone : m_zones) { + typedef FillColorProperty FCP; + const QColor color(zone.properties()->locateOrDefault()->color()); + painter.setBrush(m_colorAdapter(color)); + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); + } } InteractionHandler* FillZoneEditor::createContextMenuInteraction(InteractionState& interaction) { - // Return a standard ZoneContextMenuInteraction but with a customized menu. - return ZoneContextMenuInteraction::create(m_context, interaction, MenuCustomizer(this)); + // Return a standard ZoneContextMenuInteraction but with a customized menu. + return ZoneContextMenuInteraction::create(m_context, interaction, MenuCustomizer(this)); } InteractionHandler* FillZoneEditor::createColorPickupInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction) { - m_colorPickupInteraction.startInteraction(zone, interaction); + m_colorPickupInteraction.startInteraction(zone, interaction); - return &m_colorPickupInteraction; + return &m_colorPickupInteraction; } void FillZoneEditor::commitZones() { - ZoneSet zones; + ZoneSet zones; - for (const EditableZoneSet::Zone& zone : m_zones) { - const SerializableSpline spline = SerializableSpline(*zone.spline()).transformed(m_imageToOrig); - zones.add(Zone(spline, *zone.properties())); - } + for (const EditableZoneSet::Zone& zone : m_zones) { + const SerializableSpline spline = SerializableSpline(*zone.spline()).transformed(m_imageToOrig); + zones.add(Zone(spline, *zone.properties())); + } - m_ptrSettings->setFillZones(m_pageId, zones); + m_settings->setFillZones(m_pageId, zones); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void FillZoneEditor::updateRequested() { - update(); + update(); } QColor FillZoneEditor::toOpaque(const QColor& color) { - QColor adapted(color); - adapted.setAlpha(0xff); + QColor adapted(color); + adapted.setAlpha(0xff); - return adapted; + return adapted; } QColor FillZoneEditor::toGrayscale(const QColor& color) { - const int gray = qGray(color.rgb()); + const int gray = qGray(color.rgb()); - return QColor(gray, gray, gray); + return QColor(gray, gray, gray); } QColor FillZoneEditor::toBlackWhite(const QColor& color) { - const int gray = qGray(color.rgb()); + const int gray = qGray(color.rgb()); - return gray < 128 ? Qt::black : Qt::white; + return gray < 128 ? Qt::black : Qt::white; } FillZoneEditor::ColorAdapter FillZoneEditor::colorAdapterFor(const QImage& image) { - switch (image.format()) { - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - return &FillZoneEditor::toBlackWhite; - case QImage::Format_Indexed8: - if (image.allGray()) { - return &FillZoneEditor::toGrayscale; - } - // fall through - default: - return &FillZoneEditor::toOpaque; - } + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + return &FillZoneEditor::toBlackWhite; + case QImage::Format_Indexed8: + if (image.allGray()) { + return &FillZoneEditor::toGrayscale; + } + // fall through + default: + return &FillZoneEditor::toOpaque; + } } /*=========================== MenuCustomizer =========================*/ std::vector FillZoneEditor::MenuCustomizer::operator()(const EditableZoneSet::Zone& zone, const StdMenuItems& std_items) { - std::vector items; - items.reserve(2); - items.emplace_back(tr("Pick color"), - boost::bind(&FillZoneEditor::createColorPickupInteraction, m_pEditor, zone, _1)); - items.push_back(std_items.deleteItem); + std::vector items; + items.reserve(2); + items.emplace_back(tr("Pick color"), boost::bind(&FillZoneEditor::createColorPickupInteraction, m_editor, zone, _1)); + items.push_back(std_items.deleteItem); - return items; + return items; } } // namespace output \ No newline at end of file diff --git a/filters/output/FillZoneEditor.h b/filters/output/FillZoneEditor.h index 6560d124f..cbed4da49 100644 --- a/filters/output/FillZoneEditor.h +++ b/filters/output/FillZoneEditor.h @@ -20,22 +20,22 @@ #ifndef OUTPUT_FILL_ZONE_EDITOR_H_ #define OUTPUT_FILL_ZONE_EDITOR_H_ -#include "ImageViewBase.h" +#include +#include +#include +#include +#include "ColorPickupInteraction.h" +#include "DragHandler.h" +#include "EditableSpline.h" +#include "EditableZoneSet.h" #include "ImagePixmapUnion.h" +#include "ImageViewBase.h" #include "NonCopyable.h" -#include "ref_countable.h" -#include "intrusive_ptr.h" #include "PageId.h" #include "ZoneInteractionContext.h" -#include "ColorPickupInteraction.h" -#include "EditableSpline.h" -#include "EditableZoneSet.h" #include "ZoomHandler.h" -#include "DragHandler.h" -#include -#include -#include -#include +#include "intrusive_ptr.h" +#include "ref_countable.h" class InteractionState; class QPainter; @@ -44,62 +44,62 @@ namespace output { class Settings; class FillZoneEditor : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - FillZoneEditor(const QImage& image, - const ImagePixmapUnion& downscaled_version, - const boost::function& orig_to_image, - const boost::function& image_to_orig, - const PageId& page_id, - intrusive_ptr settings); + Q_OBJECT + public: + FillZoneEditor(const QImage& image, + const ImagePixmapUnion& downscaled_version, + const boost::function& orig_to_image, + const boost::function& image_to_orig, + const PageId& page_id, + intrusive_ptr settings); - ~FillZoneEditor() override; + ~FillZoneEditor() override; -signals: + signals: - void invalidateThumbnail(const PageId& page_id); + void invalidateThumbnail(const PageId& page_id); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; -private slots: + private slots: - void commitZones(); + void commitZones(); - void updateRequested(); + void updateRequested(); -private: - class MenuCustomizer; + private: + class MenuCustomizer; - typedef QColor (*ColorAdapter)(const QColor&); + typedef QColor (*ColorAdapter)(const QColor&); - InteractionHandler* createContextMenuInteraction(InteractionState& interaction); + InteractionHandler* createContextMenuInteraction(InteractionState& interaction); - InteractionHandler* createColorPickupInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction); + InteractionHandler* createColorPickupInteraction(const EditableZoneSet::Zone& zone, InteractionState& interaction); - static QColor toOpaque(const QColor& color); + static QColor toOpaque(const QColor& color); - static QColor toGrayscale(const QColor& color); + static QColor toGrayscale(const QColor& color); - static QColor toBlackWhite(const QColor& color); + static QColor toBlackWhite(const QColor& color); - static ColorAdapter colorAdapterFor(const QImage& image); + static ColorAdapter colorAdapterFor(const QImage& image); - ColorAdapter m_colorAdapter; - EditableZoneSet m_zones; + ColorAdapter m_colorAdapter; + EditableZoneSet m_zones; - // Must go after m_zones. - ZoneInteractionContext m_context; - // Must go after m_context. - ColorPickupInteraction m_colorPickupInteraction; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + // Must go after m_zones. + ZoneInteractionContext m_context; + // Must go after m_context. + ColorPickupInteraction m_colorPickupInteraction; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; - boost::function m_origToImage; - boost::function m_imageToOrig; - PageId m_pageId; - intrusive_ptr m_ptrSettings; + boost::function m_origToImage; + boost::function m_imageToOrig; + PageId m_pageId; + intrusive_ptr m_settings; }; } // namespace output #endif // ifndef OUTPUT_FILL_ZONE_EDITOR_H_ diff --git a/filters/output/FillZonePropFactory.cpp b/filters/output/FillZonePropFactory.cpp index 60638850a..45b12517a 100644 --- a/filters/output/FillZonePropFactory.cpp +++ b/filters/output/FillZonePropFactory.cpp @@ -21,6 +21,6 @@ namespace output { FillZonePropFactory::FillZonePropFactory() { - FillColorProperty::registerIn(*this); + FillColorProperty::registerIn(*this); } } // namespace output diff --git a/filters/output/FillZonePropFactory.h b/filters/output/FillZonePropFactory.h index c17a86fca..1e811dda2 100644 --- a/filters/output/FillZonePropFactory.h +++ b/filters/output/FillZonePropFactory.h @@ -23,8 +23,8 @@ namespace output { class FillZonePropFactory : public PropertyFactory { -public: - FillZonePropFactory(); + public: + FillZonePropFactory(); }; } // namespace output #endif diff --git a/filters/output/Filter.cpp b/filters/output/Filter.cpp index c835d88c3..148e926fe 100644 --- a/filters/output/Filter.cpp +++ b/filters/output/Filter.cpp @@ -17,132 +17,138 @@ */ #include "Filter.h" +#include +#include +#include +#include +#include +#include +#include +#include "CacheDrivenTask.h" +#include "CommandLine.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" -#include "Task.h" -#include "Settings.h" #include "ProjectReader.h" #include "ProjectWriter.h" -#include "CacheDrivenTask.h" -#include -#include -#include -#include -#include -#include -#include "CommandLine.h" +#include "Settings.h" +#include "Task.h" #include "ThumbnailPixmapCache.h" namespace output { -Filter::Filter(const PageSelectionAccessor& page_selection_accessor) : m_ptrSettings(new Settings) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); - } +Filter::Filter(const PageSelectionAccessor& page_selection_accessor) + : m_settings(new Settings), m_selectedPageOrder(0) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, page_selection_accessor)); + } + + const PageOrderOption::ProviderPtr default_order; + const auto order_by_completeness = make_intrusive(); + m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); + m_pageOrderOptions.emplace_back(tr("Order by completeness"), order_by_completeness); } Filter::~Filter() = default; QString Filter::getName() const { - return QCoreApplication::translate("output::Filter", "Output"); + return QCoreApplication::translate("output::Filter", "Output"); } PageView Filter::getView() const { - return PAGE_VIEW; + return PAGE_VIEW; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) { - m_ptrOptionsWidget->preUpdateUI(page_info.id()); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); + m_optionsWidget->preUpdateUI(page_info.id()); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("output")); + QDomElement filter_el(doc.createElement("output")); - writer.enumPages([&](const PageId& page_id, int numeric_id) { - this->writePageSettings(doc, filter_el, page_id, numeric_id); - }); + writer.enumPages( + [&](const PageId& page_id, int numeric_id) { this->writePageSettings(doc, filter_el, page_id, numeric_id); }); - return filter_el; + return filter_el; } void Filter::writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const Params params(m_ptrSettings->getParams(page_id)); + const Params params(m_settings->getParams(page_id)); - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); - page_el.appendChild(m_ptrSettings->pictureZonesForPage(page_id).toXml(doc, "zones")); - page_el.appendChild(m_ptrSettings->fillZonesForPage(page_id).toXml(doc, "fill-zones")); - page_el.appendChild(params.toXml(doc, "params")); - page_el.appendChild(m_ptrSettings->getOutputProcessingParams(page_id).toXml(doc, "processing-params")); + page_el.appendChild(m_settings->pictureZonesForPage(page_id).toXml(doc, "zones")); + page_el.appendChild(m_settings->fillZonesForPage(page_id).toXml(doc, "fill-zones")); + page_el.appendChild(params.toXml(doc, "params")); + page_el.appendChild(m_settings->getOutputProcessingParams(page_id).toXml(doc, "processing-params")); - std::unique_ptr output_params(m_ptrSettings->getOutputParams(page_id)); - if (output_params) { - page_el.appendChild(output_params->toXml(doc, "output-params")); - } + std::unique_ptr output_params(m_settings->getOutputParams(page_id)); + if (output_params) { + page_el.appendChild(output_params->toXml(doc, "output-params")); + } - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); - - const QDomElement filter_el(filters_el.namedItem("output").toElement()); - - const QString page_tag_name("page"); - QDomNode node(filter_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const ZoneSet picture_zones(el.namedItem("zones").toElement(), m_pictureZonePropFactory); - if (!picture_zones.empty()) { - m_ptrSettings->setPictureZones(page_id, picture_zones); - } - - const ZoneSet fill_zones(el.namedItem("fill-zones").toElement(), m_fillZonePropFactory); - if (!fill_zones.empty()) { - m_ptrSettings->setFillZones(page_id, fill_zones); - } - - const QDomElement params_el(el.namedItem("params").toElement()); - if (!params_el.isNull()) { - const Params params(params_el); - m_ptrSettings->setParams(page_id, params); - } - - const QDomElement output_processing_params_el(el.namedItem("processing-params").toElement()); - if (!output_processing_params_el.isNull()) { - const OutputProcessingParams output_processing_params(output_processing_params_el); - m_ptrSettings->setOutputProcessingParams(page_id, output_processing_params); - } - - const QDomElement output_params_el(el.namedItem("output-params").toElement()); - if (!output_params_el.isNull()) { - const OutputParams output_params(output_params_el); - m_ptrSettings->setOutputParams(page_id, output_params); - } + m_settings->clear(); + + const QDomElement filter_el(filters_el.namedItem("output").toElement()); + + const QString page_tag_name("page"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; + } + + const ZoneSet picture_zones(el.namedItem("zones").toElement(), m_pictureZonePropFactory); + if (!picture_zones.empty()) { + m_settings->setPictureZones(page_id, picture_zones); + } + + const ZoneSet fill_zones(el.namedItem("fill-zones").toElement(), m_fillZonePropFactory); + if (!fill_zones.empty()) { + m_settings->setFillZones(page_id, fill_zones); + } + + const QDomElement params_el(el.namedItem("params").toElement()); + if (!params_el.isNull()) { + const Params params(params_el); + m_settings->setParams(page_id, params); + } + + const QDomElement output_processing_params_el(el.namedItem("processing-params").toElement()); + if (!output_processing_params_el.isNull()) { + const OutputProcessingParams output_processing_params(output_processing_params_el); + m_settings->setOutputProcessingParams(page_id, output_processing_params); + } + + const QDomElement output_params_el(el.namedItem("output-params").toElement()); + if (!output_params_el.isNull()) { + const OutputParams output_params(output_params_el); + m_settings->setOutputParams(page_id, output_params); + } + } } // Filter::loadSettings intrusive_ptr Filter::createTask(const PageId& page_id, @@ -150,34 +156,47 @@ intrusive_ptr Filter::createTask(const PageId& page_id, const OutputFileNameGenerator& out_file_name_gen, const bool batch, const bool debug) { - ImageViewTab lastTab(TAB_OUTPUT); - if (m_ptrOptionsWidget.get() != nullptr) { - lastTab = m_ptrOptionsWidget->lastTab(); - } + ImageViewTab lastTab(TAB_OUTPUT); + if (m_optionsWidget.get() != nullptr) { + lastTab = m_optionsWidget->lastTab(); + } - return make_intrusive(intrusive_ptr(this), m_ptrSettings, std::move(thumbnail_cache), page_id, - out_file_name_gen, lastTab, batch, debug); + return make_intrusive(intrusive_ptr(this), m_settings, std::move(thumbnail_cache), page_id, + out_file_name_gen, lastTab, batch, debug); } intrusive_ptr Filter::createCacheDrivenTask(const OutputFileNameGenerator& out_file_name_gen) { - return make_intrusive(m_ptrSettings, out_file_name_gen); + return make_intrusive(m_settings, out_file_name_gen); } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->isParamsNull(page_info.id())) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::OutputParams& outputParams = defaultParams.getOutputParams(); - - m_ptrSettings->setParams(page_info.id(), - Params(outputParams.getDpi(), outputParams.getColorParams(), - outputParams.getSplittingOptions(), outputParams.getPictureShapeOptions(), - dewarping::DistortionModel(), outputParams.getDepthPerception(), - outputParams.getDewarpingOptions(), outputParams.getDespeckleLevel())); + if (!m_settings->isParamsNull(page_info.id())) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::OutputParams& outputParams = defaultParams.getOutputParams(); + + m_settings->setParams( + page_info.id(), + Params(outputParams.getDpi(), outputParams.getColorParams(), outputParams.getSplittingOptions(), + outputParams.getPictureShapeOptions(), dewarping::DistortionModel(), outputParams.getDepthPerception(), + outputParams.getDewarpingOptions(), outputParams.getDespeckleLevel())); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); +} + +std::vector Filter::pageOrderOptions() const { + return m_pageOrderOptions; +} + +int Filter::selectedPageOrder() const { + return m_selectedPageOrder; +} + +void Filter::selectPageOrder(int option) { + assert((unsigned) option < m_pageOrderOptions.size()); + m_selectedPageOrder = option; } } // namespace output \ No newline at end of file diff --git a/filters/output/Filter.h b/filters/output/Filter.h index 8bd31127e..f9faf5cbd 100644 --- a/filters/output/Filter.h +++ b/filters/output/Filter.h @@ -19,15 +19,16 @@ #ifndef OUTPUT_FILTER_H_ #define OUTPUT_FILTER_H_ -#include "NonCopyable.h" +#include +#include #include "AbstractFilter.h" -#include "PageView.h" -#include "intrusive_ptr.h" +#include "FillZonePropFactory.h" #include "FilterResult.h" -#include "SafeDeletingQObjectPtr.h" +#include "NonCopyable.h" +#include "PageView.h" #include "PictureZonePropFactory.h" -#include "FillZonePropFactory.h" -#include +#include "SafeDeletingQObjectPtr.h" +#include "intrusive_ptr.h" class PageSelectionAccessor; class ThumbnailPixmapCache; @@ -41,44 +42,53 @@ class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) + + Q_DECLARE_TR_FUNCTIONS(output::Filter) + public: + explicit Filter(const PageSelectionAccessor& page_selection_accessor); + + ~Filter() override; + + QString getName() const override; -public: - explicit Filter(const PageSelectionAccessor& page_selection_accessor); + PageView getView() const override; - ~Filter() override; + void performRelinking(const AbstractRelinker& relinker) override; - QString getName() const override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - PageView getView() const override; + QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; - void performRelinking(const AbstractRelinker& relinker) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; + intrusive_ptr createTask(const PageId& page_id, + intrusive_ptr thumbnail_cache, + const OutputFileNameGenerator& out_file_name_gen, + bool batch, + bool debug); - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + intrusive_ptr createCacheDrivenTask(const OutputFileNameGenerator& out_file_name_gen); - void loadDefaultSettings(const PageInfo& page_info) override; + OptionsWidget* optionsWidget(); - intrusive_ptr createTask(const PageId& page_id, - intrusive_ptr thumbnail_cache, - const OutputFileNameGenerator& out_file_name_gen, - bool batch, - bool debug); + std::vector pageOrderOptions() const override; - intrusive_ptr createCacheDrivenTask(const OutputFileNameGenerator& out_file_name_gen); + int selectedPageOrder() const override; - OptionsWidget* optionsWidget(); + void selectPageOrder(int option) override; -private: - void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + private: + void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - intrusive_ptr m_ptrSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; - PictureZonePropFactory m_pictureZonePropFactory; - FillZonePropFactory m_fillZonePropFactory; + intrusive_ptr m_settings; + SafeDeletingQObjectPtr m_optionsWidget; + PictureZonePropFactory m_pictureZonePropFactory; + FillZonePropFactory m_fillZonePropFactory; + std::vector m_pageOrderOptions; + int m_selectedPageOrder; }; } // namespace output #endif // ifndef OUTPUT_FILTER_H_ diff --git a/filters/output/ImageView.cpp b/filters/output/ImageView.cpp index c7938b31d..a3c5563cf 100644 --- a/filters/output/ImageView.cpp +++ b/filters/output/ImageView.cpp @@ -22,14 +22,11 @@ namespace output { ImageView::ImageView(const QImage& image, const QImage& downscaled_image) - : ImageViewBase(image, - downscaled_image, - ImagePresentation(QTransform(), QRectF(image.rect())), - OutputMargins()), - m_dragHandler(*this), - m_zoomHandler(*this) { - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + : ImageViewBase(image, downscaled_image, ImagePresentation(QTransform(), QRectF(image.rect())), OutputMargins()), + m_dragHandler(*this), + m_zoomHandler(*this) { + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); } ImageView::~ImageView() = default; diff --git a/filters/output/ImageView.h b/filters/output/ImageView.h index dcf769976..7b0697521 100644 --- a/filters/output/ImageView.h +++ b/filters/output/ImageView.h @@ -19,24 +19,24 @@ #ifndef OUTPUT_IMAGEVIEW_H_ #define OUTPUT_IMAGEVIEW_H_ -#include "ImageViewBase.h" +#include #include "DragHandler.h" +#include "ImageViewBase.h" #include "ZoomHandler.h" -#include class ImageTransformation; namespace output { class ImageView : public ImageViewBase { - Q_OBJECT -public: - ImageView(const QImage& image, const QImage& downscaled_image); + Q_OBJECT + public: + ImageView(const QImage& image, const QImage& downscaled_image); - ~ImageView() override; + ~ImageView() override; -private: - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + private: + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; }; } // namespace output #endif diff --git a/filters/output/OptionsWidget.cpp b/filters/output/OptionsWidget.cpp index 8f5fc1e49..802a3139c 100644 --- a/filters/output/OptionsWidget.cpp +++ b/filters/output/OptionsWidget.cpp @@ -17,950 +17,987 @@ */ #include "OptionsWidget.h" -#include "ChangeDpiDialog.h" -#include "ChangeDewarpingDialog.h" +#include +#include +#include "../../Utils.h" #include "ApplyColorsDialog.h" -#include "PictureZoneComparator.h" +#include "ChangeDewarpingDialog.h" +#include "ChangeDpiDialog.h" #include "FillZoneComparator.h" #include "OtsuBinarizationOptionsWidget.h" +#include "PictureZoneComparator.h" #include "SauvolaBinarizationOptionsWidget.h" #include "WolfBinarizationOptionsWidget.h" -#include "../../Utils.h" -#include -#include namespace output { OptionsWidget::OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), - m_pageSelectionAccessor(page_selection_accessor), - m_despeckleLevel(DESPECKLE_NORMAL), - m_lastTab(TAB_OUTPUT) { - setupUi(this); + : m_settings(std::move(settings)), + m_pageSelectionAccessor(page_selection_accessor), + m_despeckleLevel(1.0), + m_lastTab(TAB_OUTPUT) { + setupUi(this); - delayedReloadRequest.setSingleShot(true); + m_delayedReloadRequest.setSingleShot(true); - depthPerceptionSlider->setMinimum(qRound(DepthPerception::minValue() * 10)); - depthPerceptionSlider->setMaximum(qRound(DepthPerception::maxValue() * 10)); + depthPerceptionSlider->setMinimum(qRound(DepthPerception::minValue() * 10)); + depthPerceptionSlider->setMaximum(qRound(DepthPerception::maxValue() * 10)); - colorModeSelector->addItem(tr("Black and White"), BLACK_AND_WHITE); - colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE); - colorModeSelector->addItem(tr("Mixed"), MIXED); + despeckleSlider->setMinimum(qRound(1.0 * 10)); + despeckleSlider->setMaximum(qRound(3.0 * 10)); - thresholdMethodBox->addItem(tr("Otsu"), OTSU); - thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); - thresholdMethodBox->addItem(tr("Wolf"), WOLF); + colorModeSelector->addItem(tr("Black and White"), BLACK_AND_WHITE); + colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE); + colorModeSelector->addItem(tr("Mixed"), MIXED); - fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND); - fillingColorBox->addItem(tr("White"), FILL_WHITE); + thresholdMethodBox->addItem(tr("Otsu"), OTSU); + thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); + thresholdMethodBox->addItem(tr("Wolf"), WOLF); - QPointer otsuBinarizationOptionsWidget - = new OtsuBinarizationOptionsWidget(m_ptrSettings); - QPointer sauvolaBinarizationOptionsWidget - = new SauvolaBinarizationOptionsWidget(m_ptrSettings); - QPointer wolfBinarizationOptionsWidget - = new WolfBinarizationOptionsWidget(m_ptrSettings); + fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND); + fillingColorBox->addItem(tr("White"), FILL_WHITE); - while (binarizationOptions->count() != 0) { - binarizationOptions->removeWidget(binarizationOptions->widget(0)); - } - addBinarizationOptionsWidget(otsuBinarizationOptionsWidget); - addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget); - addBinarizationOptionsWidget(wolfBinarizationOptionsWidget); - updateBinarizationOptionsDisplay(binarizationOptions->currentIndex()); + QPointer otsuBinarizationOptionsWidget = new OtsuBinarizationOptionsWidget(m_settings); + QPointer sauvolaBinarizationOptionsWidget + = new SauvolaBinarizationOptionsWidget(m_settings); + QPointer wolfBinarizationOptionsWidget = new WolfBinarizationOptionsWidget(m_settings); - pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE); - pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE); - pictureShapeSelector->addItem(tr("Rectangular"), RECTANGULAR_SHAPE); + while (binarizationOptions->count() != 0) { + binarizationOptions->removeWidget(binarizationOptions->widget(0)); + } + addBinarizationOptionsWidget(otsuBinarizationOptionsWidget); + addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget); + addBinarizationOptionsWidget(wolfBinarizationOptionsWidget); + updateBinarizationOptionsDisplay(binarizationOptions->currentIndex()); - updateDpiDisplay(); - updateColorsDisplay(); - updateDewarpingDisplay(); + pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE); + pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE); + pictureShapeSelector->addItem(tr("Rectangular"), RECTANGULAR_SHAPE); + + updateDpiDisplay(); + updateColorsDisplay(); + updateDewarpingDisplay(); - connect(binarizationOptions, SIGNAL(currentChanged(int)), this, SLOT(updateBinarizationOptionsDisplay(int))); + connect(binarizationOptions, SIGNAL(currentChanged(int)), this, SLOT(updateBinarizationOptionsDisplay(int))); - setupUiConnections(); + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); - - const Params params(m_ptrSettings->getParams(page_id)); - m_pageId = page_id; - m_outputDpi = params.outputDpi(); - m_colorParams = params.colorParams(); - m_splittingOptions = params.splittingOptions(); - m_pictureShapeOptions = params.pictureShapeOptions(); - m_dewarpingOptions = params.dewarpingOptions(); - m_depthPerception = params.depthPerception(); - m_despeckleLevel = params.despeckleLevel(); + removeUiConnections(); - updateDpiDisplay(); - updateColorsDisplay(); - updateDewarpingDisplay(); + const Params params(m_settings->getParams(page_id)); + m_pageId = page_id; + m_outputDpi = params.outputDpi(); + m_colorParams = params.colorParams(); + m_splittingOptions = params.splittingOptions(); + m_pictureShapeOptions = params.pictureShapeOptions(); + m_dewarpingOptions = params.dewarpingOptions(); + m_depthPerception = params.depthPerception(); + m_despeckleLevel = params.despeckleLevel(); + + updateDpiDisplay(); + updateColorsDisplay(); + updateDewarpingDisplay(); + updateProcessingDisplay(); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::postUpdateUI() { - removeUiConnections(); + removeUiConnections(); - m_colorParams = m_ptrSettings->getParams(m_pageId).colorParams(); - updateColorsDisplay(); + updateProcessingDisplay(); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::tabChanged(const ImageViewTab tab) { - m_lastTab = tab; - updateDpiDisplay(); - updateColorsDisplay(); - updateDewarpingDisplay(); - reloadIfNecessary(); + m_lastTab = tab; + updateDpiDisplay(); + updateColorsDisplay(); + updateDewarpingDisplay(); + reloadIfNecessary(); } void OptionsWidget::distortionModelChanged(const dewarping::DistortionModel& model) { - m_ptrSettings->setDistortionModel(m_pageId, model); + m_settings->setDistortionModel(m_pageId, model); - m_dewarpingOptions.setDewarpingMode(MANUAL); - m_ptrSettings->setDewarpingOptions(m_pageId, m_dewarpingOptions); - updateDewarpingDisplay(); + m_dewarpingOptions.setDewarpingMode(MANUAL); + m_settings->setDewarpingOptions(m_pageId, m_dewarpingOptions); + updateDewarpingDisplay(); } void OptionsWidget::colorModeChanged(const int idx) { - const int mode = colorModeSelector->itemData(idx).toInt(); - m_colorParams.setColorMode((ColorMode) mode); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - updateColorsDisplay(); - emit reloadRequested(); + const int mode = colorModeSelector->itemData(idx).toInt(); + m_colorParams.setColorMode((ColorMode) mode); + m_settings->setColorParams(m_pageId, m_colorParams); + updateColorsDisplay(); + emit reloadRequested(); } void OptionsWidget::thresholdMethodChanged(int idx) { - const BinarizationMethod method = (BinarizationMethod) thresholdMethodBox->itemData(idx).toInt(); - BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); - blackWhiteOptions.setBinarizationMethod(method); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + const BinarizationMethod method = (BinarizationMethod) thresholdMethodBox->itemData(idx).toInt(); + BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); + blackWhiteOptions.setBinarizationMethod(method); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::fillingColorChanged(int idx) { - const FillingColor color = (FillingColor) fillingColorBox->itemData(idx).toInt(); - ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); - colorCommonOptions.setFillingColor(color); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + const FillingColor color = (FillingColor) fillingColorBox->itemData(idx).toInt(); + ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); + colorCommonOptions.setFillingColor(color); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::pictureShapeChanged(const int idx) { - const auto shapeMode = static_cast(pictureShapeSelector->itemData(idx).toInt()); - m_pictureShapeOptions.setPictureShape(shapeMode); - m_ptrSettings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); + const auto shapeMode = static_cast(pictureShapeSelector->itemData(idx).toInt()); + m_pictureShapeOptions.setPictureShape(shapeMode); + m_settings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); - pictureShapeSensitivityOptions->setVisible(shapeMode == RECTANGULAR_SHAPE); - higherSearchSensitivityCB->setVisible(shapeMode != OFF_SHAPE); + pictureShapeSensitivityOptions->setVisible(shapeMode == RECTANGULAR_SHAPE); + higherSearchSensitivityCB->setVisible(shapeMode != OFF_SHAPE); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::pictureShapeSensitivityChanged(int value) { - m_pictureShapeOptions.setSensitivity(value); - m_ptrSettings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); + m_pictureShapeOptions.setSensitivity(value); + m_settings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::higherSearchSensivityToggled(const bool checked) { - m_pictureShapeOptions.setHigherSearchSensitivity(checked); - m_ptrSettings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); + m_pictureShapeOptions.setHigherSearchSensitivity(checked); + m_settings->setPictureShapeOptions(m_pageId, m_pictureShapeOptions); - emit reloadRequested(); + emit reloadRequested(); } -void OptionsWidget::cutMarginsToggled(const bool checked) { - ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); - colorCommonOptions.setCutMargins(checked); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); +void OptionsWidget::fillMarginsToggled(const bool checked) { + ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); + colorCommonOptions.setFillMargins(checked); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); +} + +void OptionsWidget::fillOffcutToggled(const bool checked) { + ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); + colorCommonOptions.setFillOffcut(checked); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); } void OptionsWidget::equalizeIlluminationToggled(const bool checked) { - BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); - blackWhiteOptions.setNormalizeIllumination(checked); - - if (m_colorParams.colorMode() == MIXED) { - if (!checked) { - ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); - colorCommonOptions.setNormalizeIllumination(false); - equalizeIlluminationColorCB->setChecked(false); - m_colorParams.setColorCommonOptions(colorCommonOptions); - } - equalizeIlluminationColorCB->setEnabled(checked); + BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); + blackWhiteOptions.setNormalizeIllumination(checked); + + if (m_colorParams.colorMode() == MIXED) { + if (!checked) { + ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); + colorCommonOptions.setNormalizeIllumination(false); + equalizeIlluminationColorCB->setChecked(false); + m_colorParams.setColorCommonOptions(colorCommonOptions); } + equalizeIlluminationColorCB->setEnabled(checked); + } - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); } void OptionsWidget::equalizeIlluminationColorToggled(const bool checked) { - ColorCommonOptions opt(m_colorParams.colorCommonOptions()); - opt.setNormalizeIllumination(checked); - m_colorParams.setColorCommonOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + ColorCommonOptions opt(m_colorParams.colorCommonOptions()); + opt.setNormalizeIllumination(checked); + m_colorParams.setColorCommonOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); } void OptionsWidget::binarizationSettingsChanged() { - emit reloadRequested(); - emit invalidateThumbnail(m_pageId); + emit reloadRequested(); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::changeDpiButtonClicked() { - auto* dialog = new ChangeDpiDialog(this, m_outputDpi, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(accepted(const std::set&, const Dpi&)), this, - SLOT(dpiChanged(const std::set&, const Dpi&))); - dialog->show(); + auto* dialog = new ChangeDpiDialog(this, m_outputDpi, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(accepted(const std::set&, const Dpi&)), this, + SLOT(dpiChanged(const std::set&, const Dpi&))); + dialog->show(); } void OptionsWidget::applyColorsButtonClicked() { - auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(accepted(const std::set&)), this, - SLOT(applyColorsConfirmed(const std::set&))); - dialog->show(); + auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(accepted(const std::set&)), this, SLOT(applyColorsConfirmed(const std::set&))); + dialog->show(); } void OptionsWidget::dpiChanged(const std::set& pages, const Dpi& dpi) { - for (const PageId& page_id : pages) { - m_ptrSettings->setDpi(page_id, dpi); - } + for (const PageId& page_id : pages) { + m_settings->setDpi(page_id, dpi); + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } - if (pages.find(m_pageId) != pages.end()) { - m_outputDpi = dpi; - updateDpiDisplay(); - emit reloadRequested(); - } + if (pages.find(m_pageId) != pages.end()) { + m_outputDpi = dpi; + updateDpiDisplay(); + emit reloadRequested(); + } } void OptionsWidget::applyColorsConfirmed(const std::set& pages) { + for (const PageId& page_id : pages) { + m_settings->setColorParams(page_id, m_colorParams); + m_settings->setPictureShapeOptions(page_id, m_pictureShapeOptions); + } + + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { for (const PageId& page_id : pages) { - m_ptrSettings->setColorParams(page_id, m_colorParams); - m_ptrSettings->setPictureShapeOptions(page_id, m_pictureShapeOptions); + emit invalidateThumbnail(page_id); } + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } - } - - if (pages.find(m_pageId) != pages.end()) { - emit reloadRequested(); - } + if (pages.find(m_pageId) != pages.end()) { + emit reloadRequested(); + } } void OptionsWidget::applySplittingButtonClicked() { - auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Splitting Settings")); - connect(dialog, SIGNAL(accepted(const std::set&)), this, - SLOT(applySplittingOptionsConfirmed(const std::set&))); - dialog->show(); + auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Splitting Settings")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, + SLOT(applySplittingOptionsConfirmed(const std::set&))); + dialog->show(); } void OptionsWidget::applySplittingOptionsConfirmed(const std::set& pages) { - for (const PageId& page_id : pages) { - m_ptrSettings->setSplittingOptions(page_id, m_splittingOptions); - } + for (const PageId& page_id : pages) { + m_settings->setSplittingOptions(page_id, m_splittingOptions); + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } - if (pages.find(m_pageId) != pages.end()) { - emit reloadRequested(); - } + if (pages.find(m_pageId) != pages.end()) { + emit reloadRequested(); + } } -void OptionsWidget::despeckleOffSelected() { - handleDespeckleLevelChange(DESPECKLE_OFF); -} +void OptionsWidget::despeckleToggled(bool checked) { + if (checked) { + handleDespeckleLevelChange(0.1 * despeckleSlider->value()); + } else { + handleDespeckleLevelChange(0); + }; -void OptionsWidget::despeckleCautiousSelected() { - handleDespeckleLevelChange(DESPECKLE_CAUTIOUS); + despeckleSlider->setEnabled(checked); } -void OptionsWidget::despeckleNormalSelected() { - handleDespeckleLevelChange(DESPECKLE_NORMAL); +void OptionsWidget::despeckleSliderReleased() { + const double value = 0.1 * despeckleSlider->value(); + handleDespeckleLevelChange(value); } -void OptionsWidget::despeckleAggressiveSelected() { - handleDespeckleLevelChange(DESPECKLE_AGGRESSIVE); +void OptionsWidget::despeckleSliderValueChanged(int value) { + const double new_value = 0.1 * value; + + const QString tooltip_text(QString::number(new_value)); + despeckleSlider->setToolTip(tooltip_text); + + // Show the tooltip immediately. + const QPoint center(despeckleSlider->rect().center()); + QPoint tooltip_pos(despeckleSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), despeckleSlider->width())); + tooltip_pos = despeckleSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, despeckleSlider); + + if (despeckleSlider->isSliderDown()) { + return; + } + + handleDespeckleLevelChange(new_value, true); } -void OptionsWidget::handleDespeckleLevelChange(const DespeckleLevel level) { - m_despeckleLevel = level; - m_ptrSettings->setDespeckleLevel(m_pageId, level); +void OptionsWidget::handleDespeckleLevelChange(const double level, const bool delay) { + m_despeckleLevel = level; + m_settings->setDespeckleLevel(m_pageId, level); - bool handled = false; - emit despeckleLevelChanged(level, &handled); + bool handled = false; + emit despeckleLevelChanged(level, &handled); - if (handled) { - // This means we are on the "Despeckling" tab. - emit invalidateThumbnail(m_pageId); + if (handled) { + // This means we are on the "Despeckling" tab. + emit invalidateThumbnail(m_pageId); + } else { + if (delay) { + m_delayedReloadRequest.start(750); } else { - emit reloadRequested(); + emit reloadRequested(); } + } } void OptionsWidget::applyDespeckleButtonClicked() { - auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Despeckling Level")); - connect(dialog, SIGNAL(accepted(const std::set&)), this, - SLOT(applyDespeckleConfirmed(const std::set&))); - dialog->show(); + auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Despeckling Level")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, + SLOT(applyDespeckleConfirmed(const std::set&))); + dialog->show(); } void OptionsWidget::applyDespeckleConfirmed(const std::set& pages) { - for (const PageId& page_id : pages) { - m_ptrSettings->setDespeckleLevel(page_id, m_despeckleLevel); - } + for (const PageId& page_id : pages) { + m_settings->setDespeckleLevel(page_id, m_despeckleLevel); + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } - if (pages.find(m_pageId) != pages.end()) { - emit reloadRequested(); - } + if (pages.find(m_pageId) != pages.end()) { + emit reloadRequested(); + } } void OptionsWidget::changeDewarpingButtonClicked() { - auto* dialog = new ChangeDewarpingDialog(this, m_pageId, m_dewarpingOptions, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(accepted(const std::set&, const DewarpingOptions&)), this, - SLOT(dewarpingChanged(const std::set&, const DewarpingOptions&))); - dialog->show(); + auto* dialog = new ChangeDewarpingDialog(this, m_pageId, m_dewarpingOptions, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(accepted(const std::set&, const DewarpingOptions&)), this, + SLOT(dewarpingChanged(const std::set&, const DewarpingOptions&))); + dialog->show(); } void OptionsWidget::dewarpingChanged(const std::set& pages, const DewarpingOptions& opt) { + for (const PageId& page_id : pages) { + m_settings->setDewarpingOptions(page_id, opt); + } + + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { for (const PageId& page_id : pages) { - m_ptrSettings->setDewarpingOptions(page_id, opt); + emit invalidateThumbnail(page_id); } + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } - } + if (pages.find(m_pageId) != pages.end()) { + if (m_dewarpingOptions != opt) { + m_dewarpingOptions = opt; - if (pages.find(m_pageId) != pages.end()) { - if (m_dewarpingOptions != opt) { - m_dewarpingOptions = opt; - - - // We also have to reload if we are currently on the "Fill Zones" tab, - // as it makes use of original <-> dewarped coordinate mapping, - // which is too hard to update without reloading. For consistency, - // we reload not just on TAB_FILL_ZONES but on all tabs except TAB_DEWARPING. - // PS: the static original <-> dewarped mappings are constructed - // in Task::UiUpdater::updateUI(). Look for "new DewarpingPointMapper" there. - if ((opt.dewarpingMode() == AUTO) || (m_lastTab != TAB_DEWARPING) || (opt.dewarpingMode() == MARGINAL)) { - // Switch to the Output tab after reloading. - m_lastTab = TAB_OUTPUT; - // These depend on the value of m_lastTab. - updateDpiDisplay(); - updateColorsDisplay(); - updateDewarpingDisplay(); - - emit reloadRequested(); - } else { - // This one we have to call anyway, as it depends on m_dewarpingMode. - updateDewarpingDisplay(); - } - } + + // We also have to reload if we are currently on the "Fill Zones" tab, + // as it makes use of original <-> dewarped coordinate mapping, + // which is too hard to update without reloading. For consistency, + // we reload not just on TAB_FILL_ZONES but on all tabs except TAB_DEWARPING. + // PS: the static original <-> dewarped mappings are constructed + // in Task::UiUpdater::updateUI(). Look for "new DewarpingPointMapper" there. + if ((opt.dewarpingMode() == AUTO) || (m_lastTab != TAB_DEWARPING) || (opt.dewarpingMode() == MARGINAL)) { + // Switch to the Output tab after reloading. + m_lastTab = TAB_OUTPUT; + // These depend on the value of m_lastTab. + updateDpiDisplay(); + updateColorsDisplay(); + updateDewarpingDisplay(); + + emit reloadRequested(); + } else { + // This one we have to call anyway, as it depends on m_dewarpingMode. + updateDewarpingDisplay(); + } } + } } // OptionsWidget::dewarpingChanged void OptionsWidget::applyDepthPerceptionButtonClicked() { - auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Depth Perception")); - connect(dialog, SIGNAL(accepted(const std::set&)), this, - SLOT(applyDepthPerceptionConfirmed(const std::set&))); - dialog->show(); + auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Depth Perception")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, + SLOT(applyDepthPerceptionConfirmed(const std::set&))); + dialog->show(); } void OptionsWidget::applyDepthPerceptionConfirmed(const std::set& pages) { - for (const PageId& page_id : pages) { - m_ptrSettings->setDepthPerception(page_id, m_depthPerception); - } + for (const PageId& page_id : pages) { + m_settings->setDepthPerception(page_id, m_depthPerception); + } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } - if (pages.find(m_pageId) != pages.end()) { - emit reloadRequested(); - } + if (pages.find(m_pageId) != pages.end()) { + emit reloadRequested(); + } } void OptionsWidget::depthPerceptionChangedSlot(int val) { - m_depthPerception.setValue(0.1 * val); - const QString tooltip_text(QString::number(m_depthPerception.value())); - depthPerceptionSlider->setToolTip(tooltip_text); + m_depthPerception.setValue(0.1 * val); + const QString tooltip_text(QString::number(m_depthPerception.value())); + depthPerceptionSlider->setToolTip(tooltip_text); - // Show the tooltip immediately. - const QPoint center(depthPerceptionSlider->rect().center()); - QPoint tooltip_pos(depthPerceptionSlider->mapFromGlobal(QCursor::pos())); - tooltip_pos.setY(center.y()); - tooltip_pos.setX(qBound(0, tooltip_pos.x(), depthPerceptionSlider->width())); - tooltip_pos = depthPerceptionSlider->mapToGlobal(tooltip_pos); - QToolTip::showText(tooltip_pos, tooltip_text, depthPerceptionSlider); + // Show the tooltip immediately. + const QPoint center(depthPerceptionSlider->rect().center()); + QPoint tooltip_pos(depthPerceptionSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), depthPerceptionSlider->width())); + tooltip_pos = depthPerceptionSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, depthPerceptionSlider); - m_ptrSettings->setDepthPerception(m_pageId, m_depthPerception); - // Propagate the signal. - emit depthPerceptionChanged(m_depthPerception.value()); + m_settings->setDepthPerception(m_pageId, m_depthPerception); + // Propagate the signal. + emit depthPerceptionChanged(m_depthPerception.value()); } void OptionsWidget::reloadIfNecessary() { - ZoneSet saved_picture_zones; - ZoneSet saved_fill_zones; - DewarpingOptions saved_dewarping_options; - dewarping::DistortionModel saved_distortion_model; - DepthPerception saved_depth_perception; - DespeckleLevel saved_despeckle_level = DESPECKLE_CAUTIOUS; - - std::unique_ptr output_params(m_ptrSettings->getOutputParams(m_pageId)); - if (output_params) { - saved_picture_zones = output_params->pictureZones(); - saved_fill_zones = output_params->fillZones(); - saved_dewarping_options = output_params->outputImageParams().dewarpingMode(); - saved_distortion_model = output_params->outputImageParams().distortionModel(); - saved_depth_perception = output_params->outputImageParams().depthPerception(); - saved_despeckle_level = output_params->outputImageParams().despeckleLevel(); - } - - if (!PictureZoneComparator::equal(saved_picture_zones, m_ptrSettings->pictureZonesForPage(m_pageId))) { - emit reloadRequested(); + ZoneSet saved_picture_zones; + ZoneSet saved_fill_zones; + DewarpingOptions saved_dewarping_options; + dewarping::DistortionModel saved_distortion_model; + DepthPerception saved_depth_perception; + double saved_despeckle_level = 1.0; + + std::unique_ptr output_params(m_settings->getOutputParams(m_pageId)); + if (output_params) { + saved_picture_zones = output_params->pictureZones(); + saved_fill_zones = output_params->fillZones(); + saved_dewarping_options = output_params->outputImageParams().dewarpingMode(); + saved_distortion_model = output_params->outputImageParams().distortionModel(); + saved_depth_perception = output_params->outputImageParams().depthPerception(); + saved_despeckle_level = output_params->outputImageParams().despeckleLevel(); + } + + if (!PictureZoneComparator::equal(saved_picture_zones, m_settings->pictureZonesForPage(m_pageId))) { + emit reloadRequested(); - return; - } else if (!FillZoneComparator::equal(saved_fill_zones, m_ptrSettings->fillZonesForPage(m_pageId))) { - emit reloadRequested(); + return; + } else if (!FillZoneComparator::equal(saved_fill_zones, m_settings->fillZonesForPage(m_pageId))) { + emit reloadRequested(); - return; - } + return; + } - const Params params(m_ptrSettings->getParams(m_pageId)); + const Params params(m_settings->getParams(m_pageId)); - if (saved_despeckle_level != params.despeckleLevel()) { - emit reloadRequested(); + if (saved_despeckle_level != params.despeckleLevel()) { + emit reloadRequested(); - return; - } + return; + } - if ((saved_dewarping_options.dewarpingMode() == OFF) && (params.dewarpingOptions().dewarpingMode() == OFF)) { - } else if (saved_depth_perception.value() != params.depthPerception().value()) { - emit reloadRequested(); + if ((saved_dewarping_options.dewarpingMode() == OFF) && (params.dewarpingOptions().dewarpingMode() == OFF)) { + } else if (saved_depth_perception.value() != params.depthPerception().value()) { + emit reloadRequested(); - return; - } else if ((saved_dewarping_options.dewarpingMode() == AUTO) - && (params.dewarpingOptions().dewarpingMode() == AUTO)) { - } else if ((saved_dewarping_options.dewarpingMode() == MARGINAL) - && (params.dewarpingOptions().dewarpingMode() == MARGINAL)) { - } else if (!saved_distortion_model.matches(params.distortionModel())) { - emit reloadRequested(); + return; + } else if ((saved_dewarping_options.dewarpingMode() == AUTO) && (params.dewarpingOptions().dewarpingMode() == AUTO)) { + } else if ((saved_dewarping_options.dewarpingMode() == MARGINAL) + && (params.dewarpingOptions().dewarpingMode() == MARGINAL)) { + } else if (!saved_distortion_model.matches(params.distortionModel())) { + emit reloadRequested(); - return; - } else if ((saved_dewarping_options.dewarpingMode() == OFF) != (params.dewarpingOptions().dewarpingMode() == OFF)) { - emit reloadRequested(); + return; + } else if ((saved_dewarping_options.dewarpingMode() == OFF) != (params.dewarpingOptions().dewarpingMode() == OFF)) { + emit reloadRequested(); - return; - } + return; + } } // OptionsWidget::reloadIfNecessary void OptionsWidget::updateDpiDisplay() { - if (m_outputDpi.horizontal() != m_outputDpi.vertical()) { - dpiLabel->setText(QString::fromLatin1("%1 x %2").arg(m_outputDpi.horizontal()).arg(m_outputDpi.vertical())); - } else { - dpiLabel->setText(QString::number(m_outputDpi.horizontal())); - } + if (m_outputDpi.horizontal() != m_outputDpi.vertical()) { + dpiLabel->setText(QString::fromLatin1("%1 x %2").arg(m_outputDpi.horizontal()).arg(m_outputDpi.vertical())); + } else { + dpiLabel->setText(QString::number(m_outputDpi.horizontal())); + } } void OptionsWidget::updateColorsDisplay() { - colorModeSelector->blockSignals(true); - - const ColorMode color_mode = m_colorParams.colorMode(); - const int color_mode_idx = colorModeSelector->findData(color_mode); - colorModeSelector->setCurrentIndex(color_mode_idx); - - bool threshold_options_visible = false; - bool picture_shape_visible = false; - bool splitting_options_visible = false; - switch (color_mode) { - case MIXED: - picture_shape_visible = true; - splitting_options_visible = true; - // fall into - case BLACK_AND_WHITE: - threshold_options_visible = true; - // fall into - case COLOR_GRAYSCALE: - break; - } - - commonOptions->setVisible(true); - ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); - BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); - - if (!blackWhiteOptions.normalizeIllumination() && color_mode == MIXED) { - colorCommonOptions.setNormalizeIllumination(false); - } - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - - cutMarginsCB->setChecked(colorCommonOptions.cutMargins()); - cutMarginsCB->setVisible(true); - equalizeIlluminationCB->setChecked(blackWhiteOptions.normalizeIllumination()); - equalizeIlluminationCB->setVisible(color_mode != COLOR_GRAYSCALE); - equalizeIlluminationColorCB->setChecked(colorCommonOptions.normalizeIllumination()); - equalizeIlluminationColorCB->setVisible(color_mode != BLACK_AND_WHITE); - equalizeIlluminationColorCB->setEnabled(color_mode == COLOR_GRAYSCALE || blackWhiteOptions.normalizeIllumination()); - savitzkyGolaySmoothingCB->setChecked(blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()); - savitzkyGolaySmoothingCB->setVisible(threshold_options_visible); - morphologicalSmoothingCB->setChecked(blackWhiteOptions.isMorphologicalSmoothingEnabled()); - morphologicalSmoothingCB->setVisible(threshold_options_visible); - - modePanel->setVisible(m_lastTab != TAB_DEWARPING); - pictureShapeOptions->setVisible(picture_shape_visible); - thresholdOptions->setVisible(threshold_options_visible); - despecklePanel->setVisible(threshold_options_visible && m_lastTab != TAB_DEWARPING); - - splittingOptions->setVisible(splitting_options_visible); - splittingCB->setChecked(m_splittingOptions.isSplitOutput()); - switch (m_splittingOptions.getSplittingMode()) { - case BLACK_AND_WHITE_FOREGROUND: - bwForegroundRB->setChecked(true); - break; - case COLOR_FOREGROUND: - colorForegroundRB->setChecked(true); - break; - } - originalBackgroundCB->setChecked(m_splittingOptions.isOriginalBackground()); - colorForegroundRB->setEnabled(m_splittingOptions.isSplitOutput()); - bwForegroundRB->setEnabled(m_splittingOptions.isSplitOutput()); - originalBackgroundCB->setEnabled(m_splittingOptions.isSplitOutput() - && (m_splittingOptions.getSplittingMode() == BLACK_AND_WHITE_FOREGROUND)); - - thresholdMethodBox->setCurrentIndex((int) blackWhiteOptions.getBinarizationMethod()); - binarizationOptions->setCurrentIndex((int) blackWhiteOptions.getBinarizationMethod()); - - fillingOptions->setVisible(color_mode != BLACK_AND_WHITE); - fillingColorBox->setCurrentIndex((int) colorCommonOptions.getFillingColor()); - - colorSegmentationCB->setVisible(threshold_options_visible); - segmenterOptionsWidget->setVisible(threshold_options_visible); - segmenterOptionsWidget->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); - if (threshold_options_visible) { - posterizeCB->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); - posterizeOptionsWidget->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled() - && colorCommonOptions.getPosterizationOptions().isEnabled()); + colorModeSelector->blockSignals(true); + + const ColorMode color_mode = m_colorParams.colorMode(); + const int color_mode_idx = colorModeSelector->findData(color_mode); + colorModeSelector->setCurrentIndex(color_mode_idx); + + bool threshold_options_visible = false; + bool picture_shape_visible = false; + bool splitting_options_visible = false; + switch (color_mode) { + case MIXED: + picture_shape_visible = true; + splitting_options_visible = true; + // fall into + case BLACK_AND_WHITE: + threshold_options_visible = true; + // fall into + case COLOR_GRAYSCALE: + break; + } + + commonOptions->setVisible(true); + ColorCommonOptions colorCommonOptions(m_colorParams.colorCommonOptions()); + BlackWhiteOptions blackWhiteOptions(m_colorParams.blackWhiteOptions()); + + if (!blackWhiteOptions.normalizeIllumination() && color_mode == MIXED) { + colorCommonOptions.setNormalizeIllumination(false); + } + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); + + fillMarginsCB->setChecked(colorCommonOptions.fillMargins()); + fillMarginsCB->setVisible(true); + fillOffcutCB->setChecked(colorCommonOptions.fillOffcut()); + fillOffcutCB->setVisible(true); + equalizeIlluminationCB->setChecked(blackWhiteOptions.normalizeIllumination()); + equalizeIlluminationCB->setVisible(color_mode != COLOR_GRAYSCALE); + equalizeIlluminationColorCB->setChecked(colorCommonOptions.normalizeIllumination()); + equalizeIlluminationColorCB->setVisible(color_mode != BLACK_AND_WHITE); + equalizeIlluminationColorCB->setEnabled(color_mode == COLOR_GRAYSCALE || blackWhiteOptions.normalizeIllumination()); + savitzkyGolaySmoothingCB->setChecked(blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()); + savitzkyGolaySmoothingCB->setVisible(threshold_options_visible); + morphologicalSmoothingCB->setChecked(blackWhiteOptions.isMorphologicalSmoothingEnabled()); + morphologicalSmoothingCB->setVisible(threshold_options_visible); + + modePanel->setVisible(m_lastTab != TAB_DEWARPING); + pictureShapeOptions->setVisible(picture_shape_visible); + thresholdOptions->setVisible(threshold_options_visible); + despecklePanel->setVisible(threshold_options_visible && m_lastTab != TAB_DEWARPING); + + splittingOptions->setVisible(splitting_options_visible); + splittingCB->setChecked(m_splittingOptions.isSplitOutput()); + switch (m_splittingOptions.getSplittingMode()) { + case BLACK_AND_WHITE_FOREGROUND: + bwForegroundRB->setChecked(true); + break; + case COLOR_FOREGROUND: + colorForegroundRB->setChecked(true); + break; + } + originalBackgroundCB->setChecked(m_splittingOptions.isOriginalBackgroundEnabled()); + colorForegroundRB->setEnabled(m_splittingOptions.isSplitOutput()); + bwForegroundRB->setEnabled(m_splittingOptions.isSplitOutput()); + originalBackgroundCB->setEnabled(m_splittingOptions.isSplitOutput() + && (m_splittingOptions.getSplittingMode() == BLACK_AND_WHITE_FOREGROUND)); + + thresholdMethodBox->setCurrentIndex((int) blackWhiteOptions.getBinarizationMethod()); + binarizationOptions->setCurrentIndex((int) blackWhiteOptions.getBinarizationMethod()); + + fillingOptions->setVisible(color_mode != BLACK_AND_WHITE); + fillingColorBox->setCurrentIndex((int) colorCommonOptions.getFillingColor()); + + colorSegmentationCB->setVisible(threshold_options_visible); + segmenterOptionsWidget->setVisible(threshold_options_visible); + segmenterOptionsWidget->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); + if (threshold_options_visible) { + posterizeCB->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); + posterizeOptionsWidget->setEnabled(blackWhiteOptions.getColorSegmenterOptions().isEnabled() + && colorCommonOptions.getPosterizationOptions().isEnabled()); + } else { + posterizeCB->setEnabled(true); + posterizeOptionsWidget->setEnabled(colorCommonOptions.getPosterizationOptions().isEnabled()); + } + colorSegmentationCB->setChecked(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); + reduceNoiseSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getNoiseReduction()); + redAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getRedThresholdAdjustment()); + greenAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getGreenThresholdAdjustment()); + blueAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getBlueThresholdAdjustment()); + posterizeCB->setChecked(colorCommonOptions.getPosterizationOptions().isEnabled()); + posterizeLevelSB->setValue(colorCommonOptions.getPosterizationOptions().getLevel()); + posterizeNormalizationCB->setChecked(colorCommonOptions.getPosterizationOptions().isNormalizationEnabled()); + posterizeForceBwCB->setChecked(colorCommonOptions.getPosterizationOptions().isForceBlackAndWhite()); + + if (picture_shape_visible) { + const int picture_shape_idx = pictureShapeSelector->findData(m_pictureShapeOptions.getPictureShape()); + pictureShapeSelector->setCurrentIndex(picture_shape_idx); + pictureShapeSensitivitySB->setValue(m_pictureShapeOptions.getSensitivity()); + pictureShapeSensitivityOptions->setVisible(m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE); + higherSearchSensitivityCB->setChecked(m_pictureShapeOptions.isHigherSearchSensitivity()); + higherSearchSensitivityCB->setVisible(m_pictureShapeOptions.getPictureShape() != OFF_SHAPE); + } + + if (threshold_options_visible) { + if (m_despeckleLevel != 0) { + despeckleCB->setChecked(true); + despeckleSlider->setValue(qRound(10 * m_despeckleLevel)); } else { - posterizeCB->setEnabled(true); - posterizeOptionsWidget->setEnabled(colorCommonOptions.getPosterizationOptions().isEnabled()); - } - colorSegmentationCB->setChecked(blackWhiteOptions.getColorSegmenterOptions().isEnabled()); - reduceNoiseSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getNoiseReduction()); - redAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getRedThresholdAdjustment()); - greenAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getGreenThresholdAdjustment()); - blueAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getBlueThresholdAdjustment()); - posterizeCB->setChecked(colorCommonOptions.getPosterizationOptions().isEnabled()); - posterizeLevelSB->setValue(colorCommonOptions.getPosterizationOptions().getLevel()); - posterizeNormalizationCB->setChecked(colorCommonOptions.getPosterizationOptions().isNormalizationEnabled()); - posterizeForceBwCB->setChecked(colorCommonOptions.getPosterizationOptions().isForceBlackAndWhite()); - - if (picture_shape_visible) { - const int picture_shape_idx = pictureShapeSelector->findData(m_pictureShapeOptions.getPictureShape()); - pictureShapeSelector->setCurrentIndex(picture_shape_idx); - pictureShapeSensitivitySB->setValue(m_pictureShapeOptions.getSensitivity()); - pictureShapeSensitivityOptions->setVisible(m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE); - higherSearchSensitivityCB->setChecked(m_pictureShapeOptions.isHigherSearchSensitivity()); - higherSearchSensitivityCB->setVisible(m_pictureShapeOptions.getPictureShape() != OFF_SHAPE); + despeckleCB->setChecked(false); } + despeckleSlider->setEnabled(m_despeckleLevel != 0); + despeckleSlider->setToolTip(QString::number(0.1 * despeckleSlider->value())); - if (threshold_options_visible) { - switch (m_despeckleLevel) { - case DESPECKLE_OFF: - despeckleOffBtn->setChecked(true); - break; - case DESPECKLE_CAUTIOUS: - despeckleCautiousBtn->setChecked(true); - break; - case DESPECKLE_NORMAL: - despeckleNormalBtn->setChecked(true); - break; - case DESPECKLE_AGGRESSIVE: - despeckleAggressiveBtn->setChecked(true); - break; - } - - for (int i = 0; i < binarizationOptions->count(); i++) { - auto* widget = dynamic_cast(binarizationOptions->widget(i)); - widget->preUpdateUI(m_pageId); - } + for (int i = 0; i < binarizationOptions->count(); i++) { + auto* widget = dynamic_cast(binarizationOptions->widget(i)); + widget->updateUi(m_pageId); } + } - colorModeSelector->blockSignals(false); + colorModeSelector->blockSignals(false); } // OptionsWidget::updateColorsDisplay void OptionsWidget::updateDewarpingDisplay() { - depthPerceptionPanel->setVisible(m_lastTab == TAB_DEWARPING); - - switch (m_dewarpingOptions.dewarpingMode()) { - case OFF: - dewarpingStatusLabel->setText(tr("Off")); - break; - case AUTO: - dewarpingStatusLabel->setText(tr("Auto")); - break; - case MANUAL: - dewarpingStatusLabel->setText(tr("Manual")); - break; - case MARGINAL: - dewarpingStatusLabel->setText(tr("Marginal")); - break; - } - if (!m_dewarpingOptions.needPostDeskew() - && ((m_dewarpingOptions.dewarpingMode() == MANUAL) || (m_dewarpingOptions.dewarpingMode() == MARGINAL))) { - dewarpingStatusLabel->setText( - dewarpingStatusLabel->text().append(" (").append(tr("deskew disabled")).append(")")); - } - - depthPerceptionSlider->blockSignals(true); - depthPerceptionSlider->setValue(qRound(m_depthPerception.value() * 10)); - depthPerceptionSlider->blockSignals(false); + depthPerceptionPanel->setVisible(m_lastTab == TAB_DEWARPING); + + switch (m_dewarpingOptions.dewarpingMode()) { + case OFF: + dewarpingStatusLabel->setText(tr("Off")); + break; + case AUTO: + dewarpingStatusLabel->setText(tr("Auto")); + break; + case MANUAL: + dewarpingStatusLabel->setText(tr("Manual")); + break; + case MARGINAL: + dewarpingStatusLabel->setText(tr("Marginal")); + break; + } + if (!m_dewarpingOptions.needPostDeskew() + && ((m_dewarpingOptions.dewarpingMode() == MANUAL) || (m_dewarpingOptions.dewarpingMode() == MARGINAL))) { + dewarpingStatusLabel->setText(dewarpingStatusLabel->text().append(" (").append(tr("deskew disabled")).append(")")); + } + + depthPerceptionSlider->blockSignals(true); + depthPerceptionSlider->setValue(qRound(m_depthPerception.value() * 10)); + depthPerceptionSlider->blockSignals(false); } void OptionsWidget::savitzkyGolaySmoothingToggled(bool checked) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setSavitzkyGolaySmoothingEnabled(checked); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setSavitzkyGolaySmoothingEnabled(checked); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); } void OptionsWidget::morphologicalSmoothingToggled(bool checked) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setMorphologicalSmoothingEnabled(checked); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setMorphologicalSmoothingEnabled(checked); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); + emit reloadRequested(); } void OptionsWidget::bwForegroundToggled(bool checked) { - if (!checked) { - return; - } + if (!checked) { + return; + } - originalBackgroundCB->setEnabled(checked); + originalBackgroundCB->setEnabled(checked); - m_splittingOptions.setSplittingMode(BLACK_AND_WHITE_FOREGROUND); - m_ptrSettings->setSplittingOptions(m_pageId, m_splittingOptions); - emit reloadRequested(); + m_splittingOptions.setSplittingMode(BLACK_AND_WHITE_FOREGROUND); + m_settings->setSplittingOptions(m_pageId, m_splittingOptions); + emit reloadRequested(); } void OptionsWidget::colorForegroundToggled(bool checked) { - if (!checked) { - return; - } + if (!checked) { + return; + } - originalBackgroundCB->setEnabled(!checked); + originalBackgroundCB->setEnabled(!checked); - m_splittingOptions.setSplittingMode(COLOR_FOREGROUND); - m_ptrSettings->setSplittingOptions(m_pageId, m_splittingOptions); - emit reloadRequested(); + m_splittingOptions.setSplittingMode(COLOR_FOREGROUND); + m_settings->setSplittingOptions(m_pageId, m_splittingOptions); + emit reloadRequested(); } void OptionsWidget::splittingToggled(bool checked) { - m_splittingOptions.setSplitOutput(checked); + m_splittingOptions.setSplitOutput(checked); - bwForegroundRB->setEnabled(checked); - colorForegroundRB->setEnabled(checked); - originalBackgroundCB->setEnabled(checked && bwForegroundRB->isChecked()); + bwForegroundRB->setEnabled(checked); + colorForegroundRB->setEnabled(checked); + originalBackgroundCB->setEnabled(checked && bwForegroundRB->isChecked()); - m_ptrSettings->setSplittingOptions(m_pageId, m_splittingOptions); - emit reloadRequested(); + m_settings->setSplittingOptions(m_pageId, m_splittingOptions); + emit reloadRequested(); } void OptionsWidget::originalBackgroundToggled(bool checked) { - m_splittingOptions.setOriginalBackground(checked); + m_splittingOptions.setOriginalBackgroundEnabled(checked); - m_ptrSettings->setSplittingOptions(m_pageId, m_splittingOptions); - emit reloadRequested(); + m_settings->setSplittingOptions(m_pageId, m_splittingOptions); + emit reloadRequested(); } void OptionsWidget::colorSegmentationToggled(bool checked) { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setEnabled(checked); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); - - segmenterOptionsWidget->setEnabled(checked); - if ((m_colorParams.colorMode() == BLACK_AND_WHITE) || (m_colorParams.colorMode() == MIXED)) { - posterizeCB->setEnabled(checked); - posterizeOptionsWidget->setEnabled(checked && posterizeCB->isChecked()); - } + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setEnabled(checked); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + segmenterOptionsWidget->setEnabled(checked); + if ((m_colorParams.colorMode() == BLACK_AND_WHITE) || (m_colorParams.colorMode() == MIXED)) { + posterizeCB->setEnabled(checked); + posterizeOptionsWidget->setEnabled(checked && posterizeCB->isChecked()); + } + + emit reloadRequested(); } void OptionsWidget::reduceNoiseChanged(int value) { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setNoiseReduction(value); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setNoiseReduction(value); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::redAdjustmentChanged(int value) { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setRedThresholdAdjustment(value); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setRedThresholdAdjustment(value); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::greenAdjustmentChanged(int value) { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setGreenThresholdAdjustment(value); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setGreenThresholdAdjustment(value); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::blueAdjustmentChanged(int value) { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); - segmenterOptions.setBlueThresholdAdjustment(value); - blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); - m_colorParams.setBlackWhiteOptions(blackWhiteOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions(); + segmenterOptions.setBlueThresholdAdjustment(value); + blackWhiteOptions.setColorSegmenterOptions(segmenterOptions); + m_colorParams.setBlackWhiteOptions(blackWhiteOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::posterizeToggled(bool checked) { - ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); - ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); - posterizationOptions.setEnabled(checked); - colorCommonOptions.setPosterizationOptions(posterizationOptions); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); + ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); + posterizationOptions.setEnabled(checked); + colorCommonOptions.setPosterizationOptions(posterizationOptions); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - posterizeOptionsWidget->setEnabled(checked); + posterizeOptionsWidget->setEnabled(checked); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::posterizeLevelChanged(int value) { - ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); - ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); - posterizationOptions.setLevel(value); - colorCommonOptions.setPosterizationOptions(posterizationOptions); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); + ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); + posterizationOptions.setLevel(value); + colorCommonOptions.setPosterizationOptions(posterizationOptions); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedReloadRequest.start(750); + m_delayedReloadRequest.start(750); } void OptionsWidget::posterizeNormalizationToggled(bool checked) { - ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); - ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); - posterizationOptions.setNormalizationEnabled(checked); - colorCommonOptions.setPosterizationOptions(posterizationOptions); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); + ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); + posterizationOptions.setNormalizationEnabled(checked); + colorCommonOptions.setPosterizationOptions(posterizationOptions); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::posterizeForceBwToggled(bool checked) { - ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); - ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); - posterizationOptions.setForceBlackAndWhite(checked); - colorCommonOptions.setPosterizationOptions(posterizationOptions); - m_colorParams.setColorCommonOptions(colorCommonOptions); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions(); + ColorCommonOptions::PosterizationOptions posterizationOptions = colorCommonOptions.getPosterizationOptions(); + posterizationOptions.setForceBlackAndWhite(checked); + colorCommonOptions.setPosterizationOptions(posterizationOptions); + m_colorParams.setColorCommonOptions(colorCommonOptions); + m_settings->setColorParams(m_pageId, m_colorParams); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::updateBinarizationOptionsDisplay(int idx) { - for (int i = 0; i < binarizationOptions->count(); i++) { - QWidget* currentWidget = binarizationOptions->widget(i); - currentWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - currentWidget->resize(0, 0); + for (int i = 0; i < binarizationOptions->count(); i++) { + QWidget* currentWidget = binarizationOptions->widget(i); + currentWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + currentWidget->resize(0, 0); - disconnect(currentWidget, SIGNAL(stateChanged()), this, SLOT(binarizationSettingsChanged())); - } + disconnect(currentWidget, SIGNAL(stateChanged()), this, SLOT(binarizationSettingsChanged())); + } - QWidget* widget = binarizationOptions->widget(idx); - widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - widget->adjustSize(); - binarizationOptions->adjustSize(); + QWidget* widget = binarizationOptions->widget(idx); + widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + widget->adjustSize(); + binarizationOptions->adjustSize(); - connect(widget, SIGNAL(stateChanged()), this, SLOT(binarizationSettingsChanged())); + connect(widget, SIGNAL(stateChanged()), this, SLOT(binarizationSettingsChanged())); } void OptionsWidget::addBinarizationOptionsWidget(BinarizationOptionsWidget* widget) { - widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - binarizationOptions->addWidget(widget); + widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + binarizationOptions->addWidget(widget); } void OptionsWidget::sendReloadRequested() { - emit reloadRequested(); + emit reloadRequested(); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(changeDpiButton, SIGNAL(clicked()), this, SLOT(changeDpiButtonClicked())); - connect(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); - connect(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); - connect(fillingColorBox, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorChanged(int))); - connect(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); - connect(pictureShapeSensitivitySB, SIGNAL(valueChanged(int)), this, SLOT(pictureShapeSensitivityChanged(int))); - connect(higherSearchSensitivityCB, SIGNAL(clicked(bool)), this, SLOT(higherSearchSensivityToggled(bool))); - - connect(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); - connect(reduceNoiseSB, SIGNAL(valueChanged(int)), this, SLOT(reduceNoiseChanged(int))); - connect(redAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(redAdjustmentChanged(int))); - connect(greenAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(greenAdjustmentChanged(int))); - connect(blueAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(blueAdjustmentChanged(int))); - connect(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); - connect(posterizeLevelSB, SIGNAL(valueChanged(int)), this, SLOT(posterizeLevelChanged(int))); - connect(posterizeNormalizationCB, SIGNAL(clicked(bool)), this, SLOT(posterizeNormalizationToggled(bool))); - connect(posterizeForceBwCB, SIGNAL(clicked(bool)), this, SLOT(posterizeForceBwToggled(bool))); - - connect(cutMarginsCB, SIGNAL(clicked(bool)), this, SLOT(cutMarginsToggled(bool))); - connect(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); - connect(equalizeIlluminationColorCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationColorToggled(bool))); - connect(savitzkyGolaySmoothingCB, SIGNAL(clicked(bool)), this, SLOT(savitzkyGolaySmoothingToggled(bool))); - connect(morphologicalSmoothingCB, SIGNAL(clicked(bool)), this, SLOT(morphologicalSmoothingToggled(bool))); - connect(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); - connect(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); - connect(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); - connect(originalBackgroundCB, SIGNAL(clicked(bool)), this, SLOT(originalBackgroundToggled(bool))); - connect(applyColorsButton, SIGNAL(clicked()), this, SLOT(applyColorsButtonClicked())); - - connect(applySplittingButton, SIGNAL(clicked()), this, SLOT(applySplittingButtonClicked())); - - connect(changeDewarpingButton, SIGNAL(clicked()), this, SLOT(changeDewarpingButtonClicked())); - - connect(applyDepthPerceptionButton, SIGNAL(clicked()), this, SLOT(applyDepthPerceptionButtonClicked())); - - connect(despeckleOffBtn, SIGNAL(clicked()), this, SLOT(despeckleOffSelected())); - connect(despeckleCautiousBtn, SIGNAL(clicked()), this, SLOT(despeckleCautiousSelected())); - connect(despeckleNormalBtn, SIGNAL(clicked()), this, SLOT(despeckleNormalSelected())); - connect(despeckleAggressiveBtn, SIGNAL(clicked()), this, SLOT(despeckleAggressiveSelected())); - connect(applyDespeckleButton, SIGNAL(clicked()), this, SLOT(applyDespeckleButtonClicked())); - connect(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); - connect(&delayedReloadRequest, SIGNAL(timeout()), this, SLOT(sendReloadRequested())); -} + CONNECT(changeDpiButton, SIGNAL(clicked()), this, SLOT(changeDpiButtonClicked())); + CONNECT(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); + CONNECT(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); + CONNECT(fillingColorBox, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorChanged(int))); + CONNECT(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); + CONNECT(pictureShapeSensitivitySB, SIGNAL(valueChanged(int)), this, SLOT(pictureShapeSensitivityChanged(int))); + CONNECT(higherSearchSensitivityCB, SIGNAL(clicked(bool)), this, SLOT(higherSearchSensivityToggled(bool))); + + CONNECT(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); + CONNECT(reduceNoiseSB, SIGNAL(valueChanged(int)), this, SLOT(reduceNoiseChanged(int))); + CONNECT(redAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(redAdjustmentChanged(int))); + CONNECT(greenAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(greenAdjustmentChanged(int))); + CONNECT(blueAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(blueAdjustmentChanged(int))); + CONNECT(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); + CONNECT(posterizeLevelSB, SIGNAL(valueChanged(int)), this, SLOT(posterizeLevelChanged(int))); + CONNECT(posterizeNormalizationCB, SIGNAL(clicked(bool)), this, SLOT(posterizeNormalizationToggled(bool))); + CONNECT(posterizeForceBwCB, SIGNAL(clicked(bool)), this, SLOT(posterizeForceBwToggled(bool))); + + CONNECT(fillMarginsCB, SIGNAL(clicked(bool)), this, SLOT(fillMarginsToggled(bool))); + CONNECT(fillOffcutCB, SIGNAL(clicked(bool)), this, SLOT(fillOffcutToggled(bool))); + CONNECT(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); + CONNECT(equalizeIlluminationColorCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationColorToggled(bool))); + CONNECT(savitzkyGolaySmoothingCB, SIGNAL(clicked(bool)), this, SLOT(savitzkyGolaySmoothingToggled(bool))); + CONNECT(morphologicalSmoothingCB, SIGNAL(clicked(bool)), this, SLOT(morphologicalSmoothingToggled(bool))); + CONNECT(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); + CONNECT(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); + CONNECT(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); + CONNECT(originalBackgroundCB, SIGNAL(clicked(bool)), this, SLOT(originalBackgroundToggled(bool))); + CONNECT(applyColorsButton, SIGNAL(clicked()), this, SLOT(applyColorsButtonClicked())); + + CONNECT(applySplittingButton, SIGNAL(clicked()), this, SLOT(applySplittingButtonClicked())); + + CONNECT(changeDewarpingButton, SIGNAL(clicked()), this, SLOT(changeDewarpingButtonClicked())); + + CONNECT(applyDepthPerceptionButton, SIGNAL(clicked()), this, SLOT(applyDepthPerceptionButtonClicked())); + + CONNECT(despeckleCB, SIGNAL(clicked(bool)), this, SLOT(despeckleToggled(bool))); + CONNECT(despeckleSlider, SIGNAL(sliderReleased()), this, SLOT(despeckleSliderReleased())); + CONNECT(despeckleSlider, SIGNAL(valueChanged(int)), this, SLOT(despeckleSliderValueChanged(int))); + CONNECT(applyDespeckleButton, SIGNAL(clicked()), this, SLOT(applyDespeckleButtonClicked())); + CONNECT(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); + CONNECT(&m_delayedReloadRequest, SIGNAL(timeout()), this, SLOT(sendReloadRequested())); + + CONNECT(blackOnWhiteCB, SIGNAL(clicked(bool)), this, SLOT(blackOnWhiteToggled(bool))); + CONNECT(applyProcessingOptionsButton, SIGNAL(clicked()), this, SLOT(applyProcessingParamsClicked())); +} + +#undef CONNECT void OptionsWidget::removeUiConnections() { - disconnect(changeDpiButton, SIGNAL(clicked()), this, SLOT(changeDpiButtonClicked())); - disconnect(colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int))); - disconnect(thresholdMethodBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdMethodChanged(int))); - disconnect(fillingColorBox, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorChanged(int))); - disconnect(pictureShapeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(pictureShapeChanged(int))); - disconnect(pictureShapeSensitivitySB, SIGNAL(valueChanged(int)), this, SLOT(pictureShapeSensitivityChanged(int))); - disconnect(higherSearchSensitivityCB, SIGNAL(clicked(bool)), this, SLOT(higherSearchSensivityToggled(bool))); - - disconnect(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool))); - disconnect(reduceNoiseSB, SIGNAL(valueChanged(int)), this, SLOT(reduceNoiseChanged(int))); - disconnect(redAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(redAdjustmentChanged(int))); - disconnect(greenAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(greenAdjustmentChanged(int))); - disconnect(blueAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(blueAdjustmentChanged(int))); - disconnect(posterizeCB, SIGNAL(clicked(bool)), this, SLOT(posterizeToggled(bool))); - disconnect(posterizeLevelSB, SIGNAL(valueChanged(int)), this, SLOT(posterizeLevelChanged(int))); - disconnect(posterizeNormalizationCB, SIGNAL(clicked(bool)), this, SLOT(posterizeNormalizationToggled(bool))); - disconnect(posterizeForceBwCB, SIGNAL(clicked(bool)), this, SLOT(posterizeForceBwToggled(bool))); - - disconnect(cutMarginsCB, SIGNAL(clicked(bool)), this, SLOT(cutMarginsToggled(bool))); - disconnect(equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool))); - disconnect(equalizeIlluminationColorCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationColorToggled(bool))); - disconnect(savitzkyGolaySmoothingCB, SIGNAL(clicked(bool)), this, SLOT(savitzkyGolaySmoothingToggled(bool))); - disconnect(morphologicalSmoothingCB, SIGNAL(clicked(bool)), this, SLOT(morphologicalSmoothingToggled(bool))); - disconnect(splittingCB, SIGNAL(clicked(bool)), this, SLOT(splittingToggled(bool))); - disconnect(bwForegroundRB, SIGNAL(clicked(bool)), this, SLOT(bwForegroundToggled(bool))); - disconnect(colorForegroundRB, SIGNAL(clicked(bool)), this, SLOT(colorForegroundToggled(bool))); - disconnect(originalBackgroundCB, SIGNAL(clicked(bool)), this, SLOT(originalBackgroundToggled(bool))); - disconnect(applyColorsButton, SIGNAL(clicked()), this, SLOT(applyColorsButtonClicked())); - - disconnect(applySplittingButton, SIGNAL(clicked()), this, SLOT(applySplittingButtonClicked())); - - disconnect(changeDewarpingButton, SIGNAL(clicked()), this, SLOT(changeDewarpingButtonClicked())); - - disconnect(applyDepthPerceptionButton, SIGNAL(clicked()), this, SLOT(applyDepthPerceptionButtonClicked())); - - disconnect(despeckleOffBtn, SIGNAL(clicked()), this, SLOT(despeckleOffSelected())); - disconnect(despeckleCautiousBtn, SIGNAL(clicked()), this, SLOT(despeckleCautiousSelected())); - disconnect(despeckleNormalBtn, SIGNAL(clicked()), this, SLOT(despeckleNormalSelected())); - disconnect(despeckleAggressiveBtn, SIGNAL(clicked()), this, SLOT(despeckleAggressiveSelected())); - disconnect(applyDespeckleButton, SIGNAL(clicked()), this, SLOT(applyDespeckleButtonClicked())); - disconnect(depthPerceptionSlider, SIGNAL(valueChanged(int)), this, SLOT(depthPerceptionChangedSlot(int))); - disconnect(&delayedReloadRequest, SIGNAL(timeout()), this, SLOT(sendReloadRequested())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } ImageViewTab OptionsWidget::lastTab() const { - return m_lastTab; + return m_lastTab; } const DepthPerception& OptionsWidget::depthPerception() const { - return m_depthPerception; + return m_depthPerception; +} + +void OptionsWidget::blackOnWhiteToggled(bool value) { + m_settings->setBlackOnWhite(m_pageId, value); + OutputProcessingParams processingParams = m_settings->getOutputProcessingParams(m_pageId); + processingParams.setBlackOnWhiteSetManually(true); + m_settings->setOutputProcessingParams(m_pageId, processingParams); + + emit reloadRequested(); +} + +void OptionsWidget::applyProcessingParamsClicked() { + auto* dialog = new ApplyColorsDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Processing Settings")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, + SLOT(applyProcessingParamsConfirmed(const std::set&))); + dialog->show(); } +void OptionsWidget::applyProcessingParamsConfirmed(const std::set& pages) { + for (const PageId& page_id : pages) { + m_settings->setBlackOnWhite(page_id, m_settings->getParams(m_pageId).isBlackOnWhite()); + OutputProcessingParams processingParams = m_settings->getOutputProcessingParams(page_id); + processingParams.setBlackOnWhiteSetManually(true); + m_settings->setOutputProcessingParams(page_id, processingParams); + } + + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); + } + } + + if (pages.find(m_pageId) != pages.end()) { + emit reloadRequested(); + } +} + +void OptionsWidget::updateProcessingDisplay() { + blackOnWhiteCB->setChecked(m_settings->getParams(m_pageId).isBlackOnWhite()); +} } // namespace output \ No newline at end of file diff --git a/filters/output/OptionsWidget.h b/filters/output/OptionsWidget.h index 6754bdf2f..7e822118f 100644 --- a/filters/output/OptionsWidget.h +++ b/filters/output/OptionsWidget.h @@ -19,26 +19,27 @@ #ifndef OUTPUT_OPTIONSWIDGET_H_ #define OUTPUT_OPTIONSWIDGET_H_ -#include "ui_OutputOptionsWidget.h" -#include "FilterOptionsWidget.h" -#include "intrusive_ptr.h" -#include "PageId.h" -#include "PageSelectionAccessor.h" +#include +#include +#include +#include +#include +#include +#include +#include "BinarizationOptionsWidget.h" #include "ColorParams.h" -#include "DewarpingOptions.h" #include "DepthPerception.h" #include "DespeckleLevel.h" +#include "DewarpingOptions.h" #include "Dpi.h" +#include "FilterOptionsWidget.h" #include "ImageViewTab.h" -#include "Params.h" -#include "BinarizationOptionsWidget.h" #include "OutputProcessingParams.h" -#include -#include -#include -#include -#include -#include +#include "PageId.h" +#include "PageSelectionAccessor.h" +#include "Params.h" +#include "intrusive_ptr.h" +#include "ui_OutputOptionsWidget.h" namespace dewarping { class DistortionModel; @@ -48,151 +49,161 @@ namespace output { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::OutputOptionsWidget { - Q_OBJECT -public: - OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + + ~OptionsWidget() override; + + void preUpdateUI(const PageId& page_id); + + void postUpdateUI(); + + ImageViewTab lastTab() const; + + const DepthPerception& depthPerception() const; - ~OptionsWidget() override; + signals: - void preUpdateUI(const PageId& page_id); + void despeckleLevelChanged(double level, bool* handled); - void postUpdateUI(); + void depthPerceptionChanged(double val); - ImageViewTab lastTab() const; + public slots: - const DepthPerception& depthPerception() const; + void tabChanged(ImageViewTab tab); -signals: + void distortionModelChanged(const dewarping::DistortionModel& model); - void despeckleLevelChanged(DespeckleLevel level, bool* handled); + private slots: - void depthPerceptionChanged(double val); + void changeDpiButtonClicked(); -public slots: + void applyColorsButtonClicked(); - void tabChanged(ImageViewTab tab); + void applySplittingButtonClicked(); - void distortionModelChanged(const dewarping::DistortionModel& model); + void dpiChanged(const std::set& pages, const Dpi& dpi); -private slots: + void applyColorsConfirmed(const std::set& pages); - void changeDpiButtonClicked(); + void applySplittingOptionsConfirmed(const std::set& pages); - void applyColorsButtonClicked(); + void colorModeChanged(int idx); - void applySplittingButtonClicked(); + void blackOnWhiteToggled(bool value); - void dpiChanged(const std::set& pages, const Dpi& dpi); + void applyProcessingParamsClicked(); - void applyColorsConfirmed(const std::set& pages); + void applyProcessingParamsConfirmed(const std::set& pages); - void applySplittingOptionsConfirmed(const std::set& pages); + void thresholdMethodChanged(int idx); - void colorModeChanged(int idx); + void fillingColorChanged(int idx); - void thresholdMethodChanged(int idx); + void pictureShapeChanged(int idx); - void fillingColorChanged(int idx); + void pictureShapeSensitivityChanged(int value); - void pictureShapeChanged(int idx); + void higherSearchSensivityToggled(bool checked); - void pictureShapeSensitivityChanged(int value); + void colorSegmentationToggled(bool checked); - void higherSearchSensivityToggled(bool checked); + void reduceNoiseChanged(int value); - void colorSegmentationToggled(bool checked); + void redAdjustmentChanged(int value); - void reduceNoiseChanged(int value); + void greenAdjustmentChanged(int value); - void redAdjustmentChanged(int value); + void blueAdjustmentChanged(int value); - void greenAdjustmentChanged(int value); + void posterizeToggled(bool checked); - void blueAdjustmentChanged(int value); + void posterizeLevelChanged(int value); - void posterizeToggled(bool checked); + void posterizeNormalizationToggled(bool checked); - void posterizeLevelChanged(int value); + void posterizeForceBwToggled(bool checked); - void posterizeNormalizationToggled(bool checked); + void fillMarginsToggled(bool checked); - void posterizeForceBwToggled(bool checked); + void fillOffcutToggled(bool checked); - void cutMarginsToggled(bool checked); + void equalizeIlluminationToggled(bool checked); - void equalizeIlluminationToggled(bool checked); + void equalizeIlluminationColorToggled(bool checked); - void equalizeIlluminationColorToggled(bool checked); + void savitzkyGolaySmoothingToggled(bool checked); - void savitzkyGolaySmoothingToggled(bool checked); + void morphologicalSmoothingToggled(bool checked); - void morphologicalSmoothingToggled(bool checked); + void splittingToggled(bool checked); - void splittingToggled(bool checked); + void bwForegroundToggled(bool checked); - void bwForegroundToggled(bool checked); + void colorForegroundToggled(bool checked); - void colorForegroundToggled(bool checked); + void originalBackgroundToggled(bool checked); - void originalBackgroundToggled(bool checked); + void binarizationSettingsChanged(); - void binarizationSettingsChanged(); + void despeckleToggled(bool checked); - void despeckleOffSelected(); + void despeckleSliderReleased(); - void despeckleCautiousSelected(); + void despeckleSliderValueChanged(int value); - void despeckleNormalSelected(); + void applyDespeckleButtonClicked(); - void despeckleAggressiveSelected(); + void applyDespeckleConfirmed(const std::set& pages); - void applyDespeckleButtonClicked(); + void changeDewarpingButtonClicked(); - void applyDespeckleConfirmed(const std::set& pages); + void dewarpingChanged(const std::set& pages, const DewarpingOptions& opt); - void changeDewarpingButtonClicked(); + void applyDepthPerceptionButtonClicked(); - void dewarpingChanged(const std::set& pages, const DewarpingOptions& opt); + void applyDepthPerceptionConfirmed(const std::set& pages); - void applyDepthPerceptionButtonClicked(); + void depthPerceptionChangedSlot(int val); - void applyDepthPerceptionConfirmed(const std::set& pages); + void updateBinarizationOptionsDisplay(int idx); - void depthPerceptionChangedSlot(int val); + void sendReloadRequested(); - void updateBinarizationOptionsDisplay(int idx); + private: + void handleDespeckleLevelChange(double level, bool delay = false); - void sendReloadRequested(); + void reloadIfNecessary(); -private: - void handleDespeckleLevelChange(DespeckleLevel level); + void updateDpiDisplay(); - void reloadIfNecessary(); + void updateColorsDisplay(); - void updateDpiDisplay(); + void updateDewarpingDisplay(); - void updateColorsDisplay(); + void updateProcessingDisplay(); - void updateDewarpingDisplay(); + void addBinarizationOptionsWidget(BinarizationOptionsWidget* widget); - void addBinarizationOptionsWidget(BinarizationOptionsWidget* widget); + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + intrusive_ptr m_settings; + PageSelectionAccessor m_pageSelectionAccessor; + PageId m_pageId; + Dpi m_outputDpi; + ColorParams m_colorParams; + SplittingOptions m_splittingOptions; + PictureShapeOptions m_pictureShapeOptions; + DepthPerception m_depthPerception; + DewarpingOptions m_dewarpingOptions; + double m_despeckleLevel; + ImageViewTab m_lastTab; + QTimer m_delayedReloadRequest; - intrusive_ptr m_ptrSettings; - PageSelectionAccessor m_pageSelectionAccessor; - PageId m_pageId; - Dpi m_outputDpi; - ColorParams m_colorParams; - SplittingOptions m_splittingOptions; - PictureShapeOptions m_pictureShapeOptions; - DepthPerception m_depthPerception; - DewarpingOptions m_dewarpingOptions; - DespeckleLevel m_despeckleLevel; - ImageViewTab m_lastTab; - QTimer delayedReloadRequest; + std::list m_connectionList; }; } // namespace output #endif // ifndef OUTPUT_OPTIONSWIDGET_H_ diff --git a/filters/output/OtsuBinarizationOptionsWidget.cpp b/filters/output/OtsuBinarizationOptionsWidget.cpp index c39b49561..56ea4e3e5 100644 --- a/filters/output/OtsuBinarizationOptionsWidget.cpp +++ b/filters/output/OtsuBinarizationOptionsWidget.cpp @@ -1,140 +1,142 @@ +#include "OtsuBinarizationOptionsWidget.h" +#include #include #include -#include -#include "OtsuBinarizationOptionsWidget.h" #include "../../Utils.h" namespace output { OtsuBinarizationOptionsWidget::OtsuBinarizationOptionsWidget(intrusive_ptr settings) - : m_ptrSettings(std::move(settings)), ignoreSliderChanges(0) { - setupUi(this); + : m_settings(std::move(settings)), m_ignoreSliderChanges(0) { + setupUi(this); - darkerThresholdLink->setText(Utils::richTextForLink(darkerThresholdLink->text())); - lighterThresholdLink->setText(Utils::richTextForLink(lighterThresholdLink->text())); - thresholdSlider->setToolTip(QString::number(thresholdSlider->value())); + darkerThresholdLink->setText(Utils::richTextForLink(darkerThresholdLink->text())); + lighterThresholdLink->setText(Utils::richTextForLink(lighterThresholdLink->text())); + thresholdSlider->setToolTip(QString::number(thresholdSlider->value())); - thresholdSlider->setMinimum(-100); - thresholdSlider->setMaximum(100); - thresholLabel->setText(QString::number(thresholdSlider->value())); + thresholdSlider->setMinimum(-100); + thresholdSlider->setMaximum(100); + thresholLabel->setText(QString::number(thresholdSlider->value())); - delayedStateChanger.setSingleShot(true); + m_delayedStateChanger.setSingleShot(true); - setupUiConnections(); + setupUiConnections(); } -void OtsuBinarizationOptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); +void OtsuBinarizationOptionsWidget::updateUi(const PageId& page_id) { + removeUiConnections(); - const Params params(m_ptrSettings->getParams(page_id)); - m_pageId = page_id; - m_colorParams = params.colorParams(); + const Params params(m_settings->getParams(page_id)); + m_pageId = page_id; + m_colorParams = params.colorParams(); - updateView(); + updateView(); - setupUiConnections(); + setupUiConnections(); } void OtsuBinarizationOptionsWidget::thresholdSliderReleased() { - const int value = thresholdSlider->value(); - setThresholdAdjustment(value); + const int value = thresholdSlider->value(); + setThresholdAdjustment(value); - emit stateChanged(); + emit stateChanged(); } void OtsuBinarizationOptionsWidget::thresholdSliderValueChanged(int value) { - if (ignoreSliderChanges) { - return; - } + if (m_ignoreSliderChanges) { + return; + } - thresholLabel->setText(QString::number(value)); + thresholLabel->setText(QString::number(value)); - const QString tooltip_text(QString::number(value)); - thresholdSlider->setToolTip(tooltip_text); + const QString tooltip_text(QString::number(value)); + thresholdSlider->setToolTip(tooltip_text); - // Show the tooltip immediately. - const QPoint center(thresholdSlider->rect().center()); - QPoint tooltip_pos(thresholdSlider->mapFromGlobal(QCursor::pos())); - tooltip_pos.setY(center.y()); - tooltip_pos.setX(qBound(0, tooltip_pos.x(), thresholdSlider->width())); - tooltip_pos = thresholdSlider->mapToGlobal(tooltip_pos); - QToolTip::showText(tooltip_pos, tooltip_text, thresholdSlider); + // Show the tooltip immediately. + const QPoint center(thresholdSlider->rect().center()); + QPoint tooltip_pos(thresholdSlider->mapFromGlobal(QCursor::pos())); + tooltip_pos.setY(center.y()); + tooltip_pos.setX(qBound(0, tooltip_pos.x(), thresholdSlider->width())); + tooltip_pos = thresholdSlider->mapToGlobal(tooltip_pos); + QToolTip::showText(tooltip_pos, tooltip_text, thresholdSlider); - if (thresholdSlider->isSliderDown()) { - return; - } + if (thresholdSlider->isSliderDown()) { + return; + } - setThresholdAdjustment(value); + setThresholdAdjustment(value); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void OtsuBinarizationOptionsWidget::setThresholdAdjustment(int value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - if (opt.thresholdAdjustment() == value) { - return; - } + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + if (opt.thresholdAdjustment() == value) { + return; + } - thresholLabel->setText(QString::number(value)); + thresholLabel->setText(QString::number(value)); - opt.setThresholdAdjustment(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + opt.setThresholdAdjustment(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); } void OtsuBinarizationOptionsWidget::setLighterThreshold() { - const ScopedIncDec scopeGuard(ignoreSliderChanges); + const ScopedIncDec scopeGuard(m_ignoreSliderChanges); - thresholdSlider->setValue(thresholdSlider->value() - 1); - setThresholdAdjustment(thresholdSlider->value()); + thresholdSlider->setValue(thresholdSlider->value() - 1); + setThresholdAdjustment(thresholdSlider->value()); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void OtsuBinarizationOptionsWidget::setDarkerThreshold() { - const ScopedIncDec scopeGuard(ignoreSliderChanges); + const ScopedIncDec scopeGuard(m_ignoreSliderChanges); - thresholdSlider->setValue(thresholdSlider->value() + 1); - setThresholdAdjustment(thresholdSlider->value()); + thresholdSlider->setValue(thresholdSlider->value() + 1); + setThresholdAdjustment(thresholdSlider->value()); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void OtsuBinarizationOptionsWidget::setNeutralThreshold() { - const ScopedIncDec scopeGuard(ignoreSliderChanges); + const ScopedIncDec scopeGuard(m_ignoreSliderChanges); - thresholdSlider->setValue(0); - setThresholdAdjustment(thresholdSlider->value()); + thresholdSlider->setValue(0); + setThresholdAdjustment(thresholdSlider->value()); - emit stateChanged(); + emit stateChanged(); } void OtsuBinarizationOptionsWidget::updateView() { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - thresholdSlider->setValue(blackWhiteOptions.thresholdAdjustment()); - thresholLabel->setText(QString::number(blackWhiteOptions.thresholdAdjustment())); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + thresholdSlider->setValue(blackWhiteOptions.thresholdAdjustment()); + thresholLabel->setText(QString::number(blackWhiteOptions.thresholdAdjustment())); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OtsuBinarizationOptionsWidget::setupUiConnections() { - connect(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); - connect(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); - connect(thresholdSlider, SIGNAL(sliderReleased()), this, SLOT(thresholdSliderReleased())); - connect(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); - connect(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); - connect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + CONNECT(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); + CONNECT(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); + CONNECT(thresholdSlider, SIGNAL(sliderReleased()), this, SLOT(thresholdSliderReleased())); + CONNECT(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); + CONNECT(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); + CONNECT(&m_delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); } +#undef CONNECT + void OtsuBinarizationOptionsWidget::removeUiConnections() { - disconnect(lighterThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setLighterThreshold())); - disconnect(darkerThresholdLink, SIGNAL(linkActivated(const QString&)), this, SLOT(setDarkerThreshold())); - disconnect(thresholdSlider, SIGNAL(sliderReleased()), this, SLOT(thresholdSliderReleased())); - disconnect(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(thresholdSliderValueChanged(int))); - disconnect(neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold())); - disconnect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } void OtsuBinarizationOptionsWidget::sendStateChanged() { - emit stateChanged(); + emit stateChanged(); } } // namespace output \ No newline at end of file diff --git a/filters/output/OtsuBinarizationOptionsWidget.h b/filters/output/OtsuBinarizationOptionsWidget.h index 3c95c833b..b59e4cc46 100644 --- a/filters/output/OtsuBinarizationOptionsWidget.h +++ b/filters/output/OtsuBinarizationOptionsWidget.h @@ -2,52 +2,54 @@ #ifndef SCANTAILOR_OTSUBINARIZATIONOPTIONSWIDGET_H #define SCANTAILOR_OTSUBINARIZATIONOPTIONSWIDGET_H -#include "ui_OtsuBinarizationOptionsWidget.h" +#include +#include #include "BinarizationOptionsWidget.h" #include "ColorParams.h" -#include "intrusive_ptr.h" #include "Settings.h" -#include +#include "intrusive_ptr.h" +#include "ui_OtsuBinarizationOptionsWidget.h" namespace output { class OtsuBinarizationOptionsWidget : public BinarizationOptionsWidget, private Ui::OtsuBinarizationOptionsWidget { - Q_OBJECT -private: - intrusive_ptr m_ptrSettings; - PageId m_pageId; - ColorParams m_colorParams; - QTimer delayedStateChanger; - int ignoreSliderChanges; + Q_OBJECT + public: + explicit OtsuBinarizationOptionsWidget(intrusive_ptr settings); + + ~OtsuBinarizationOptionsWidget() override = default; -public: - explicit OtsuBinarizationOptionsWidget(intrusive_ptr settings); + void updateUi(const PageId& m_pageId) override; - ~OtsuBinarizationOptionsWidget() override = default; + private slots: - void preUpdateUI(const PageId& m_pageId) override; + void thresholdSliderReleased(); -private slots: + void thresholdSliderValueChanged(int value); - void thresholdSliderReleased(); + void setLighterThreshold(); - void thresholdSliderValueChanged(int value); + void setDarkerThreshold(); - void setLighterThreshold(); + void setNeutralThreshold(); - void setDarkerThreshold(); + void sendStateChanged(); - void setNeutralThreshold(); + private: + void updateView(); - void sendStateChanged(); + void setThresholdAdjustment(int value); -private: - void updateView(); + void setupUiConnections(); - void setThresholdAdjustment(int value); + void removeUiConnections(); - void setupUiConnections(); + intrusive_ptr m_settings; + PageId m_pageId; + ColorParams m_colorParams; + QTimer m_delayedStateChanger; + int m_ignoreSliderChanges; - void removeUiConnections(); + std::list m_connectionList; }; } // namespace output diff --git a/filters/output/OutputFileParams.cpp b/filters/output/OutputFileParams.cpp index 69246c864..afc09aeec 100644 --- a/filters/output/OutputFileParams.cpp +++ b/filters/output/OutputFileParams.cpp @@ -17,47 +17,46 @@ */ #include "OutputFileParams.h" +#include #include #include -#include namespace output { -OutputFileParams::OutputFileParams() : m_size(-1), m_mtime(0) { -} +OutputFileParams::OutputFileParams() : m_size(-1), m_modifiedTime(0) {} -OutputFileParams::OutputFileParams(const QFileInfo& file_info) : m_size(-1), m_mtime(0) { - if (file_info.exists()) { - m_size = file_info.size(); - m_mtime = file_info.lastModified().toTime_t(); - } +OutputFileParams::OutputFileParams(const QFileInfo& file_info) : m_size(-1), m_modifiedTime(0) { + if (file_info.exists()) { + m_size = file_info.size(); + m_modifiedTime = file_info.lastModified().toTime_t(); + } } -OutputFileParams::OutputFileParams(const QDomElement& el) : m_size(-1), m_mtime(0) { - if (el.hasAttribute("size")) { - m_size = (qint64) el.attribute("size").toLongLong(); - } - if (el.hasAttribute("mtime")) { - m_mtime = (time_t) el.attribute("mtime").toLongLong(); - } +OutputFileParams::OutputFileParams(const QDomElement& el) : m_size(-1), m_modifiedTime(0) { + if (el.hasAttribute("size")) { + m_size = (qint64) el.attribute("size").toLongLong(); + } + if (el.hasAttribute("mtime")) { + m_modifiedTime = (time_t) el.attribute("mtime").toLongLong(); + } } QDomElement OutputFileParams::toXml(QDomDocument& doc, const QString& name) const { - if (isValid()) { - QDomElement el(doc.createElement(name)); - el.setAttribute("size", QString::number(m_size)); - el.setAttribute("mtime", QString::number(m_mtime)); - - return el; - } else { - return QDomElement(); - } + if (isValid()) { + QDomElement el(doc.createElement(name)); + el.setAttribute("size", QString::number(m_size)); + el.setAttribute("mtime", QString::number(m_modifiedTime)); + + return el; + } else { + return QDomElement(); + } } bool OutputFileParams::matches(const OutputFileParams& other) const { - return isValid() && other.isValid() && m_size == other.m_size /* && m_mtime == other.m_mtime*/; + return isValid() && other.isValid() && m_size == other.m_size /* && m_mtime == other.m_mtime*/; } const bool OutputFileParams::isValid() const { - return m_size >= 0; + return m_size >= 0; } } // namespace output \ No newline at end of file diff --git a/filters/output/OutputFileParams.h b/filters/output/OutputFileParams.h index 3cce4d3e0..4393e49cf 100644 --- a/filters/output/OutputFileParams.h +++ b/filters/output/OutputFileParams.h @@ -31,25 +31,25 @@ namespace output { * \brief Parameters of the output file used to determine if it has changed. */ class OutputFileParams { -public: - OutputFileParams(); + public: + OutputFileParams(); - explicit OutputFileParams(const QFileInfo& file_info); + explicit OutputFileParams(const QFileInfo& file_info); - explicit OutputFileParams(const QDomElement& el); + explicit OutputFileParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - const bool isValid() const; + const bool isValid() const; - /** - * \brief Returns true if it's likely we have two identical files. - */ - bool matches(const OutputFileParams& other) const; + /** + * \brief Returns true if it's likely we have two identical files. + */ + bool matches(const OutputFileParams& other) const; -private: - qint64 m_size; - time_t m_mtime; + private: + qint64 m_size; + time_t m_modifiedTime; }; } // namespace output #endif // ifndef OUTPUT_OUTPUT_FILE_PARAMS_H_ diff --git a/filters/output/OutputGenerator.cpp b/filters/output/OutputGenerator.cpp index b5dd1f207..91c43dce1 100644 --- a/filters/output/OutputGenerator.cpp +++ b/filters/output/OutputGenerator.cpp @@ -17,46 +17,48 @@ */ #include "OutputGenerator.h" -#include "FilterData.h" -#include "TaskStatus.h" -#include "Utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "DebugImages.h" -#include "EstimateBackground.h" -#include "RenderParams.h" #include "Dpm.h" +#include "EstimateBackground.h" #include "FillColorProperty.h" +#include "FilterData.h" +#include "RenderParams.h" +#include "TaskStatus.h" +#include "Utils.h" #include "dewarping/CylindricalSurfaceDewarper.h" -#include "dewarping/TextLineTracer.h" -#include "dewarping/TopBottomEdgeTracer.h" -#include "dewarping/DistortionModelBuilder.h" #include "dewarping/DewarpingPointMapper.h" +#include "dewarping/DistortionModelBuilder.h" #include "dewarping/RasterDewarper.h" +#include "dewarping/TextLineTracer.h" +#include "dewarping/TopBottomEdgeTracer.h" +#include "imageproc/AdjustBrightness.h" #include "imageproc/Binarize.h" -#include "imageproc/Transform.h" -#include "imageproc/Scale.h" -#include "imageproc/Morphology.h" #include "imageproc/ConnCompEraser.h" -#include "imageproc/SeedFill.h" +#include "imageproc/ConnectivityMap.h" #include "imageproc/Constants.h" -#include "imageproc/Grayscale.h" -#include "imageproc/RasterOp.h" -#include "imageproc/GrayRasterOp.h" -#include "imageproc/PolynomialSurface.h" -#include "imageproc/SavGolFilter.h" #include "imageproc/DrawOver.h" -#include "imageproc/AdjustBrightness.h" -#include "imageproc/PolygonRasterizer.h" -#include "imageproc/ConnectivityMap.h" +#include "imageproc/GrayRasterOp.h" +#include "imageproc/Grayscale.h" #include "imageproc/InfluenceMap.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "imageproc/Morphology.h" #include "imageproc/OrthogonalRotation.h" +#include "imageproc/PolygonRasterizer.h" +#include "imageproc/PolynomialSurface.h" +#include "imageproc/RasterOp.h" +#include "imageproc/SavGolFilter.h" +#include "imageproc/Scale.h" +#include "imageproc/SeedFill.h" +#include "imageproc/Transform.h" using namespace imageproc; using namespace dewarping; @@ -64,182 +66,209 @@ using namespace dewarping; namespace output { namespace { struct RaiseAboveBackground { - static uint8_t transform(uint8_t src, uint8_t dst) { - // src: orig - // dst: background (dst >= src) - if (dst - src < 1) { - return 0xff; - } - const unsigned orig = src; - const unsigned background = dst; - - return static_cast((orig * 255 + background / 2) / background); + static uint8_t transform(uint8_t src, uint8_t dst) { + // src: orig + // dst: background (dst >= src) + if (dst - src < 1) { + return 0xff; } + const unsigned orig = src; + const unsigned background = dst; + + return static_cast((orig * 255 + background / 2) / background); + } }; struct CombineInverted { - static uint8_t transform(uint8_t src, uint8_t dst) { - const unsigned dilated = dst; - const unsigned eroded = src; - const unsigned res = 255 - (255 - dilated) * eroded / 255; + static uint8_t transform(uint8_t src, uint8_t dst) { + const unsigned dilated = dst; + const unsigned eroded = src; + const unsigned res = 255 - (255 - dilated) * eroded / 255; - return static_cast(res); - } + return static_cast(res); + } }; -template +template PixelType reserveBlackAndWhite(PixelType color); -template<> +template <> uint32_t reserveBlackAndWhite(uint32_t color) { - // We handle both RGB32 and ARGB32 here. - switch (color & 0x00FFFFFF) { - case 0x00000000: - return 0xFF010101; - case 0x00FFFFFF: - return 0xFFFEFEFE; - default: - return color; - } + // We handle both RGB32 and ARGB32 here. + switch (color & 0x00FFFFFF) { + case 0x00000000: + return 0xFF010101; + case 0x00FFFFFF: + return 0xFFFEFEFE; + default: + return color; + } } -template<> +template <> uint8_t reserveBlackAndWhite(uint8_t color) { - switch (color) { - case 0x00: - return 0x01; - case 0xFF: - return 0xFE; - default: - return color; - } + switch (color) { + case 0x00: + return 0x01; + case 0xFF: + return 0xFE; + default: + return color; + } } -template +template void reserveBlackAndWhite(QImage& img) { - const int width = img.width(); - const int height = img.height(); + const int width = img.width(); + const int height = img.height(); - auto* image_line = reinterpret_cast(img.bits()); - const int image_stride = img.bytesPerLine() / sizeof(PixelType); + auto* image_line = reinterpret_cast(img.bits()); + const int image_stride = img.bytesPerLine() / sizeof(PixelType); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - image_line[x] = reserveBlackAndWhite(image_line[x]); - } - image_line += image_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + image_line[x] = reserveBlackAndWhite(image_line[x]); } + image_line += image_stride; + } } void reserveBlackAndWhite(QImage& img) { - switch (img.format()) { - case QImage::Format_Indexed8: - reserveBlackAndWhite(img); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - reserveBlackAndWhite(img); - break; - default: - throw std::invalid_argument("reserveBlackAndWhite: wrong image format."); - ; - } + switch (img.format()) { + case QImage::Format_Indexed8: + reserveBlackAndWhite(img); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + reserveBlackAndWhite(img); + break; + default: + throw std::invalid_argument("reserveBlackAndWhite: wrong image format."); + ; + } } -template +template void reserveBlackAndWhite(QImage& img, const BinaryImage& mask) { - const int width = img.width(); - const int height = img.height(); - - auto* image_line = reinterpret_cast(img.bits()); - const int image_stride = img.bytesPerLine() / sizeof(PixelType); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - image_line[x] = reserveBlackAndWhite(image_line[x]); - } - } - image_line += image_stride; - mask_line += mask_stride; - } + const int width = img.width(); + const int height = img.height(); + + auto* image_line = reinterpret_cast(img.bits()); + const int image_stride = img.bytesPerLine() / sizeof(PixelType); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + image_line[x] = reserveBlackAndWhite(image_line[x]); + } + } + image_line += image_stride; + mask_line += mask_stride; + } } void reserveBlackAndWhite(QImage& img, const BinaryImage& mask) { - switch (img.format()) { - case QImage::Format_Indexed8: - reserveBlackAndWhite(img, mask); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - reserveBlackAndWhite(img, mask); - break; - default: - throw std::invalid_argument("reserveBlackAndWhite: wrong image format."); - ; - } + switch (img.format()) { + case QImage::Format_Indexed8: + reserveBlackAndWhite(img, mask); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + reserveBlackAndWhite(img, mask); + break; + default: + throw std::invalid_argument("reserveBlackAndWhite: wrong image format."); + ; + } } -template +template void fillExcept(QImage& image, const BinaryImage& bw_mask, const QColor& color) { - auto* image_line = reinterpret_cast(image.bits()); - const int image_stride = image.bytesPerLine() / sizeof(MixedPixel); - const uint32_t* bw_mask_line = bw_mask.data(); - const int bw_mask_stride = bw_mask.wordsPerLine(); - const int width = image.width(); - const int height = image.height(); - const uint32_t msb = uint32_t(1) << 31; - const auto fillingPixel = static_cast(color.rgba()); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { - image_line[x] = fillingPixel; - } - } - image_line += image_stride; - bw_mask_line += bw_mask_stride; - } + auto* image_line = reinterpret_cast(image.bits()); + const int image_stride = image.bytesPerLine() / sizeof(MixedPixel); + const uint32_t* bw_mask_line = bw_mask.data(); + const int bw_mask_stride = bw_mask.wordsPerLine(); + const int width = image.width(); + const int height = image.height(); + const uint32_t msb = uint32_t(1) << 31; + const auto fillingPixel = static_cast(color.rgba()); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { + image_line[x] = fillingPixel; + } + } + image_line += image_stride; + bw_mask_line += bw_mask_stride; + } } void fillExcept(BinaryImage& image, const BinaryImage& bw_mask, const BWColor color) { - uint32_t* image_line = image.data(); - const int image_stride = image.wordsPerLine(); - const uint32_t* bw_mask_line = bw_mask.data(); - const int bw_mask_stride = bw_mask.wordsPerLine(); - const int width = image.width(); - const int height = image.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { - if (color == BLACK) { - image_line[x >> 5] |= (msb >> (x & 31)); - } else { - image_line[x >> 5] &= ~(msb >> (x & 31)); - } - } + uint32_t* image_line = image.data(); + const int image_stride = image.wordsPerLine(); + const uint32_t* bw_mask_line = bw_mask.data(); + const int bw_mask_stride = bw_mask.wordsPerLine(); + const int width = image.width(); + const int height = image.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { + if (color == BLACK) { + image_line[x >> 5] |= (msb >> (x & 31)); + } else { + image_line[x >> 5] &= ~(msb >> (x & 31)); } - image_line += image_stride; - bw_mask_line += bw_mask_stride; + } } + image_line += image_stride; + bw_mask_line += bw_mask_stride; + } } void removeAutoPictureZones(ZoneSet& picture_zones) { - for (auto it = picture_zones.begin(); it != picture_zones.end();) { - const Zone& zone = *it; - if (zone.properties().locateOrDefault()->zone_category() - == ZoneCategoryProperty::RECTANGULAR_OUTLINE) { - it = picture_zones.erase(it); - } else { - ++it; - } + for (auto it = picture_zones.begin(); it != picture_zones.end();) { + const Zone& zone = *it; + if (zone.properties().locateOrDefault()->zone_category() + == ZoneCategoryProperty::RECTANGULAR_OUTLINE) { + it = picture_zones.erase(it); + } else { + ++it; } + } +} + +bool updateBlackOnWhite(const FilterData& input, const PageId& pageId, const intrusive_ptr& settings) { + QSettings appSettings; + Params params = settings->getParams(pageId); + if ((appSettings.value("settings/blackOnWhiteDetection", true).toBool() + && appSettings.value("settings/blackOnWhiteDetectionAtOutput", true).toBool()) + && !settings->getOutputProcessingParams(pageId).isBlackOnWhiteSetManually()) { + if (params.isBlackOnWhite() != input.isBlackOnWhite()) { + params.setBlackOnWhite(input.isBlackOnWhite()); + settings->setParams(pageId, params); + } + return input.isBlackOnWhite(); + } else { + return params.isBlackOnWhite(); + } +} + +BackgroundColorCalculator getBackgroundColorCalculator(const PageId& pageId, const intrusive_ptr& settings) { + QSettings appSettings; + if (!(appSettings.value("settings/blackOnWhiteDetection", true).toBool() + && appSettings.value("settings/blackOnWhiteDetectionAtOutput", true).toBool()) + && !settings->getOutputProcessingParams(pageId).isBlackOnWhiteSetManually()) { + return BackgroundColorCalculator(); + } else { + return BackgroundColorCalculator(false); + } } } // namespace @@ -249,28 +278,28 @@ OutputGenerator::OutputGenerator(const Dpi& dpi, const PictureShapeOptions& picture_shape_options, const DewarpingOptions& dewarping_options, const OutputProcessingParams& output_processing_params, - DespeckleLevel despeckle_level, + const double despeckle_level, const ImageTransformation& xform, const QPolygonF& content_rect_phys) - : m_dpi(dpi), - m_colorParams(color_params), - m_splittingOptions(splitting_options), - m_pictureShapeOptions(picture_shape_options), - m_dewarpingOptions(dewarping_options), - m_outputProcessingParams(output_processing_params), - m_xform(xform), - m_outRect(xform.resultingRect().toRect()), - m_contentRect(xform.transform().map(content_rect_phys).boundingRect().toRect()), - m_despeckleLevel(despeckle_level) { - assert(m_outRect.topLeft() == QPoint(0, 0)); - - if (!m_contentRect.isEmpty()) { - // prevents a crash due to round error on transforming virtual coordinates to output image coordinates - // when m_contentRect coordinates could exceed m_outRect ones by 1 px - m_contentRect = m_contentRect.intersected(m_outRect); - // Note that QRect::contains() always returns false, so we don't use it here. - assert(m_outRect.contains(m_contentRect.topLeft()) && m_outRect.contains(m_contentRect.bottomRight())); - } + : m_dpi(dpi), + m_colorParams(color_params), + m_splittingOptions(splitting_options), + m_pictureShapeOptions(picture_shape_options), + m_dewarpingOptions(dewarping_options), + m_outputProcessingParams(output_processing_params), + m_xform(xform), + m_outRect(xform.resultingRect().toRect()), + m_contentRect(xform.transform().map(content_rect_phys).boundingRect().toRect()), + m_despeckleLevel(despeckle_level) { + assert(m_outRect.topLeft() == QPoint(0, 0)); + + if (!m_contentRect.isEmpty()) { + // prevents a crash due to round error on transforming virtual coordinates to output image coordinates + // when m_contentRect coordinates could exceed m_outRect ones by 1 px + m_contentRect = m_contentRect.intersected(m_outRect); + // Note that QRect::contains() always returns false, so we don't use it here. + assert(m_outRect.contains(m_contentRect.topLeft()) && m_outRect.contains(m_contentRect.bottomRight())); + } } QImage OutputGenerator::process(const TaskStatus& status, @@ -282,36 +311,36 @@ QImage OutputGenerator::process(const TaskStatus& status, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, + const PageId& pageId, + const intrusive_ptr& settings, SplitImage* splitImage) { - QImage image(processImpl(status, input, picture_zones, fill_zones, distortion_model, depth_perception, - auto_picture_mask, speckles_image, dbg, p_pageId, p_settings, splitImage)); - // Set the correct DPI. - const RenderParams renderParams(m_colorParams, m_splittingOptions); - const Dpm output_dpm(m_dpi); - - if (!renderParams.splitOutput()) { - assert(!image.isNull()); - - image.setDotsPerMeterX(output_dpm.horizontal()); - image.setDotsPerMeterY(output_dpm.vertical()); - } else { - splitImage->applyToLayerImages([&output_dpm](QImage& img) { - img.setDotsPerMeterX(output_dpm.horizontal()); - img.setDotsPerMeterY(output_dpm.vertical()); - }); - } - - return image; + QImage image(processImpl(status, input, picture_zones, fill_zones, distortion_model, depth_perception, + auto_picture_mask, speckles_image, dbg, pageId, settings, splitImage)); + // Set the correct DPI. + const RenderParams renderParams(m_colorParams, m_splittingOptions); + const Dpm output_dpm(m_dpi); + + if (!renderParams.splitOutput()) { + assert(!image.isNull()); + + image.setDotsPerMeterX(output_dpm.horizontal()); + image.setDotsPerMeterY(output_dpm.vertical()); + } else { + splitImage->applyToLayerImages([&output_dpm](QImage& img) { + img.setDotsPerMeterX(output_dpm.horizontal()); + img.setDotsPerMeterY(output_dpm.vertical()); + }); + } + + return image; } QSize OutputGenerator::outputImageSize() const { - return m_outRect.size(); + return m_outRect.size(); } QRect OutputGenerator::outputContentRect() const { - return m_contentRect; + return m_contentRect; } GrayImage OutputGenerator::normalizeIlluminationGray(const TaskStatus& status, @@ -321,36 +350,36 @@ GrayImage OutputGenerator::normalizeIlluminationGray(const TaskStatus& status, const QRect& target_rect, GrayImage* background, DebugImages* const dbg) { - GrayImage to_be_normalized(transformToGray(input, xform, target_rect, OutsidePixels::assumeWeakNearest())); - if (dbg) { - dbg->add(to_be_normalized, "to_be_normalized"); - } + GrayImage to_be_normalized(transformToGray(input, xform, target_rect, OutsidePixels::assumeWeakNearest())); + if (dbg) { + dbg->add(to_be_normalized, "to_be_normalized"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - QPolygonF transformed_consideration_area(xform.map(area_to_consider)); - transformed_consideration_area.translate(-target_rect.topLeft()); + QPolygonF transformed_consideration_area(xform.map(area_to_consider)); + transformed_consideration_area.translate(-target_rect.topLeft()); - const PolynomialSurface bg_ps(estimateBackground(to_be_normalized, transformed_consideration_area, status, dbg)); + const PolynomialSurface bg_ps(estimateBackground(to_be_normalized, transformed_consideration_area, status, dbg)); - status.throwIfCancelled(); + status.throwIfCancelled(); - GrayImage bg_img(bg_ps.render(to_be_normalized.size())); - if (dbg) { - dbg->add(bg_img, "background"); - } - if (background) { - *background = bg_img; - } + GrayImage bg_img(bg_ps.render(to_be_normalized.size())); + if (dbg) { + dbg->add(bg_img, "background"); + } + if (background) { + *background = bg_img; + } - status.throwIfCancelled(); + status.throwIfCancelled(); - grayRasterOp(bg_img, to_be_normalized); - if (dbg) { - dbg->add(bg_img, "normalized_illumination"); - } + grayRasterOp(bg_img, to_be_normalized); + if (dbg) { + dbg->add(bg_img, "normalized_illumination"); + } - return bg_img; + return bg_img; } // OutputGenerator::normalizeIlluminationGray imageproc::BinaryImage OutputGenerator::estimateBinarizationMask(const TaskStatus& status, @@ -358,82 +387,82 @@ imageproc::BinaryImage OutputGenerator::estimateBinarizationMask(const TaskStatu const QRect& source_rect, const QRect& source_sub_rect, DebugImages* const dbg) const { - assert(source_rect.contains(source_sub_rect)); + assert(source_rect.contains(source_sub_rect)); - // If we need to strip some of the margins from a grayscale - // image, we may actually do it without copying anything. - // We are going to construct a QImage from existing data. - // That image won't own that data, but gray_source is not - // going anywhere, so it's fine. + // If we need to strip some of the margins from a grayscale + // image, we may actually do it without copying anything. + // We are going to construct a QImage from existing data. + // That image won't own that data, but gray_source is not + // going anywhere, so it's fine. - GrayImage trimmed_image; + GrayImage trimmed_image; - if (source_rect == source_sub_rect) { - trimmed_image = gray_source; // Shallow copy. - } else { - // Sub-rectangle in input image coordinates. - QRect relative_subrect(source_sub_rect); - relative_subrect.moveTopLeft(source_sub_rect.topLeft() - source_rect.topLeft()); + if (source_rect == source_sub_rect) { + trimmed_image = gray_source; // Shallow copy. + } else { + // Sub-rectangle in input image coordinates. + QRect relative_subrect(source_sub_rect); + relative_subrect.moveTopLeft(source_sub_rect.topLeft() - source_rect.topLeft()); - const int stride = gray_source.stride(); - const int offset = relative_subrect.top() * stride + relative_subrect.left(); + const int stride = gray_source.stride(); + const int offset = relative_subrect.top() * stride + relative_subrect.left(); - trimmed_image = GrayImage(QImage(gray_source.data() + offset, relative_subrect.width(), - relative_subrect.height(), stride, QImage::Format_Indexed8)); - } + trimmed_image = GrayImage(QImage(gray_source.data() + offset, relative_subrect.width(), relative_subrect.height(), + stride, QImage::Format_Indexed8)); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - const QSize downscaled_size(to300dpi(trimmed_image.size(), m_dpi)); + const QSize downscaled_size(to300dpi(trimmed_image.size(), m_dpi)); - // A 300dpi version of trimmed_image. - GrayImage downscaled_input(scaleToGray(trimmed_image, downscaled_size)); - trimmed_image = GrayImage(); // Save memory. - status.throwIfCancelled(); + // A 300dpi version of trimmed_image. + GrayImage downscaled_input(scaleToGray(trimmed_image, downscaled_size)); + trimmed_image = GrayImage(); // Save memory. + status.throwIfCancelled(); - // Light areas indicate pictures. - GrayImage picture_areas(detectPictures(downscaled_input, status, dbg)); - downscaled_input = GrayImage(); // Save memory. - status.throwIfCancelled(); + // Light areas indicate pictures. + GrayImage picture_areas(detectPictures(downscaled_input, status, dbg)); + downscaled_input = GrayImage(); // Save memory. + status.throwIfCancelled(); - const BinaryThreshold threshold(48); - // Scale back to original size. - picture_areas = scaleToGray(picture_areas, source_sub_rect.size()); + const BinaryThreshold threshold(48); + // Scale back to original size. + picture_areas = scaleToGray(picture_areas, source_sub_rect.size()); - return BinaryImage(picture_areas, threshold); + return BinaryImage(picture_areas, threshold); } // OutputGenerator::estimateBinarizationMask void OutputGenerator::modifyBinarizationMask(imageproc::BinaryImage& bw_mask, const QRect& mask_rect, const ZoneSet& zones) const { - QTransform xform(m_xform.transform()); - xform *= QTransform().translate(-mask_rect.x(), -mask_rect.y()); + QTransform xform(m_xform.transform()); + xform *= QTransform().translate(-mask_rect.x(), -mask_rect.y()); - typedef PictureLayerProperty PLP; + typedef PictureLayerProperty PLP; - // Pass 1: ERASER1 - for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::ERASER1) { - const QPolygonF poly(zone.spline().toPolygon()); - PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); - } + // Pass 1: ERASER1 + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ERASER1) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); } + } - // Pass 2: PAINTER2 - for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::PAINTER2) { - const QPolygonF poly(zone.spline().toPolygon()); - PolygonRasterizer::fill(bw_mask, WHITE, xform.map(poly), Qt::WindingFill); - } + // Pass 2: PAINTER2 + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::PAINTER2) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bw_mask, WHITE, xform.map(poly), Qt::WindingFill); } + } - // Pass 1: ERASER3 - for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::ERASER3) { - const QPolygonF poly(zone.spline().toPolygon()); - PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); - } + // Pass 1: ERASER3 + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ERASER3) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); } + } } QImage OutputGenerator::processImpl(const TaskStatus& status, @@ -445,17 +474,17 @@ QImage OutputGenerator::processImpl(const TaskStatus& status, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, + const PageId& pageId, + const intrusive_ptr& settings, SplitImage* splitImage) { - if ((m_dewarpingOptions.dewarpingMode() == AUTO) || (m_dewarpingOptions.dewarpingMode() == MARGINAL) - || ((m_dewarpingOptions.dewarpingMode() == MANUAL) && distortion_model.isValid())) { - return processWithDewarping(status, input, picture_zones, fill_zones, distortion_model, depth_perception, - auto_picture_mask, speckles_image, dbg, p_pageId, p_settings, splitImage); - } else { - return processWithoutDewarping(status, input, picture_zones, fill_zones, auto_picture_mask, speckles_image, dbg, - p_pageId, p_settings, splitImage); - } + if ((m_dewarpingOptions.dewarpingMode() == AUTO) || (m_dewarpingOptions.dewarpingMode() == MARGINAL) + || ((m_dewarpingOptions.dewarpingMode() == MANUAL) && distortion_model.isValid())) { + return processWithDewarping(status, input, picture_zones, fill_zones, distortion_model, depth_perception, + auto_picture_mask, speckles_image, dbg, pageId, settings, splitImage); + } else { + return processWithoutDewarping(status, input, picture_zones, fill_zones, auto_picture_mask, speckles_image, dbg, + pageId, settings, splitImage); + } } QImage OutputGenerator::processWithoutDewarping(const TaskStatus& status, @@ -465,468 +494,500 @@ QImage OutputGenerator::processWithoutDewarping(const TaskStatus& status, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, + const PageId& pageId, + const intrusive_ptr& settings, SplitImage* splitImage) { - const RenderParams render_params(m_colorParams, m_splittingOptions); - - const QPolygonF contentArea = m_xform.resultingPreCropArea().intersected( - QRectF(render_params.cutMargins() ? m_contentRect : m_outRect)); - const QRect contentRect = contentArea.boundingRect().toRect(); - const QPolygonF outCropArea = m_xform.resultingPreCropArea().intersected(QRectF(m_outRect)); - - const QSize target_size(m_outRect.size().expandedTo(QSize(1, 1))); - // If the content area is empty or outside the cropping area, return a blank page. - if (contentRect.isEmpty()) { - QImage emptyImage(BinaryImage(target_size, WHITE).toQImage()); - if (!render_params.splitOutput()) { - return emptyImage; - } else { - splitImage->setForegroundImage(emptyImage); - splitImage->setBackgroundImage(emptyImage.convertToFormat(QImage::Format_Indexed8)); - return QImage(); - } + const RenderParams render_params(m_colorParams, m_splittingOptions); + + const QPolygonF preCropArea = [this, &render_params]() { + if (render_params.fillOffcut()) { + return m_xform.resultingPreCropArea(); + } else { + const QPolygonF imageRectInOutputCs = m_xform.transform().map(m_xform.origRect()); + return imageRectInOutputCs.intersected(QRectF(m_outRect)); + } + }(); + const QPolygonF contentArea + = preCropArea.intersected(QRectF(render_params.fillMargins() ? m_contentRect : m_outRect)); + const QRect contentRect = contentArea.boundingRect().toRect(); + const QPolygonF outCropArea = preCropArea.intersected(QRectF(m_outRect)); + + const QSize target_size(m_outRect.size().expandedTo(QSize(1, 1))); + // If the content area is empty or outside the cropping area, return a blank page. + if (contentRect.isEmpty()) { + QImage emptyImage(BinaryImage(target_size, WHITE).toQImage()); + if (!render_params.splitOutput()) { + return emptyImage; + } else { + splitImage->setForegroundImage(emptyImage); + splitImage->setBackgroundImage(emptyImage.convertToFormat(QImage::Format_Indexed8)); + return QImage(); + } + } + + // For various reasons, we need some whitespace around the content + // area. This is the number of pixels of such whitespace. + const int content_margin = m_dpi.vertical() * 20 / 300; + // The content area (in output image coordinates) extended + // with content_margin. Note that we prevent that extension + // from reaching the neighboring page. + // This is the area we are going to pass to estimateBackground(). + // estimateBackground() needs some margins around content, and + // generally smaller margins are better, except when there is + // some garbage that connects the content to the edge of the + // image area. + const QRect workingBoundingRect( + preCropArea + .intersected(QRectF(contentRect.adjusted(-content_margin, -content_margin, content_margin, content_margin))) + .boundingRect() + .toRect()); + const QRect contentRectInWorkingCs(contentRect.translated(-workingBoundingRect.topLeft())); + const QPolygonF contentAreaInWorkingCs(contentArea.translated(-workingBoundingRect.topLeft())); + const QPolygonF outCropAreaInWorkingCs(outCropArea.translated(-workingBoundingRect.topLeft())); + const QPolygonF preCropAreaInOriginalCs(m_xform.transformBack().map(preCropArea)); + const QPolygonF contentAreaInOriginalCs(m_xform.transformBack().map(contentArea)); + const QPolygonF outCropAreaInOriginalCs(m_xform.transformBack().map(outCropArea)); + + const bool isBlackOnWhite = updateBlackOnWhite(input, pageId, settings); + const GrayImage inputGrayImage = isBlackOnWhite ? input.grayImage() : input.grayImage().inverted(); + const QImage inputOrigImage = [&input, &isBlackOnWhite]() { + QImage result = input.origImage(); + if (!result.allGray() && (result.format() != QImage::Format_ARGB32) && (result.format() != QImage::Format_RGB32)) { + result = result.convertToFormat(QImage::Format_RGB32); + } + if (!isBlackOnWhite) { + result.invertPixels(); + } + + return result; + }(); + + const BackgroundColorCalculator backgroundColorCalculator = getBackgroundColorCalculator(pageId, settings); + QColor outsideBackgroundColor = backgroundColorCalculator.calcDominantBackgroundColor( + inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); + + const bool needNormalizeIllumination + = (render_params.normalizeIllumination() && render_params.needBinarization()) + || (render_params.normalizeIlluminationColor() && !render_params.needBinarization()); + + QImage maybe_normalized; + if (needNormalizeIllumination) { + maybe_normalized = normalizeIlluminationGray(status, inputGrayImage, preCropAreaInOriginalCs, m_xform.transform(), + workingBoundingRect, nullptr, dbg); + } else { + if (inputOrigImage.allGray()) { + maybe_normalized = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeColor(outsideBackgroundColor)); + } else { + maybe_normalized = transform(inputOrigImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeColor(outsideBackgroundColor)); } + } - // For various reasons, we need some whitespace around the content - // area. This is the number of pixels of such whitespace. - const int content_margin = m_dpi.vertical() * 20 / 300; - // The content area (in output image coordinates) extended - // with content_margin. Note that we prevent that extension - // from reaching the neighboring page. - // This is the area we are going to pass to estimateBackground(). - // estimateBackground() needs some margins around content, and - // generally smaller margins are better, except when there is - // some garbage that connects the content to the edge of the - // image area. - const QRect workingBoundingRect(m_xform.resultingPreCropArea() - .intersected(QRectF(contentRect.adjusted(-content_margin, -content_margin, - content_margin, content_margin))) - .boundingRect() - .toRect()); - const QRect contentRectInWorkingCs(contentRect.translated(-workingBoundingRect.topLeft())); - const QPolygonF contentAreaInWorkingCs(contentArea.translated(-workingBoundingRect.topLeft())); - const QPolygonF outCropAreaInWorkingCs(outCropArea.translated(-workingBoundingRect.topLeft())); - const QPolygonF preCropAreaInOriginalCs(m_xform.transformBack().map(m_xform.resultingPreCropArea())); - const QPolygonF contentAreaInOriginalCs(m_xform.transformBack().map(contentArea)); - const QPolygonF outCropAreaInOriginalCs(m_xform.transformBack().map(outCropArea)); - - const GrayImage inputGrayImage = input.grayImage(); - const QImage inputOrigImage = [&input]() { - QImage result = input.origImage(); - if (!result.allGray() && (result.format() != QImage::Format_ARGB32) - && (result.format() != QImage::Format_RGB32)) { - result = result.convertToFormat(QImage::Format_RGB32); - } + if (needNormalizeIllumination && !inputOrigImage.allGray()) { + assert(maybe_normalized.format() == QImage::Format_Indexed8); + QImage tmp(transform(inputOrigImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeColor(outsideBackgroundColor))); - return result; - }(); + status.throwIfCancelled(); - QColor outsideBackgroundColor = BackgroundColorCalculator::calcDominantBackgroundColor( - inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); + adjustBrightnessGrayscale(tmp, maybe_normalized); + maybe_normalized = tmp; + } - const bool needNormalizeIllumination - = (render_params.normalizeIllumination() && render_params.needBinarization()) - || (render_params.normalizeIlluminationColor() && !render_params.needBinarization()); + if (dbg) { + dbg->add(maybe_normalized, "maybe_normalized"); + } - QImage maybe_normalized; - if (needNormalizeIllumination) { - maybe_normalized = normalizeIlluminationGray(status, inputGrayImage, preCropAreaInOriginalCs, - m_xform.transform(), workingBoundingRect, nullptr, dbg); + if (needNormalizeIllumination) { + outsideBackgroundColor + = backgroundColorCalculator.calcDominantBackgroundColor(maybe_normalized, outCropAreaInWorkingCs, dbg); + } + + status.throwIfCancelled(); + + if (render_params.binaryOutput()) { + BinaryImage dst(target_size, WHITE); + + QImage maybe_smoothed; + // We only do smoothing if we are going to do binarization later. + if (!render_params.needSavitzkyGolaySmoothing()) { + maybe_smoothed = maybe_normalized; } else { - if (inputOrigImage.allGray()) { - maybe_normalized = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeColor(outsideBackgroundColor)); - } else { - maybe_normalized = transform(inputOrigImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeColor(outsideBackgroundColor)); - } + maybe_smoothed = smoothToGrayscale(maybe_normalized, m_dpi); + if (dbg) { + dbg->add(maybe_smoothed, "smoothed"); + } } - if (needNormalizeIllumination && !inputOrigImage.allGray()) { - assert(maybe_normalized.format() == QImage::Format_Indexed8); - QImage tmp(transform(inputOrigImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeColor(outsideBackgroundColor))); + // don't destroy as it's needed for color segmentation + if (!render_params.needColorSegmentation()) { + maybe_normalized = QImage(); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - adjustBrightnessGrayscale(tmp, maybe_normalized); - maybe_normalized = tmp; - } + BinaryImage bw_content(binarize(maybe_smoothed, contentAreaInWorkingCs)); + maybe_smoothed = QImage(); if (dbg) { - dbg->add(maybe_normalized, "maybe_normalized"); + dbg->add(bw_content, "binarized_and_cropped"); } - if (needNormalizeIllumination) { - outsideBackgroundColor - = BackgroundColorCalculator::calcDominantBackgroundColor(maybe_normalized, outCropAreaInWorkingCs, dbg); + if (render_params.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(bw_content, status); + if (dbg) { + dbg->add(bw_content, "edges_smoothed"); + } } status.throwIfCancelled(); - if (render_params.binaryOutput()) { - BinaryImage dst(target_size, WHITE); + rasterOp(dst, contentRect, bw_content, contentRectInWorkingCs.topLeft()); + bw_content.release(); // Save memory. - QImage maybe_smoothed; - // We only do smoothing if we are going to do binarization later. - if (!render_params.needSavitzkyGolaySmoothing()) { - maybe_smoothed = maybe_normalized; - } else { - maybe_smoothed = smoothToGrayscale(maybe_normalized, m_dpi); - if (dbg) { - dbg->add(maybe_smoothed, "smoothed"); - } - } + // It's important to keep despeckling the very last operation + // affecting the binary part of the output. That's because + // we will be reconstructing the input to this despeckling + // operation from the final output file. + maybeDespeckleInPlace(dst, m_outRect, m_outRect, m_despeckleLevel, speckles_image, m_dpi, status, dbg); - // don't destroy as it's needed for color segmentation - if (!render_params.needColorSegmentation()) { - maybe_normalized = QImage(); - } + if (!render_params.needColorSegmentation()) { + if (!isBlackOnWhite) { + dst.invert(); + } - status.throwIfCancelled(); - - BinaryImage bw_content(binarize(maybe_smoothed, contentAreaInWorkingCs)); - - maybe_smoothed = QImage(); - if (dbg) { - dbg->add(bw_content, "binarized_and_cropped"); - } + applyFillZonesInPlace(dst, fill_zones); - if (render_params.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(bw_content, status); - if (dbg) { - dbg->add(bw_content, "edges_smoothed"); - } + return dst.toQImage(); + } else { + QImage segmented_image; + { + QImage color_image(target_size, maybe_normalized.format()); + color_image.fill(Qt::white); + if (maybe_normalized.format() == QImage::Format_Indexed8) { + color_image.setColorTable(createGrayscalePalette()); } + drawOver(color_image, contentRect, maybe_normalized, contentRectInWorkingCs); + maybe_normalized = QImage(); - status.throwIfCancelled(); + segmented_image = segmentImage(dst, color_image); + dst.release(); + } - rasterOp(dst, contentRect, bw_content, contentRectInWorkingCs.topLeft()); - bw_content.release(); // Save memory. + if (dbg) { + dbg->add(segmented_image, "segmented"); + } - // It's important to keep despeckling the very last operation - // affecting the binary part of the output. That's because - // we will be reconstructing the input to this despeckling - // operation from the final output file. - maybeDespeckleInPlace(dst, m_outRect, m_outRect, m_despeckleLevel, speckles_image, m_dpi, status, dbg); + status.throwIfCancelled(); - if (!render_params.needColorSegmentation()) { - applyFillZonesInPlace(dst, fill_zones); + if (!isBlackOnWhite) { + segmented_image.invertPixels(); + } - return dst.toQImage(); - } else { - QImage segmented_image; - { - QImage color_image(target_size, maybe_normalized.format()); - color_image.fill(Qt::white); - if (maybe_normalized.format() == QImage::Format_Indexed8) { - color_image.setColorTable(createGrayscalePalette()); - } - drawOver(color_image, contentRect, maybe_normalized, contentRectInWorkingCs); - maybe_normalized = QImage(); - - segmented_image = segmentImage(dst, color_image); - dst.release(); - } + applyFillZonesInPlace(segmented_image, fill_zones, false); - if (dbg) { - dbg->add(segmented_image, "segmented"); - } + if (dbg) { + dbg->add(segmented_image, "segmented_with_fill_zones"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - applyFillZonesInPlace(segmented_image, fill_zones, false); + if (render_params.posterize()) { + segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); - if (dbg) { - dbg->add(segmented_image, "segmented_with_fill_zones"); - } + if (dbg) { + dbg->add(segmented_image, "posterized"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); + } - if (render_params.posterize()) { - segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); + return segmented_image; + } + } - if (dbg) { - dbg->add(segmented_image, "posterized"); - } + BinaryImage bw_content_mask_output; + QImage original_background; + if (render_params.mixedOutput()) { + BinaryImage bw_mask(workingBoundingRect.size(), BLACK); - status.throwIfCancelled(); - } + if ((m_pictureShapeOptions.getPictureShape() != RECTANGULAR_SHAPE) + || !m_outputProcessingParams.isAutoZonesFound()) { + if (m_pictureShapeOptions.getPictureShape() != OFF_SHAPE) { + bw_mask = estimateBinarizationMask(status, GrayImage(maybe_normalized), workingBoundingRect, + workingBoundingRect, dbg); + } - return segmented_image; - } + removeAutoPictureZones(picture_zones); + settings->setPictureZones(pageId, picture_zones); + m_outputProcessingParams.setAutoZonesFound(false); + settings->setOutputProcessingParams(pageId, m_outputProcessingParams); } + if ((m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE) + && !m_outputProcessingParams.isAutoZonesFound()) { + std::vector areas; + bw_mask.rectangularizeAreas(areas, WHITE, m_pictureShapeOptions.getSensitivity()); - BinaryImage bw_content_mask_output; - QImage original_background; - if (render_params.mixedOutput()) { - BinaryImage bw_mask(workingBoundingRect.size(), BLACK); + QTransform xform1(m_xform.transform()); + xform1 *= QTransform().translate(-workingBoundingRect.x(), -workingBoundingRect.y()); + QTransform inv_xform(xform1.inverted()); - if ((m_pictureShapeOptions.getPictureShape() != RECTANGULAR_SHAPE) - || !m_outputProcessingParams.isAutoZonesFound()) { - if (m_pictureShapeOptions.getPictureShape() != OFF_SHAPE) { - bw_mask = estimateBinarizationMask(status, GrayImage(maybe_normalized), workingBoundingRect, - workingBoundingRect, dbg); - } + for (auto i : areas) { + QRectF area0(i); + QPolygonF area1(area0); + QPolygonF area(inv_xform.map(area1)); - removeAutoPictureZones(picture_zones); - p_settings->setPictureZones(p_pageId, picture_zones); - m_outputProcessingParams.setAutoZonesFound(false); - p_settings->setOutputProcessingParams(p_pageId, m_outputProcessingParams); - } - if ((m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE) - && !m_outputProcessingParams.isAutoZonesFound()) { - std::vector areas; - bw_mask.rectangularizeAreas(areas, WHITE, m_pictureShapeOptions.getSensitivity()); + Zone zone1(area); - QTransform xform1(m_xform.transform()); - xform1 *= QTransform().translate(-workingBoundingRect.x(), -workingBoundingRect.y()); - QTransform inv_xform(xform1.inverted()); + picture_zones.add(zone1); + } + settings->setPictureZones(pageId, picture_zones); + m_outputProcessingParams.setAutoZonesFound(true); + settings->setOutputProcessingParams(pageId, m_outputProcessingParams); - for (auto i : areas) { - QRectF area0(i); - QPolygonF area1(area0); - QPolygonF area(inv_xform.map(area1)); + bw_mask.fill(BLACK); + } - Zone zone1(area); + if (dbg) { + dbg->add(bw_mask, "bw_mask"); + } - picture_zones.add(zone1); - } - p_settings->setPictureZones(p_pageId, picture_zones); - m_outputProcessingParams.setAutoZonesFound(true); - p_settings->setOutputProcessingParams(p_pageId, m_outputProcessingParams); + if (auto_picture_mask) { + if (auto_picture_mask->size() != target_size) { + BinaryImage(target_size).swap(*auto_picture_mask); + } + auto_picture_mask->fill(BLACK); - bw_mask.fill(BLACK); - } + rasterOp(*auto_picture_mask, contentRect, bw_mask, contentRectInWorkingCs.topLeft()); + } - if (dbg) { - dbg->add(bw_mask, "bw_mask"); - } + status.throwIfCancelled(); - if (auto_picture_mask) { - if (auto_picture_mask->size() != target_size) { - BinaryImage(target_size).swap(*auto_picture_mask); - } - auto_picture_mask->fill(BLACK); + modifyBinarizationMask(bw_mask, workingBoundingRect, picture_zones); + fillMarginsInPlace(bw_mask, contentAreaInWorkingCs, BLACK); + if (dbg) { + dbg->add(bw_mask, "bw_mask with zones"); + } - rasterOp(*auto_picture_mask, contentRect, bw_mask, contentRectInWorkingCs.topLeft()); - } + { + bw_content_mask_output = BinaryImage(target_size, BLACK); + rasterOp(bw_content_mask_output, contentRect, bw_mask, contentRectInWorkingCs.topLeft()); + } - status.throwIfCancelled(); + status.throwIfCancelled(); - modifyBinarizationMask(bw_mask, workingBoundingRect, picture_zones); - fillMarginsInPlace(bw_mask, contentAreaInWorkingCs, BLACK); + if (render_params.needBinarization()) { + QImage maybe_smoothed; + if (!render_params.needSavitzkyGolaySmoothing()) { + maybe_smoothed = maybe_normalized; + } else { + maybe_smoothed = smoothToGrayscale(maybe_normalized, m_dpi); if (dbg) { - dbg->add(bw_mask, "bw_mask with zones"); + dbg->add(maybe_smoothed, "smoothed"); } + } - { - bw_content_mask_output = BinaryImage(target_size, BLACK); - rasterOp(bw_content_mask_output, contentRect, bw_mask, contentRectInWorkingCs.topLeft()); - } - - status.throwIfCancelled(); - - if (render_params.needBinarization()) { - QImage maybe_smoothed; - if (!render_params.needSavitzkyGolaySmoothing()) { - maybe_smoothed = maybe_normalized; - } else { - maybe_smoothed = smoothToGrayscale(maybe_normalized, m_dpi); - if (dbg) { - dbg->add(maybe_smoothed, "smoothed"); - } - } - - BinaryImage bw_mask_filled(bw_mask); - fillMarginsInPlace(bw_mask_filled, contentAreaInWorkingCs, WHITE); + BinaryImage bw_mask_filled(bw_mask); + fillMarginsInPlace(bw_mask_filled, contentAreaInWorkingCs, WHITE); - BinaryImage bw_content(binarize(maybe_smoothed, bw_mask_filled)); + BinaryImage bw_content(binarize(maybe_smoothed, bw_mask_filled)); - bw_mask_filled.release(); - maybe_smoothed = QImage(); // Save memory. - - if (dbg) { - dbg->add(bw_content, "binarized_and_cropped"); - } + bw_mask_filled.release(); + maybe_smoothed = QImage(); // Save memory. - status.throwIfCancelled(); - - if (render_params.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(bw_content, status); - if (dbg) { - dbg->add(bw_content, "edges_smoothed"); - } - } - - // We don't want speckles in non-B/W areas, as they would - // then get visualized on the Despeckling tab. - status.throwIfCancelled(); + if (dbg) { + dbg->add(bw_content, "binarized_and_cropped"); + } - // It's important to keep despeckling the very last operation - // affecting the binary part of the output. That's because - // we will be reconstructing the input to this despeckling - // operation from the final output file. - maybeDespeckleInPlace(bw_content, workingBoundingRect, contentRect, m_despeckleLevel, speckles_image, m_dpi, - status, dbg); + status.throwIfCancelled(); - status.throwIfCancelled(); - - if (needNormalizeIllumination && !render_params.normalizeIlluminationColor()) { - outsideBackgroundColor = BackgroundColorCalculator::calcDominantBackgroundColor( - inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); + if (render_params.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(bw_content, status); + if (dbg) { + dbg->add(bw_content, "edges_smoothed"); + } + } - if (inputOrigImage.allGray()) { - maybe_normalized = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeColor(outsideBackgroundColor)); - } else { - maybe_normalized = transform(inputOrigImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeColor(outsideBackgroundColor)); - } + // We don't want speckles in non-B/W areas, as they would + // then get visualized on the Despeckling tab. + status.throwIfCancelled(); - status.throwIfCancelled(); - } + // It's important to keep despeckling the very last operation + // affecting the binary part of the output. That's because + // we will be reconstructing the input to this despeckling + // operation from the final output file. + maybeDespeckleInPlace(bw_content, workingBoundingRect, contentRect, m_despeckleLevel, speckles_image, m_dpi, + status, dbg); - if (render_params.originalBackground()) { - original_background = maybe_normalized; + status.throwIfCancelled(); - QImage original_background_dst(target_size, original_background.format()); - if (original_background.format() == QImage::Format_Indexed8) { - original_background_dst.setColorTable(createGrayscalePalette()); - } - if (original_background_dst.isNull()) { - // Both the constructor and setColorTable() above can leave the image null. - throw std::bad_alloc(); - } + if (needNormalizeIllumination && !render_params.normalizeIlluminationColor()) { + outsideBackgroundColor = backgroundColorCalculator.calcDominantBackgroundColor( + inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); - QColor outsideOriginalBackgroundColor = outsideBackgroundColor; - if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { - outsideOriginalBackgroundColor = Qt::white; - } - fillMarginsInPlace(original_background, contentAreaInWorkingCs, outsideOriginalBackgroundColor); - original_background_dst.fill(outsideOriginalBackgroundColor); + if (inputOrigImage.allGray()) { + maybe_normalized = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeColor(outsideBackgroundColor)); + } else { + maybe_normalized = transform(inputOrigImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeColor(outsideBackgroundColor)); + } - drawOver(original_background_dst, contentRect, original_background, contentRectInWorkingCs); + status.throwIfCancelled(); + } - reserveBlackAndWhite(original_background_dst); + if (render_params.originalBackground()) { + original_background = maybe_normalized; - original_background = original_background_dst; + QImage original_background_dst(target_size, original_background.format()); + if (original_background.format() == QImage::Format_Indexed8) { + original_background_dst.setColorTable(createGrayscalePalette()); + } + if (original_background_dst.isNull()) { + // Both the constructor and setColorTable() above can leave the image null. + throw std::bad_alloc(); + } - status.throwIfCancelled(); - } + QColor outsideOriginalBackgroundColor = outsideBackgroundColor; + if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { + outsideOriginalBackgroundColor = isBlackOnWhite ? Qt::white : Qt::black; + } + fillMarginsInPlace(original_background, contentAreaInWorkingCs, outsideOriginalBackgroundColor); + original_background_dst.fill(outsideOriginalBackgroundColor); - if (!render_params.needColorSegmentation()) { - combineImageMono(maybe_normalized, bw_content, bw_mask); - } else { - QImage segmented_image; - { - QImage maybe_normalized_content(maybe_normalized); - applyMask(maybe_normalized_content, bw_mask); - segmented_image = segmentImage(bw_content, maybe_normalized_content); - maybe_normalized_content = QImage(); + drawOver(original_background_dst, contentRect, original_background, contentRectInWorkingCs); - if (dbg) { - dbg->add(segmented_image, "segmented"); - } + reserveBlackAndWhite(original_background_dst); - status.throwIfCancelled(); + original_background = original_background_dst; - if (render_params.posterize()) { - segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); + status.throwIfCancelled(); + } - if (dbg) { - dbg->add(segmented_image, "posterized"); - } + if (!render_params.needColorSegmentation()) { + combineImages(maybe_normalized, bw_content, bw_mask); + } else { + QImage segmented_image; + { + QImage maybe_normalized_content(maybe_normalized); + applyMask(maybe_normalized_content, bw_mask); + segmented_image = segmentImage(bw_content, maybe_normalized_content); + maybe_normalized_content = QImage(); - status.throwIfCancelled(); - } - } + if (dbg) { + dbg->add(segmented_image, "segmented"); + } - combineImageColor(maybe_normalized, segmented_image, bw_mask); - } + status.throwIfCancelled(); - reserveBlackAndWhite(maybe_normalized, bw_mask.inverted()); + if (render_params.posterize()) { + segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); if (dbg) { - dbg->add(maybe_normalized, "combined"); + dbg->add(segmented_image, "posterized"); } status.throwIfCancelled(); + } } - } - status.throwIfCancelled(); + combineImages(maybe_normalized, segmented_image, bw_mask); + } - assert(!target_size.isEmpty()); - QImage dst(target_size, maybe_normalized.format()); + reserveBlackAndWhite(maybe_normalized, bw_mask.inverted()); - if (maybe_normalized.format() == QImage::Format_Indexed8) { - dst.setColorTable(createGrayscalePalette()); - } + if (dbg) { + dbg->add(maybe_normalized, "combined"); + } - if (dst.isNull()) { - // Both the constructor and setColorTable() above can leave the image null. - throw std::bad_alloc(); + status.throwIfCancelled(); } + } - if (render_params.needBinarization()) { - outsideBackgroundColor = Qt::white; - } else if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { - outsideBackgroundColor = Qt::white; - if (!render_params.needBinarization()) { - reserveBlackAndWhite(maybe_normalized); - } - } - fillMarginsInPlace(maybe_normalized, contentAreaInWorkingCs, outsideBackgroundColor); - dst.fill(outsideBackgroundColor); + status.throwIfCancelled(); - drawOver(dst, contentRect, maybe_normalized, contentRectInWorkingCs); - maybe_normalized = QImage(); + assert(!target_size.isEmpty()); + QImage dst(target_size, maybe_normalized.format()); - if (render_params.mixedOutput() && render_params.needBinarization()) { - applyFillZonesToMixedInPlace(dst, fill_zones, bw_content_mask_output, !render_params.needColorSegmentation()); - } else { - applyFillZonesInPlace(dst, fill_zones); - } + if (maybe_normalized.format() == QImage::Format_Indexed8) { + dst.setColorTable(createGrayscalePalette()); + } - if (dbg) { - dbg->add(dst, "fill_zones"); + if (dst.isNull()) { + // Both the constructor and setColorTable() above can leave the image null. + throw std::bad_alloc(); + } + + if (render_params.needBinarization()) { + outsideBackgroundColor = Qt::white; + } else if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { + outsideBackgroundColor = isBlackOnWhite ? Qt::white : Qt::black; + if (!render_params.needBinarization()) { + reserveBlackAndWhite(maybe_normalized); } + } + fillMarginsInPlace(maybe_normalized, contentAreaInWorkingCs, outsideBackgroundColor); + dst.fill(outsideBackgroundColor); - status.throwIfCancelled(); + drawOver(dst, contentRect, maybe_normalized, contentRectInWorkingCs); + maybe_normalized = QImage(); - if (render_params.splitOutput()) { - const bool binary_foreground = (render_params.needBinarization() && !render_params.needColorSegmentation()); - const bool indexed_foreground = (render_params.needBinarization() && render_params.needColorSegmentation()); + if (!isBlackOnWhite) { + dst.invertPixels(); + } - splitImage->setMask(bw_content_mask_output, binary_foreground); - splitImage->setIndexedForeground(indexed_foreground); - splitImage->setBackgroundImage(dst); + if (render_params.mixedOutput() && render_params.needBinarization()) { + applyFillZonesToMixedInPlace(dst, fill_zones, bw_content_mask_output, !render_params.needColorSegmentation()); + } else { + applyFillZonesInPlace(dst, fill_zones); + } - if (render_params.needBinarization() && render_params.originalBackground()) { - BinaryImage background_mask = BinaryImage(dst, BinaryThreshold(255)).inverted(); - fillMarginsInPlace(background_mask, m_xform.resultingPreCropArea(), BLACK); - applyMask(original_background, background_mask, WHITE); - applyMask(original_background, bw_content_mask_output, BLACK); + if (dbg) { + dbg->add(dst, "fill_zones"); + } - splitImage->setOriginalBackgroundImage(original_background); - } + status.throwIfCancelled(); + + if (render_params.splitOutput()) { + const SplitImage::ForegroundType foreground_type + = render_params.needBinarization() + ? render_params.needColorSegmentation() ? SplitImage::INDEXED_FOREGROUND : SplitImage::BINARY_FOREGROUND + : SplitImage::COLOR_FOREGROUND; + + splitImage->setMask(bw_content_mask_output, foreground_type); + splitImage->setBackgroundImage(dst); + + if (render_params.needBinarization() && render_params.originalBackground()) { + if (!isBlackOnWhite) { + dst.invertPixels(); + } - return QImage(); + BinaryImage background_mask = BinaryImage(dst, BinaryThreshold(255)).inverted(); + fillMarginsInPlace(background_mask, preCropArea, BLACK); + applyMask(original_background, background_mask, isBlackOnWhite ? WHITE : BLACK); + applyMask(original_background, bw_content_mask_output, isBlackOnWhite ? BLACK : WHITE); + + if (!isBlackOnWhite) { + original_background.invertPixels(); + } + splitImage->setOriginalBackgroundImage(original_background); } - if (!render_params.mixedOutput() && render_params.posterize()) { - dst = posterizeImage(dst); + return QImage(); + } - if (dbg) { - dbg->add(dst, "posterized"); - } + if (!render_params.mixedOutput() && render_params.posterize()) { + dst = posterizeImage(dst); - status.throwIfCancelled(); + if (dbg) { + dbg->add(dst, "posterized"); } - return dst; + status.throwIfCancelled(); + } + + return dst; } // OutputGenerator::processWithoutDewarping QImage OutputGenerator::processWithDewarping(const TaskStatus& status, @@ -938,765 +999,792 @@ QImage OutputGenerator::processWithDewarping(const TaskStatus& status, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, + const PageId& pageId, + const intrusive_ptr& settings, SplitImage* splitImage) { - const RenderParams render_params(m_colorParams, m_splittingOptions); - - const QPolygonF contentArea = m_xform.resultingPreCropArea().intersected( - QRectF(render_params.cutMargins() ? m_contentRect : m_outRect)); - const QRect contentRect = contentArea.boundingRect().toRect(); - const QPolygonF outCropArea = m_xform.resultingPreCropArea().intersected(QRectF(m_outRect)); - - const QSize target_size(m_outRect.size().expandedTo(QSize(1, 1))); - // If the content area is empty or outside the cropping area, return a blank page. - if (contentRect.isEmpty()) { - QImage emptyImage(BinaryImage(target_size, WHITE).toQImage()); - if (!render_params.splitOutput()) { - return emptyImage; - } else { - splitImage->setForegroundImage(emptyImage); - splitImage->setBackgroundImage(emptyImage.convertToFormat(QImage::Format_Indexed8)); - return QImage(); - } - } + const RenderParams render_params(m_colorParams, m_splittingOptions); - // For various reasons, we need some whitespace around the content - // area. This is the number of pixels of such whitespace. - const int content_margin = m_dpi.vertical() * 20 / 300; - // The content area (in output image coordinates) extended - // with content_margin. Note that we prevent that extension - // from reaching the neighboring page. - // This is the area we are going to pass to estimateBackground(). - // estimateBackground() needs some margins around content, and - // generally smaller margins are better, except when there is - // some garbage that connects the content to the edge of the - // image area. - const QRect workingBoundingRect(m_xform.resultingPreCropArea() - .intersected(QRectF(contentRect.adjusted(-content_margin, -content_margin, - content_margin, content_margin))) - .boundingRect() - .toRect()); - const QRect contentRectInWorkingCs(contentRect.translated(-workingBoundingRect.topLeft())); - const QPolygonF contentAreaInWorkingCs(contentArea.translated(-workingBoundingRect.topLeft())); - const QPolygonF outCropAreaInWorkingCs(outCropArea.translated(-workingBoundingRect.topLeft())); - const QPolygonF preCropAreaInOriginalCs(m_xform.transformBack().map(m_xform.resultingPreCropArea())); - const QPolygonF contentAreaInOriginalCs(m_xform.transformBack().map(contentArea)); - const QPolygonF outCropAreaInOriginalCs(m_xform.transformBack().map(outCropArea)); - - const GrayImage inputGrayImage = input.grayImage(); - const QImage inputOrigImage = [&input]() { - QImage result = input.origImage(); - if (!result.allGray() && (result.format() != QImage::Format_ARGB32) - && (result.format() != QImage::Format_RGB32)) { - result = result.convertToFormat(QImage::Format_RGB32); - } - - return result; - }(); - - QColor outsideBackgroundColor = BackgroundColorCalculator::calcDominantBackgroundColor( - inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); - - const bool color_original = !inputOrigImage.allGray(); - - const bool needNormalizeIllumination - = (render_params.normalizeIllumination() && render_params.needBinarization()) - || (render_params.normalizeIlluminationColor() && !render_params.needBinarization()); - - // Original image, but: - // 1. In a format we can handle, that is grayscale, RGB32, ARGB32 - // 2. With illumination normalized over the content area, if required. - // 3. With margins filled with white, if required. - QImage normalized_original; - - // The output we would get if dewarping was turned off, except always grayscale. - // Used for automatic picture detection and binarization threshold calculation. - // This image corresponds to the area of normalize_illumination_rect above. - GrayImage warped_gray_output; - // Picture mask (white indicate a picture) in the same coordinates as - // warped_gray_output. Only built for Mixed mode. - BinaryImage warped_bw_mask; - - BinaryThreshold bw_threshold(128); - - const QTransform norm_illum_to_original( - QTransform().translate(workingBoundingRect.left(), workingBoundingRect.top()) * m_xform.transformBack()); - - if (!needNormalizeIllumination) { - if (color_original) { - normalized_original = convertToRGBorRGBA(inputOrigImage); - } else { - normalized_original = inputGrayImage; - } - warped_gray_output = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, - OutsidePixels::assumeWeakColor(outsideBackgroundColor)); + const QPolygonF preCropArea = [this, &render_params]() { + if (render_params.fillOffcut()) { + return m_xform.resultingPreCropArea(); } else { - GrayImage warped_gray_background; - warped_gray_output - = normalizeIlluminationGray(status, inputGrayImage, preCropAreaInOriginalCs, m_xform.transform(), - workingBoundingRect, &warped_gray_background, dbg); - - status.throwIfCancelled(); - - // Transform warped_gray_background to original image coordinates. - warped_gray_background = transformToGray(warped_gray_background.toQImage(), norm_illum_to_original, - inputOrigImage.rect(), OutsidePixels::assumeWeakColor(Qt::black)); - if (dbg) { - dbg->add(warped_gray_background, "orig_background"); - } - - status.throwIfCancelled(); - // Turn background into a grayscale, illumination-normalized image. - grayRasterOp(warped_gray_background, inputGrayImage); - if (dbg) { - dbg->add(warped_gray_background, "norm_illum_gray"); - } - - status.throwIfCancelled(); - - if (!color_original) { - normalized_original = warped_gray_background; - } else { - normalized_original = convertToRGBorRGBA(inputOrigImage); - adjustBrightnessGrayscale(normalized_original, warped_gray_background); - if (dbg) { - dbg->add(normalized_original, "norm_illum_color"); - } - } - - outsideBackgroundColor = BackgroundColorCalculator::calcDominantBackgroundColor(normalized_original, - outCropAreaInOriginalCs, dbg); + const QPolygonF imageRectInOutputCs = m_xform.transform().map(m_xform.origRect()); + return imageRectInOutputCs.intersected(QRectF(m_outRect)); + } + }(); + const QPolygonF contentArea + = preCropArea.intersected(QRectF(render_params.fillMargins() ? m_contentRect : m_outRect)); + const QRect contentRect = contentArea.boundingRect().toRect(); + const QPolygonF outCropArea = preCropArea.intersected(QRectF(m_outRect)); + + const QSize target_size(m_outRect.size().expandedTo(QSize(1, 1))); + // If the content area is empty or outside the cropping area, return a blank page. + if (contentRect.isEmpty()) { + QImage emptyImage(BinaryImage(target_size, WHITE).toQImage()); + if (!render_params.splitOutput()) { + return emptyImage; + } else { + splitImage->setForegroundImage(emptyImage); + splitImage->setBackgroundImage(emptyImage.convertToFormat(QImage::Format_Indexed8)); + return QImage(); + } + } + + // For various reasons, we need some whitespace around the content + // area. This is the number of pixels of such whitespace. + const int content_margin = m_dpi.vertical() * 20 / 300; + // The content area (in output image coordinates) extended + // with content_margin. Note that we prevent that extension + // from reaching the neighboring page. + // This is the area we are going to pass to estimateBackground(). + // estimateBackground() needs some margins around content, and + // generally smaller margins are better, except when there is + // some garbage that connects the content to the edge of the + // image area. + const QRect workingBoundingRect( + preCropArea + .intersected(QRectF(contentRect.adjusted(-content_margin, -content_margin, content_margin, content_margin))) + .boundingRect() + .toRect()); + const QRect contentRectInWorkingCs(contentRect.translated(-workingBoundingRect.topLeft())); + const QPolygonF contentAreaInWorkingCs(contentArea.translated(-workingBoundingRect.topLeft())); + const QPolygonF outCropAreaInWorkingCs(outCropArea.translated(-workingBoundingRect.topLeft())); + const QPolygonF preCropAreaInOriginalCs(m_xform.transformBack().map(preCropArea)); + const QPolygonF contentAreaInOriginalCs(m_xform.transformBack().map(contentArea)); + const QPolygonF outCropAreaInOriginalCs(m_xform.transformBack().map(outCropArea)); + + const bool isBlackOnWhite = updateBlackOnWhite(input, pageId, settings); + const GrayImage inputGrayImage = isBlackOnWhite ? input.grayImage() : input.grayImage().inverted(); + const QImage inputOrigImage = [&input, &isBlackOnWhite]() { + QImage result = input.origImage(); + if (!result.allGray() && (result.format() != QImage::Format_ARGB32) && (result.format() != QImage::Format_RGB32)) { + result = result.convertToFormat(QImage::Format_RGB32); + } + if (!isBlackOnWhite) { + result.invertPixels(); + } + + return result; + }(); + + const BackgroundColorCalculator backgroundColorCalculator = getBackgroundColorCalculator(pageId, settings); + QColor outsideBackgroundColor = backgroundColorCalculator.calcDominantBackgroundColor( + inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); + + const bool color_original = !inputOrigImage.allGray(); + + const bool needNormalizeIllumination + = (render_params.normalizeIllumination() && render_params.needBinarization()) + || (render_params.normalizeIlluminationColor() && !render_params.needBinarization()); + + // Original image, but: + // 1. In a format we can handle, that is grayscale, RGB32, ARGB32 + // 2. With illumination normalized over the content area, if required. + // 3. With margins filled with white, if required. + QImage normalized_original; + + // The output we would get if dewarping was turned off, except always grayscale. + // Used for automatic picture detection and binarization threshold calculation. + // This image corresponds to the area of normalize_illumination_rect above. + GrayImage warped_gray_output; + // Picture mask (white indicate a picture) in the same coordinates as + // warped_gray_output. Only built for Mixed mode. + BinaryImage warped_bw_mask; + + BinaryThreshold bw_threshold(128); + + const QTransform norm_illum_to_original(QTransform().translate(workingBoundingRect.left(), workingBoundingRect.top()) + * m_xform.transformBack()); + + if (!needNormalizeIllumination) { + if (color_original) { + normalized_original = convertToRGBorRGBA(inputOrigImage); + } else { + normalized_original = inputGrayImage; } + warped_gray_output = transformToGray(inputGrayImage, m_xform.transform(), workingBoundingRect, + OutsidePixels::assumeWeakColor(outsideBackgroundColor)); + } else { + GrayImage warped_gray_background; + warped_gray_output = normalizeIlluminationGray(status, inputGrayImage, preCropAreaInOriginalCs, m_xform.transform(), + workingBoundingRect, &warped_gray_background, dbg); status.throwIfCancelled(); - if (render_params.binaryOutput()) { - bw_threshold = calcBinarizationThreshold(warped_gray_output, contentAreaInWorkingCs); - - status.throwIfCancelled(); - } else if (render_params.mixedOutput()) { - warped_bw_mask = BinaryImage(workingBoundingRect.size(), BLACK); - - if ((m_pictureShapeOptions.getPictureShape() != RECTANGULAR_SHAPE) - || !m_outputProcessingParams.isAutoZonesFound()) { - if (m_pictureShapeOptions.getPictureShape() != OFF_SHAPE) { - warped_bw_mask = estimateBinarizationMask(status, GrayImage(warped_gray_output), workingBoundingRect, - workingBoundingRect, dbg); - if (dbg) { - dbg->add(warped_bw_mask, "warped_bw_mask"); - } - } - - removeAutoPictureZones(picture_zones); - p_settings->setPictureZones(p_pageId, picture_zones); - m_outputProcessingParams.setAutoZonesFound(false); - p_settings->setOutputProcessingParams(p_pageId, m_outputProcessingParams); - } - if ((m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE) - && !m_outputProcessingParams.isAutoZonesFound()) { - std::vector areas; - warped_bw_mask.rectangularizeAreas(areas, WHITE, m_pictureShapeOptions.getSensitivity()); - - QTransform xform1(m_xform.transform()); - xform1 *= QTransform().translate(-workingBoundingRect.x(), -workingBoundingRect.y()); - QTransform inv_xform(xform1.inverted()); - - for (auto i : areas) { - QRectF area0(i); - QPolygonF area1(area0); - QPolygonF area(inv_xform.map(area1)); - - Zone zone1(area); + // Transform warped_gray_background to original image coordinates. + warped_gray_background = transformToGray(warped_gray_background.toQImage(), norm_illum_to_original, + inputOrigImage.rect(), OutsidePixels::assumeWeakColor(Qt::black)); + if (dbg) { + dbg->add(warped_gray_background, "orig_background"); + } - picture_zones.add(zone1); - } - p_settings->setPictureZones(p_pageId, picture_zones); - m_outputProcessingParams.setAutoZonesFound(true); - p_settings->setOutputProcessingParams(p_pageId, m_outputProcessingParams); + status.throwIfCancelled(); + // Turn background into a grayscale, illumination-normalized image. + grayRasterOp(warped_gray_background, inputGrayImage); + if (dbg) { + dbg->add(warped_gray_background, "norm_illum_gray"); + } - warped_bw_mask.fill(BLACK); - } + status.throwIfCancelled(); - status.throwIfCancelled(); + if (!color_original) { + normalized_original = warped_gray_background; + } else { + normalized_original = convertToRGBorRGBA(inputOrigImage); + adjustBrightnessGrayscale(normalized_original, warped_gray_background); + if (dbg) { + dbg->add(normalized_original, "norm_illum_color"); + } + } - if (auto_picture_mask) { - if (auto_picture_mask->size() != target_size) { - BinaryImage(target_size).swap(*auto_picture_mask); - } - auto_picture_mask->fill(BLACK); + outsideBackgroundColor + = backgroundColorCalculator.calcDominantBackgroundColor(normalized_original, outCropAreaInOriginalCs, dbg); + } - rasterOp(*auto_picture_mask, contentRect, warped_bw_mask, contentRectInWorkingCs.topLeft()); - } + status.throwIfCancelled(); - status.throwIfCancelled(); + if (render_params.binaryOutput()) { + bw_threshold = calcBinarizationThreshold(warped_gray_output, contentAreaInWorkingCs); - modifyBinarizationMask(warped_bw_mask, workingBoundingRect, picture_zones); + status.throwIfCancelled(); + } else if (render_params.mixedOutput()) { + warped_bw_mask = BinaryImage(workingBoundingRect.size(), BLACK); + + if ((m_pictureShapeOptions.getPictureShape() != RECTANGULAR_SHAPE) + || !m_outputProcessingParams.isAutoZonesFound()) { + if (m_pictureShapeOptions.getPictureShape() != OFF_SHAPE) { + warped_bw_mask = estimateBinarizationMask(status, GrayImage(warped_gray_output), workingBoundingRect, + workingBoundingRect, dbg); if (dbg) { - dbg->add(warped_bw_mask, "warped_bw_mask with zones"); + dbg->add(warped_bw_mask, "warped_bw_mask"); } + } - status.throwIfCancelled(); - - // For Mixed output, we mask out pictures when calculating binarization threshold. - bw_threshold = calcBinarizationThreshold(warped_gray_output, contentAreaInWorkingCs, &warped_bw_mask); - - status.throwIfCancelled(); + removeAutoPictureZones(picture_zones); + settings->setPictureZones(pageId, picture_zones); + m_outputProcessingParams.setAutoZonesFound(false); + settings->setOutputProcessingParams(pageId, m_outputProcessingParams); } + if ((m_pictureShapeOptions.getPictureShape() == RECTANGULAR_SHAPE) + && !m_outputProcessingParams.isAutoZonesFound()) { + std::vector areas; + warped_bw_mask.rectangularizeAreas(areas, WHITE, m_pictureShapeOptions.getSensitivity()); - if (m_dewarpingOptions.dewarpingMode() == AUTO) { - DistortionModelBuilder model_builder(Vec2d(0, 1)); - - TextLineTracer::trace(warped_gray_output, m_dpi, contentRectInWorkingCs, model_builder, status, dbg); - model_builder.transform(norm_illum_to_original); + QTransform xform1(m_xform.transform()); + xform1 *= QTransform().translate(-workingBoundingRect.x(), -workingBoundingRect.y()); + QTransform inv_xform(xform1.inverted()); - TopBottomEdgeTracer::trace(inputGrayImage, model_builder.verticalBounds(), model_builder, status, dbg); + for (auto i : areas) { + QRectF area0(i); + QPolygonF area1(area0); + QPolygonF area(inv_xform.map(area1)); - distortion_model = model_builder.tryBuildModel(dbg, &inputGrayImage.toQImage()); - if (!distortion_model.isValid()) { - setupTrivialDistortionModel(distortion_model); - } + Zone zone1(area); - BinaryImage bw_image(inputGrayImage, BinaryThreshold(64)); + picture_zones.add(zone1); + } + settings->setPictureZones(pageId, picture_zones); + m_outputProcessingParams.setAutoZonesFound(true); + settings->setOutputProcessingParams(pageId, m_outputProcessingParams); - QTransform transform = m_xform.preRotation().transform(bw_image.size()); - QTransform inv_transform = transform.inverted(); + warped_bw_mask.fill(BLACK); + } - int degrees = m_xform.preRotation().toDegrees(); - bw_image = orthogonalRotation(bw_image, degrees); + status.throwIfCancelled(); - const std::vector& top_polyline0 = distortion_model.topCurve().polyline(); - const std::vector& bottom_polyline0 = distortion_model.bottomCurve().polyline(); + if (auto_picture_mask) { + if (auto_picture_mask->size() != target_size) { + BinaryImage(target_size).swap(*auto_picture_mask); + } + auto_picture_mask->fill(BLACK); - std::vector top_polyline; - std::vector bottom_polyline; + rasterOp(*auto_picture_mask, contentRect, warped_bw_mask, contentRectInWorkingCs.topLeft()); + } - for (auto i : top_polyline0) { - top_polyline.push_back(transform.map(i)); - } + status.throwIfCancelled(); - for (auto i : bottom_polyline0) { - bottom_polyline.push_back(transform.map(i)); - } + modifyBinarizationMask(warped_bw_mask, workingBoundingRect, picture_zones); + if (dbg) { + dbg->add(warped_bw_mask, "warped_bw_mask with zones"); + } + status.throwIfCancelled(); - const PageId& pageId = p_pageId; + // For Mixed output, we mask out pictures when calculating binarization threshold. + bw_threshold = calcBinarizationThreshold(warped_gray_output, contentAreaInWorkingCs, &warped_bw_mask); - QString stAngle; + status.throwIfCancelled(); + } - float max_angle = 2.75; + if (m_dewarpingOptions.dewarpingMode() == AUTO) { + DistortionModelBuilder model_builder(Vec2d(0, 1)); - if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { - float vert_skew_angle_left = vert_border_skew_angle(top_polyline.front(), bottom_polyline.front()); + TextLineTracer::trace(warped_gray_output, m_dpi, contentRectInWorkingCs, model_builder, status, dbg); + model_builder.transform(norm_illum_to_original); - stAngle.setNum(vert_skew_angle_left); + TopBottomEdgeTracer::trace(inputGrayImage, model_builder.verticalBounds(), model_builder, status, dbg); + distortion_model = model_builder.tryBuildModel(dbg, &inputGrayImage.toQImage()); + if (!distortion_model.isValid()) { + setupTrivialDistortionModel(distortion_model); + } - if (vert_skew_angle_left > max_angle) { - auto top_x = static_cast(top_polyline.front().x()); - auto bottom_x = static_cast(bottom_polyline.front().x()); + BinaryImage bw_image(inputGrayImage, BinaryThreshold(64)); - if (top_x < bottom_x) { - std::vector new_bottom_polyline; + QTransform transform = m_xform.preRotation().transform(bw_image.size()); + QTransform inv_transform = transform.inverted(); - QPointF pt(top_x, bottom_polyline.front().y()); + int degrees = m_xform.preRotation().toDegrees(); + bw_image = orthogonalRotation(bw_image, degrees); - new_bottom_polyline.push_back(pt); + const std::vector& top_polyline0 = distortion_model.topCurve().polyline(); + const std::vector& bottom_polyline0 = distortion_model.bottomCurve().polyline(); - for (auto i : bottom_polyline) { - new_bottom_polyline.push_back(inv_transform.map(i)); - } + std::vector top_polyline; + std::vector bottom_polyline; - distortion_model.setBottomCurve(dewarping::Curve(new_bottom_polyline)); - } else { - std::vector new_top_polyline; + for (auto i : top_polyline0) { + top_polyline.push_back(transform.map(i)); + } - QPointF pt(bottom_x, top_polyline.front().y()); + for (auto i : bottom_polyline0) { + bottom_polyline.push_back(transform.map(i)); + } - new_top_polyline.push_back(pt); + QString stAngle; - for (auto i : top_polyline) { - new_top_polyline.push_back(inv_transform.map(i)); - } + float max_angle = 2.75; - distortion_model.setBottomCurve(dewarping::Curve(new_top_polyline)); - } - } - } else { - float vert_skew_angle_right = vert_border_skew_angle(top_polyline.back(), bottom_polyline.back()); + if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { + float vert_skew_angle_left = vert_border_skew_angle(top_polyline.front(), bottom_polyline.front()); - stAngle.setNum(vert_skew_angle_right); + stAngle.setNum(vert_skew_angle_left); - if (vert_skew_angle_right > max_angle) { - auto top_x = static_cast(top_polyline.back().x()); - auto bottom_x = static_cast(bottom_polyline.back().x()); + if (vert_skew_angle_left > max_angle) { + auto top_x = static_cast(top_polyline.front().x()); + auto bottom_x = static_cast(bottom_polyline.front().x()); - if (top_x > bottom_x) { - std::vector new_bottom_polyline; + if (top_x < bottom_x) { + std::vector new_bottom_polyline; - QPointF pt(top_x, bottom_polyline.back().y()); + QPointF pt(top_x, bottom_polyline.front().y()); - for (auto i : bottom_polyline) { - new_bottom_polyline.push_back(inv_transform.map(i)); - } + new_bottom_polyline.push_back(pt); - new_bottom_polyline.push_back(pt); + for (auto i : bottom_polyline) { + new_bottom_polyline.push_back(inv_transform.map(i)); + } - distortion_model.setBottomCurve(dewarping::Curve(new_bottom_polyline)); - } else { - std::vector new_top_polyline; + distortion_model.setBottomCurve(dewarping::Curve(new_bottom_polyline)); + } else { + std::vector new_top_polyline; - QPointF pt(bottom_x, top_polyline.back().y()); + QPointF pt(bottom_x, top_polyline.front().y()); - for (auto i : top_polyline) { - new_top_polyline.push_back(inv_transform.map(i)); - } + new_top_polyline.push_back(pt); - new_top_polyline.push_back(pt); + for (auto i : top_polyline) { + new_top_polyline.push_back(inv_transform.map(i)); + } - distortion_model.setBottomCurve(dewarping::Curve(new_top_polyline)); - } - } + distortion_model.setBottomCurve(dewarping::Curve(new_top_polyline)); } - } else if (m_dewarpingOptions.dewarpingMode() == MARGINAL) { - BinaryImage bw_image(inputGrayImage, BinaryThreshold(64)); - - QTransform transform = m_xform.preRotation().transform(bw_image.size()); - QTransform inv_transform = transform.inverted(); + } + } else { + float vert_skew_angle_right = vert_border_skew_angle(top_polyline.back(), bottom_polyline.back()); - int degrees = m_xform.preRotation().toDegrees(); - bw_image = orthogonalRotation(bw_image, degrees); + stAngle.setNum(vert_skew_angle_right); - setupTrivialDistortionModel(distortion_model); - const PageId& pageId = p_pageId; + if (vert_skew_angle_right > max_angle) { + auto top_x = static_cast(top_polyline.back().x()); + auto bottom_x = static_cast(bottom_polyline.back().x()); - int max_red_points = 5; - XSpline top_spline; + if (top_x > bottom_x) { + std::vector new_bottom_polyline; - const std::vector& top_polyline = distortion_model.topCurve().polyline(); + QPointF pt(top_x, bottom_polyline.back().y()); - const QLineF top_line(transform.map(top_polyline.front()), transform.map(top_polyline.back())); + for (auto i : bottom_polyline) { + new_bottom_polyline.push_back(inv_transform.map(i)); + } - top_spline.appendControlPoint(top_line.p1(), 0); + new_bottom_polyline.push_back(pt); - if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { - for (int i = 29 - max_red_points; i < 29; i++) { - top_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); - } + distortion_model.setBottomCurve(dewarping::Curve(new_bottom_polyline)); } else { - for (int i = 1; i <= max_red_points; i++) { - top_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); - } - } - - top_spline.appendControlPoint(top_line.p2(), 0); + std::vector new_top_polyline; - for (int i = 0; i <= top_spline.numSegments(); i++) { - movePointToTopMargin(bw_image, top_spline, i); - } + QPointF pt(bottom_x, top_polyline.back().y()); - for (int i = 0; i <= top_spline.numSegments(); i++) { - top_spline.moveControlPoint(i, inv_transform.map(top_spline.controlPointPosition(i))); - } + for (auto i : top_polyline) { + new_top_polyline.push_back(inv_transform.map(i)); + } - distortion_model.setTopCurve(dewarping::Curve(top_spline)); + new_top_polyline.push_back(pt); + distortion_model.setBottomCurve(dewarping::Curve(new_top_polyline)); + } + } + } + } else if (m_dewarpingOptions.dewarpingMode() == MARGINAL) { + BinaryImage bw_image(inputGrayImage, BinaryThreshold(64)); - XSpline bottom_spline; + QTransform transform = m_xform.preRotation().transform(bw_image.size()); + QTransform inv_transform = transform.inverted(); - const std::vector& bottom_polyline = distortion_model.bottomCurve().polyline(); + int degrees = m_xform.preRotation().toDegrees(); + bw_image = orthogonalRotation(bw_image, degrees); - const QLineF bottom_line(transform.map(bottom_polyline.front()), transform.map(bottom_polyline.back())); + setupTrivialDistortionModel(distortion_model); - bottom_spline.appendControlPoint(bottom_line.p1(), 0); + int max_red_points = 5; + XSpline top_spline; - if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { - for (int i = 29 - max_red_points; i < 29; i++) { - bottom_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); - } - } else { - for (int i = 1; i <= max_red_points; i++) { - bottom_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); - } - } + const std::vector& top_polyline = distortion_model.topCurve().polyline(); - bottom_spline.appendControlPoint(bottom_line.p2(), 0); + const QLineF top_line(transform.map(top_polyline.front()), transform.map(top_polyline.back())); - for (int i = 0; i <= bottom_spline.numSegments(); i++) { - movePointToBottomMargin(bw_image, bottom_spline, i); - } - - for (int i = 0; i <= bottom_spline.numSegments(); i++) { - bottom_spline.moveControlPoint(i, inv_transform.map(bottom_spline.controlPointPosition(i))); - } + top_spline.appendControlPoint(top_line.p1(), 0); - distortion_model.setBottomCurve(dewarping::Curve(bottom_spline)); - - if (!distortion_model.isValid()) { - setupTrivialDistortionModel(distortion_model); - } - - if (dbg) { - QImage out_image(bw_image.toQImage().convertToFormat(QImage::Format_RGB32)); - for (int i = 0; i <= top_spline.numSegments(); i++) { - drawPoint(out_image, top_spline.controlPointPosition(i)); - } - for (int i = 0; i <= bottom_spline.numSegments(); i++) { - drawPoint(out_image, bottom_spline.controlPointPosition(i)); - } - dbg->add(out_image, "marginal dewarping"); - } + if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { + for (int i = 29 - max_red_points; i < 29; i++) { + top_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); + } + } else { + for (int i = 1; i <= max_red_points; i++) { + top_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); + } } - warped_gray_output = GrayImage(); // Save memory. - status.throwIfCancelled(); + top_spline.appendControlPoint(top_line.p2(), 0); - QImage dewarped; - try { - dewarped = dewarp(QTransform(), normalized_original, m_xform.transform(), distortion_model, depth_perception, - outsideBackgroundColor); - } catch (const std::runtime_error&) { - // Probably an impossible distortion model. Let's fall back to a trivial one. - setupTrivialDistortionModel(distortion_model); - dewarped = dewarp(QTransform(), normalized_original, m_xform.transform(), distortion_model, depth_perception, - outsideBackgroundColor); + for (int i = 0; i <= top_spline.numSegments(); i++) { + movePointToTopMargin(bw_image, top_spline, i); } - normalized_original = QImage(); // Save memory. - if (dbg) { - dbg->add(dewarped, "dewarped"); + for (int i = 0; i <= top_spline.numSegments(); i++) { + top_spline.moveControlPoint(i, inv_transform.map(top_spline.controlPointPosition(i))); } - status.throwIfCancelled(); + distortion_model.setTopCurve(dewarping::Curve(top_spline)); - std::shared_ptr mapper( - new DewarpingPointMapper(distortion_model, depth_perception.value(), m_xform.transform(), contentRect)); - const boost::function orig_to_output( - boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1)); - const double deskew_angle = maybe_deskew(&dewarped, m_dewarpingOptions, outsideBackgroundColor); + XSpline bottom_spline; - { - QTransform post_rotate; + const std::vector& bottom_polyline = distortion_model.bottomCurve().polyline(); - QPointF center(m_outRect.width() / 2, m_outRect.height() / 2); + const QLineF bottom_line(transform.map(bottom_polyline.front()), transform.map(bottom_polyline.back())); - post_rotate.translate(center.x(), center.y()); - post_rotate.rotate(-deskew_angle); - post_rotate.translate(-center.x(), -center.y()); + bottom_spline.appendControlPoint(bottom_line.p1(), 0); - postTransform = post_rotate; + if ((pageId.subPage() == PageId::SINGLE_PAGE) || (pageId.subPage() == PageId::LEFT_PAGE)) { + for (int i = 29 - max_red_points; i < 29; i++) { + bottom_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); + } + } else { + for (int i = 1; i <= max_red_points; i++) { + bottom_spline.appendControlPoint(top_line.pointAt((float) i / 29.0), 1); + } } - BinaryImage dewarping_content_area_mask(inputGrayImage.size(), BLACK); - fillMarginsInPlace(dewarping_content_area_mask, contentAreaInOriginalCs, WHITE); - QImage dewarping_content_area_mask_dewarped(dewarp(QTransform(), dewarping_content_area_mask.toQImage(), - m_xform.transform(), distortion_model, depth_perception, - Qt::white)); - deskew(&dewarping_content_area_mask_dewarped, deskew_angle, Qt::white); - dewarping_content_area_mask = BinaryImage(dewarping_content_area_mask_dewarped); - dewarping_content_area_mask_dewarped = QImage(); + bottom_spline.appendControlPoint(bottom_line.p2(), 0); - if (render_params.binaryOutput()) { - QImage dewarped_and_maybe_smoothed; - // We only do smoothing if we are going to do binarization later. - if (!render_params.needSavitzkyGolaySmoothing()) { - dewarped_and_maybe_smoothed = dewarped; - } else { - dewarped_and_maybe_smoothed = smoothToGrayscale(dewarped, m_dpi); - if (dbg) { - dbg->add(dewarped_and_maybe_smoothed, "smoothed"); - } - } + for (int i = 0; i <= bottom_spline.numSegments(); i++) { + movePointToBottomMargin(bw_image, bottom_spline, i); + } - // don't destroy as it's needed for color segmentation - if (!render_params.needColorSegmentation()) { - dewarped = QImage(); - } + for (int i = 0; i <= bottom_spline.numSegments(); i++) { + bottom_spline.moveControlPoint(i, inv_transform.map(bottom_spline.controlPointPosition(i))); + } - status.throwIfCancelled(); + distortion_model.setBottomCurve(dewarping::Curve(bottom_spline)); - BinaryImage dewarped_bw_content(binarize(dewarped_and_maybe_smoothed, dewarping_content_area_mask)); + if (!distortion_model.isValid()) { + setupTrivialDistortionModel(distortion_model); + } - status.throwIfCancelled(); + if (dbg) { + QImage out_image(bw_image.toQImage().convertToFormat(QImage::Format_RGB32)); + for (int i = 0; i <= top_spline.numSegments(); i++) { + drawPoint(out_image, top_spline.controlPointPosition(i)); + } + for (int i = 0; i <= bottom_spline.numSegments(); i++) { + drawPoint(out_image, bottom_spline.controlPointPosition(i)); + } + dbg->add(out_image, "marginal dewarping"); + } + } + warped_gray_output = GrayImage(); // Save memory. + + status.throwIfCancelled(); + + QImage dewarped; + try { + dewarped = dewarp(QTransform(), normalized_original, m_xform.transform(), distortion_model, depth_perception, + outsideBackgroundColor); + } catch (const std::runtime_error&) { + // Probably an impossible distortion model. Let's fall back to a trivial one. + setupTrivialDistortionModel(distortion_model); + dewarped = dewarp(QTransform(), normalized_original, m_xform.transform(), distortion_model, depth_perception, + outsideBackgroundColor); + } + + normalized_original = QImage(); // Save memory. + if (dbg) { + dbg->add(dewarped, "dewarped"); + } + + status.throwIfCancelled(); + + std::shared_ptr mapper( + new DewarpingPointMapper(distortion_model, depth_perception.value(), m_xform.transform(), contentRect)); + const boost::function orig_to_output( + boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1)); + + const double deskew_angle = maybe_deskew(&dewarped, m_dewarpingOptions, outsideBackgroundColor); + + { + QTransform post_rotate; + + QPointF center(m_outRect.width() / 2.0, m_outRect.height() / 2.0); + + post_rotate.translate(center.x(), center.y()); + post_rotate.rotate(-deskew_angle); + post_rotate.translate(-center.x(), -center.y()); + + m_postTransform = post_rotate; + } + + BinaryImage dewarping_content_area_mask(inputGrayImage.size(), BLACK); + fillMarginsInPlace(dewarping_content_area_mask, contentAreaInOriginalCs, WHITE); + QImage dewarping_content_area_mask_dewarped(dewarp(QTransform(), dewarping_content_area_mask.toQImage(), + m_xform.transform(), distortion_model, depth_perception, + Qt::white)); + deskew(&dewarping_content_area_mask_dewarped, deskew_angle, Qt::white); + dewarping_content_area_mask = BinaryImage(dewarping_content_area_mask_dewarped); + dewarping_content_area_mask_dewarped = QImage(); + + if (render_params.binaryOutput()) { + QImage dewarped_and_maybe_smoothed; + // We only do smoothing if we are going to do binarization later. + if (!render_params.needSavitzkyGolaySmoothing()) { + dewarped_and_maybe_smoothed = dewarped; + } else { + dewarped_and_maybe_smoothed = smoothToGrayscale(dewarped, m_dpi); + if (dbg) { + dbg->add(dewarped_and_maybe_smoothed, "smoothed"); + } + } - dewarped_and_maybe_smoothed = QImage(); // Save memory. - if (dbg) { - dbg->add(dewarped_bw_content, "dewarped_bw_content"); - } + // don't destroy as it's needed for color segmentation + if (!render_params.needColorSegmentation()) { + dewarped = QImage(); + } - if (render_params.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(dewarped_bw_content, status); - if (dbg) { - dbg->add(dewarped_bw_content, "edges_smoothed"); - } - } + status.throwIfCancelled(); - status.throwIfCancelled(); + BinaryImage dewarped_bw_content(binarize(dewarped_and_maybe_smoothed, dewarping_content_area_mask)); - // It's important to keep despeckling the very last operation - // affecting the binary part of the output. That's because - // we will be reconstructing the input to this despeckling - // operation from the final output file. - maybeDespeckleInPlace(dewarped_bw_content, m_outRect, m_outRect, m_despeckleLevel, speckles_image, m_dpi, - status, dbg); + status.throwIfCancelled(); - if (!render_params.needColorSegmentation()) { - applyFillZonesInPlace(dewarped_bw_content, fill_zones, orig_to_output, postTransform); + dewarped_and_maybe_smoothed = QImage(); // Save memory. + if (dbg) { + dbg->add(dewarped_bw_content, "dewarped_bw_content"); + } - return dewarped_bw_content.toQImage(); - } else { - QImage segmented_image = segmentImage(dewarped_bw_content, dewarped); - dewarped = QImage(); - dewarped_bw_content.release(); + if (render_params.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(dewarped_bw_content, status); + if (dbg) { + dbg->add(dewarped_bw_content, "edges_smoothed"); + } + } - if (dbg) { - dbg->add(segmented_image, "segmented"); - } + status.throwIfCancelled(); - status.throwIfCancelled(); + // It's important to keep despeckling the very last operation + // affecting the binary part of the output. That's because + // we will be reconstructing the input to this despeckling + // operation from the final output file. + maybeDespeckleInPlace(dewarped_bw_content, m_outRect, m_outRect, m_despeckleLevel, speckles_image, m_dpi, status, + dbg); - applyFillZonesInPlace(segmented_image, fill_zones, orig_to_output, postTransform, false); + if (!render_params.needColorSegmentation()) { + if (!isBlackOnWhite) { + dewarped_bw_content.invert(); + } - if (dbg) { - dbg->add(segmented_image, "segmented_with_fill_zones"); - } + applyFillZonesInPlace(dewarped_bw_content, fill_zones, orig_to_output, m_postTransform); - status.throwIfCancelled(); + return dewarped_bw_content.toQImage(); + } else { + QImage segmented_image = segmentImage(dewarped_bw_content, dewarped); + dewarped = QImage(); + dewarped_bw_content.release(); - if (render_params.posterize()) { - segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); + if (dbg) { + dbg->add(segmented_image, "segmented"); + } - if (dbg) { - dbg->add(segmented_image, "posterized"); - } + status.throwIfCancelled(); - status.throwIfCancelled(); - } + if (!isBlackOnWhite) { + segmented_image.invertPixels(); + } - return segmented_image; - } - } + applyFillZonesInPlace(segmented_image, fill_zones, orig_to_output, m_postTransform, false); - BinaryImage dewarped_bw_content_mask; - QImage original_background; - if (render_params.mixedOutput()) { - const QTransform orig_to_working_cs( - m_xform.transform() * QTransform().translate(-workingBoundingRect.left(), -workingBoundingRect.top())); - QTransform working_to_output_cs; - working_to_output_cs.translate(workingBoundingRect.left(), workingBoundingRect.top()); - BinaryImage dewarped_bw_mask(dewarp(orig_to_working_cs, warped_bw_mask.toQImage(), working_to_output_cs, - distortion_model, depth_perception, Qt::black)); - warped_bw_mask.release(); + if (dbg) { + dbg->add(segmented_image, "segmented_with_fill_zones"); + } - { - QImage dewarped_bw_mask_deskewed(dewarped_bw_mask.toQImage()); - deskew(&dewarped_bw_mask_deskewed, deskew_angle, Qt::black); - dewarped_bw_mask = BinaryImage(dewarped_bw_mask_deskewed); - } + status.throwIfCancelled(); - fillMarginsInPlace(dewarped_bw_mask, dewarping_content_area_mask, BLACK); + if (render_params.posterize()) { + segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); if (dbg) { - dbg->add(dewarped_bw_mask, "dewarped_bw_mask"); + dbg->add(segmented_image, "posterized"); } status.throwIfCancelled(); + } - dewarped_bw_content_mask = dewarped_bw_mask; - - if (render_params.needBinarization()) { - QImage dewarped_and_maybe_smoothed; - if (!render_params.needSavitzkyGolaySmoothing()) { - dewarped_and_maybe_smoothed = dewarped; - } else { - dewarped_and_maybe_smoothed = smoothToGrayscale(dewarped, m_dpi); - if (dbg) { - dbg->add(dewarped_and_maybe_smoothed, "smoothed"); - } - } + return segmented_image; + } + } - status.throwIfCancelled(); + BinaryImage dewarped_bw_content_mask; + QImage original_background; + if (render_params.mixedOutput()) { + const QTransform orig_to_working_cs( + m_xform.transform() * QTransform().translate(-workingBoundingRect.left(), -workingBoundingRect.top())); + QTransform working_to_output_cs; + working_to_output_cs.translate(workingBoundingRect.left(), workingBoundingRect.top()); + BinaryImage dewarped_bw_mask(dewarp(orig_to_working_cs, warped_bw_mask.toQImage(), working_to_output_cs, + distortion_model, depth_perception, Qt::black)); + warped_bw_mask.release(); + + { + QImage dewarped_bw_mask_deskewed(dewarped_bw_mask.toQImage()); + deskew(&dewarped_bw_mask_deskewed, deskew_angle, Qt::black); + dewarped_bw_mask = BinaryImage(dewarped_bw_mask_deskewed); + } - BinaryImage dewarped_bw_mask_filled(dewarped_bw_mask); - fillMarginsInPlace(dewarped_bw_mask_filled, dewarping_content_area_mask, WHITE); + fillMarginsInPlace(dewarped_bw_mask, dewarping_content_area_mask, BLACK); - BinaryImage dewarped_bw_content(binarize(dewarped_and_maybe_smoothed, dewarped_bw_mask_filled)); + if (dbg) { + dbg->add(dewarped_bw_mask, "dewarped_bw_mask"); + } - dewarped_bw_mask_filled.release(); - dewarped_and_maybe_smoothed = QImage(); // Save memory. + status.throwIfCancelled(); - if (dbg) { - dbg->add(dewarped_bw_content, "dewarped_bw_content"); - } + dewarped_bw_content_mask = dewarped_bw_mask; - status.throwIfCancelled(); + if (render_params.needBinarization()) { + QImage dewarped_and_maybe_smoothed; + if (!render_params.needSavitzkyGolaySmoothing()) { + dewarped_and_maybe_smoothed = dewarped; + } else { + dewarped_and_maybe_smoothed = smoothToGrayscale(dewarped, m_dpi); + if (dbg) { + dbg->add(dewarped_and_maybe_smoothed, "smoothed"); + } + } - if (render_params.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(dewarped_bw_content, status); - if (dbg) { - dbg->add(dewarped_bw_content, "edges_smoothed"); - } - } + status.throwIfCancelled(); - status.throwIfCancelled(); + BinaryImage dewarped_bw_mask_filled(dewarped_bw_mask); + fillMarginsInPlace(dewarped_bw_mask_filled, dewarping_content_area_mask, WHITE); - if (render_params.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(dewarped_bw_content, status); - if (dbg) { - dbg->add(dewarped_bw_content, "edges_smoothed"); - } - } + BinaryImage dewarped_bw_content(binarize(dewarped_and_maybe_smoothed, dewarped_bw_mask_filled)); - status.throwIfCancelled(); + dewarped_bw_mask_filled.release(); + dewarped_and_maybe_smoothed = QImage(); // Save memory. - // It's important to keep despeckling the very last operation - // affecting the binary part of the output. That's because - // we will be reconstructing the input to this despeckling - // operation from the final output file. - maybeDespeckleInPlace(dewarped_bw_content, m_outRect, contentRect, m_despeckleLevel, speckles_image, m_dpi, - status, dbg); + if (dbg) { + dbg->add(dewarped_bw_content, "dewarped_bw_content"); + } - status.throwIfCancelled(); + status.throwIfCancelled(); + + if (render_params.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(dewarped_bw_content, status); + if (dbg) { + dbg->add(dewarped_bw_content, "edges_smoothed"); + } + } - if (needNormalizeIllumination && !render_params.normalizeIlluminationColor()) { - outsideBackgroundColor = BackgroundColorCalculator::calcDominantBackgroundColor( - inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); + status.throwIfCancelled(); - QImage orig_without_illumination; - if (color_original) { - orig_without_illumination = convertToRGBorRGBA(inputOrigImage); - } else { - orig_without_illumination = inputGrayImage; - } + if (render_params.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(dewarped_bw_content, status); + if (dbg) { + dbg->add(dewarped_bw_content, "edges_smoothed"); + } + } - status.throwIfCancelled(); + status.throwIfCancelled(); - try { - dewarped = dewarp(QTransform(), orig_without_illumination, m_xform.transform(), distortion_model, - depth_perception, outsideBackgroundColor); - } catch (const std::runtime_error&) { - setupTrivialDistortionModel(distortion_model); - dewarped = dewarp(QTransform(), orig_without_illumination, m_xform.transform(), distortion_model, - depth_perception, outsideBackgroundColor); - } - orig_without_illumination = QImage(); + // It's important to keep despeckling the very last operation + // affecting the binary part of the output. That's because + // we will be reconstructing the input to this despeckling + // operation from the final output file. + maybeDespeckleInPlace(dewarped_bw_content, m_outRect, contentRect, m_despeckleLevel, speckles_image, m_dpi, + status, dbg); - deskew(&dewarped, deskew_angle, outsideBackgroundColor); + status.throwIfCancelled(); - status.throwIfCancelled(); - } + if (needNormalizeIllumination && !render_params.normalizeIlluminationColor()) { + outsideBackgroundColor = backgroundColorCalculator.calcDominantBackgroundColor( + inputOrigImage.allGray() ? inputGrayImage : inputOrigImage, outCropAreaInOriginalCs, dbg); - if (render_params.originalBackground()) { - original_background = dewarped; + QImage orig_without_illumination; + if (color_original) { + orig_without_illumination = convertToRGBorRGBA(inputOrigImage); + } else { + orig_without_illumination = inputGrayImage; + } - QColor outsideOriginalBackgroundColor = outsideBackgroundColor; - if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { - outsideOriginalBackgroundColor = Qt::white; - } - fillMarginsInPlace(original_background, dewarping_content_area_mask, outsideOriginalBackgroundColor); + status.throwIfCancelled(); - reserveBlackAndWhite(original_background); + try { + dewarped = dewarp(QTransform(), orig_without_illumination, m_xform.transform(), distortion_model, + depth_perception, outsideBackgroundColor); + } catch (const std::runtime_error&) { + setupTrivialDistortionModel(distortion_model); + dewarped = dewarp(QTransform(), orig_without_illumination, m_xform.transform(), distortion_model, + depth_perception, outsideBackgroundColor); + } + orig_without_illumination = QImage(); - status.throwIfCancelled(); - } + deskew(&dewarped, deskew_angle, outsideBackgroundColor); - if (!render_params.needColorSegmentation()) { - combineImageMono(dewarped, dewarped_bw_content, dewarped_bw_mask); - } else { - QImage segmented_image; - { - QImage dewarped_content(dewarped); - applyMask(dewarped_content, dewarped_bw_mask); - segmented_image = segmentImage(dewarped_bw_content, dewarped_content); - dewarped_content = QImage(); + status.throwIfCancelled(); + } - if (dbg) { - dbg->add(segmented_image, "segmented"); - } + if (render_params.originalBackground()) { + original_background = dewarped; - status.throwIfCancelled(); + QColor outsideOriginalBackgroundColor = outsideBackgroundColor; + if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { + outsideOriginalBackgroundColor = isBlackOnWhite ? Qt::white : Qt::black; + } + fillMarginsInPlace(original_background, dewarping_content_area_mask, outsideOriginalBackgroundColor); - if (render_params.posterize()) { - segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); + reserveBlackAndWhite(original_background); - if (dbg) { - dbg->add(segmented_image, "posterized"); - } + status.throwIfCancelled(); + } - status.throwIfCancelled(); - } - } + if (!render_params.needColorSegmentation()) { + combineImages(dewarped, dewarped_bw_content, dewarped_bw_mask); + } else { + QImage segmented_image; + { + QImage dewarped_content(dewarped); + applyMask(dewarped_content, dewarped_bw_mask); + segmented_image = segmentImage(dewarped_bw_content, dewarped_content); + dewarped_content = QImage(); - combineImageColor(dewarped, segmented_image, dewarped_bw_mask); - } + if (dbg) { + dbg->add(segmented_image, "segmented"); + } - reserveBlackAndWhite(dewarped, dewarped_bw_mask.inverted()); + status.throwIfCancelled(); + + if (render_params.posterize()) { + segmented_image = posterizeImage(segmented_image, outsideBackgroundColor); if (dbg) { - dbg->add(dewarped, "combined_image"); + dbg->add(segmented_image, "posterized"); } status.throwIfCancelled(); + } } - } - if (render_params.needBinarization()) { - outsideBackgroundColor = Qt::white; - } else if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { - outsideBackgroundColor = Qt::white; - if (!render_params.needBinarization()) { - reserveBlackAndWhite(dewarped); - } - } - fillMarginsInPlace(dewarped, dewarping_content_area_mask, outsideBackgroundColor); + combineImages(dewarped, segmented_image, dewarped_bw_mask); + } - if (render_params.mixedOutput() && render_params.needBinarization()) { - applyFillZonesToMixedInPlace(dewarped, fill_zones, orig_to_output, postTransform, dewarped_bw_content_mask, - !render_params.needColorSegmentation()); - } else { - applyFillZonesInPlace(dewarped, fill_zones, orig_to_output, postTransform); + reserveBlackAndWhite(dewarped, dewarped_bw_mask.inverted()); + + if (dbg) { + dbg->add(dewarped, "combined_image"); + } + + status.throwIfCancelled(); } + } - if (dbg) { - dbg->add(dewarped, "fill_zones"); + if (render_params.needBinarization()) { + outsideBackgroundColor = Qt::white; + } else if (m_colorParams.colorCommonOptions().getFillingColor() == FILL_WHITE) { + outsideBackgroundColor = isBlackOnWhite ? Qt::white : Qt::black; + if (!render_params.needBinarization()) { + reserveBlackAndWhite(dewarped); } + } + fillMarginsInPlace(dewarped, dewarping_content_area_mask, outsideBackgroundColor); - status.throwIfCancelled(); + if (!isBlackOnWhite) { + dewarped.invertPixels(); + } - if (render_params.splitOutput()) { - const bool binary_foreground = (render_params.needBinarization() && !render_params.needColorSegmentation()); - const bool indexed_foreground = (render_params.needBinarization() && render_params.needColorSegmentation()); + if (render_params.mixedOutput() && render_params.needBinarization()) { + applyFillZonesToMixedInPlace(dewarped, fill_zones, orig_to_output, m_postTransform, dewarped_bw_content_mask, + !render_params.needColorSegmentation()); + } else { + applyFillZonesInPlace(dewarped, fill_zones, orig_to_output, m_postTransform); + } - splitImage->setMask(dewarped_bw_content_mask, binary_foreground); - splitImage->setIndexedForeground(indexed_foreground); - splitImage->setBackgroundImage(dewarped); + if (dbg) { + dbg->add(dewarped, "fill_zones"); + } - if (render_params.needBinarization() && render_params.originalBackground()) { - BinaryImage background_mask = BinaryImage(dewarped, BinaryThreshold(255)).inverted(); - fillMarginsInPlace(background_mask, dewarping_content_area_mask, BLACK); - applyMask(original_background, background_mask, WHITE); - applyMask(original_background, dewarped_bw_content_mask, BLACK); + status.throwIfCancelled(); - splitImage->setOriginalBackgroundImage(original_background); - } + if (render_params.splitOutput()) { + const SplitImage::ForegroundType foreground_type + = render_params.needBinarization() + ? render_params.needColorSegmentation() ? SplitImage::INDEXED_FOREGROUND : SplitImage::BINARY_FOREGROUND + : SplitImage::COLOR_FOREGROUND; + + splitImage->setMask(dewarped_bw_content_mask, foreground_type); + splitImage->setBackgroundImage(dewarped); - return QImage(); + if (render_params.needBinarization() && render_params.originalBackground()) { + if (!isBlackOnWhite) { + dewarped.invertPixels(); + } + + BinaryImage background_mask = BinaryImage(dewarped, BinaryThreshold(255)).inverted(); + fillMarginsInPlace(background_mask, dewarping_content_area_mask, BLACK); + applyMask(original_background, background_mask, isBlackOnWhite ? WHITE : BLACK); + + applyMask(original_background, dewarped_bw_content_mask, isBlackOnWhite ? BLACK : WHITE); + + if (!isBlackOnWhite) { + original_background.invertPixels(); + } + splitImage->setOriginalBackgroundImage(original_background); } - if (!render_params.mixedOutput() && render_params.posterize()) { - dewarped = posterizeImage(dewarped); + return QImage(); + } - if (dbg) { - dbg->add(dewarped, "posterized"); - } + if (!render_params.mixedOutput() && render_params.posterize()) { + dewarped = posterizeImage(dewarped); - status.throwIfCancelled(); + if (dbg) { + dbg->add(dewarped, "posterized"); } - return dewarped; + status.throwIfCancelled(); + } + + return dewarped; } // OutputGenerator::processWithDewarping /** @@ -1704,44 +1792,44 @@ QImage OutputGenerator::processWithDewarping(const TaskStatus& status, * which will result in no distortion correction. */ void OutputGenerator::setupTrivialDistortionModel(DistortionModel& distortion_model) const { - QPolygonF poly; - if (!m_contentRect.isEmpty()) { - poly = QRectF(m_contentRect); - } else { - poly << m_contentRect.topLeft() + QPointF(-0.5, -0.5); - poly << m_contentRect.topLeft() + QPointF(0.5, -0.5); - poly << m_contentRect.topLeft() + QPointF(0.5, 0.5); - poly << m_contentRect.topLeft() + QPointF(-0.5, 0.5); - } - poly = m_xform.transformBack().map(poly); - - std::vector top_polyline, bottom_polyline; - top_polyline.push_back(poly[0]); // top-left - top_polyline.push_back(poly[1]); // top-right - bottom_polyline.push_back(poly[3]); // bottom-left - bottom_polyline.push_back(poly[2]); // bottom-right - distortion_model.setTopCurve(Curve(top_polyline)); - distortion_model.setBottomCurve(Curve(bottom_polyline)); + QPolygonF poly; + if (!m_contentRect.isEmpty()) { + poly = QRectF(m_contentRect); + } else { + poly << m_contentRect.topLeft() + QPointF(-0.5, -0.5); + poly << m_contentRect.topLeft() + QPointF(0.5, -0.5); + poly << m_contentRect.topLeft() + QPointF(0.5, 0.5); + poly << m_contentRect.topLeft() + QPointF(-0.5, 0.5); + } + poly = m_xform.transformBack().map(poly); + + std::vector top_polyline, bottom_polyline; + top_polyline.push_back(poly[0]); // top-left + top_polyline.push_back(poly[1]); // top-right + bottom_polyline.push_back(poly[3]); // bottom-left + bottom_polyline.push_back(poly[2]); // bottom-right + distortion_model.setTopCurve(Curve(top_polyline)); + distortion_model.setBottomCurve(Curve(bottom_polyline)); } CylindricalSurfaceDewarper OutputGenerator::createDewarper(const DistortionModel& distortion_model, const QTransform& distortion_model_to_target, double depth_perception) { - if (distortion_model_to_target.isIdentity()) { - return CylindricalSurfaceDewarper(distortion_model.topCurve().polyline(), - distortion_model.bottomCurve().polyline(), depth_perception); - } - - std::vector top_polyline(distortion_model.topCurve().polyline()); - std::vector bottom_polyline(distortion_model.bottomCurve().polyline()); - for (QPointF& pt : top_polyline) { - pt = distortion_model_to_target.map(pt); - } - for (QPointF& pt : bottom_polyline) { - pt = distortion_model_to_target.map(pt); - } - - return CylindricalSurfaceDewarper(top_polyline, bottom_polyline, depth_perception); + if (distortion_model_to_target.isIdentity()) { + return CylindricalSurfaceDewarper(distortion_model.topCurve().polyline(), distortion_model.bottomCurve().polyline(), + depth_perception); + } + + std::vector top_polyline(distortion_model.topCurve().polyline()); + std::vector bottom_polyline(distortion_model.bottomCurve().polyline()); + for (QPointF& pt : top_polyline) { + pt = distortion_model_to_target.map(pt); + } + for (QPointF& pt : bottom_polyline) { + pt = distortion_model_to_target.map(pt); + } + + return CylindricalSurfaceDewarper(top_polyline, bottom_polyline, depth_perception); } /** @@ -1761,316 +1849,316 @@ QImage OutputGenerator::dewarp(const QTransform& orig_to_src, const DistortionModel& distortion_model, const DepthPerception& depth_perception, const QColor& bg_color) const { - const CylindricalSurfaceDewarper dewarper(createDewarper(distortion_model, orig_to_src, depth_perception.value())); + const CylindricalSurfaceDewarper dewarper(createDewarper(distortion_model, orig_to_src, depth_perception.value())); - // Model domain is a rectangle in output image coordinates that - // will be mapped to our curved quadrilateral. - const QRect model_domain( - distortion_model.modelDomain(dewarper, orig_to_src * src_to_output, outputContentRect()).toRect()); - if (model_domain.isEmpty()) { - GrayImage out(src.size()); - out.fill(0xff); // white + // Model domain is a rectangle in output image coordinates that + // will be mapped to our curved quadrilateral. + const QRect model_domain( + distortion_model.modelDomain(dewarper, orig_to_src * src_to_output, outputContentRect()).toRect()); + if (model_domain.isEmpty()) { + GrayImage out(src.size()); + out.fill(0xff); // white - return out; - } + return out; + } - return RasterDewarper::dewarp(src, m_outRect.size(), dewarper, model_domain, bg_color); + return RasterDewarper::dewarp(src, m_outRect.size(), dewarper, model_domain, bg_color); } QSize OutputGenerator::from300dpi(const QSize& size, const Dpi& target_dpi) { - const double hscale = target_dpi.horizontal() / 300.0; - const double vscale = target_dpi.vertical() / 300.0; - const int width = qRound(size.width() * hscale); - const int height = qRound(size.height() * vscale); + const double hscale = target_dpi.horizontal() / 300.0; + const double vscale = target_dpi.vertical() / 300.0; + const int width = qRound(size.width() * hscale); + const int height = qRound(size.height() * vscale); - return QSize(std::max(1, width), std::max(1, height)); + return QSize(std::max(1, width), std::max(1, height)); } QSize OutputGenerator::to300dpi(const QSize& size, const Dpi& source_dpi) { - const double hscale = 300.0 / source_dpi.horizontal(); - const double vscale = 300.0 / source_dpi.vertical(); - const int width = qRound(size.width() * hscale); - const int height = qRound(size.height() * vscale); + const double hscale = 300.0 / source_dpi.horizontal(); + const double vscale = 300.0 / source_dpi.vertical(); + const int width = qRound(size.width() * hscale); + const int height = qRound(size.height() * vscale); - return QSize(std::max(1, width), std::max(1, height)); + return QSize(std::max(1, width), std::max(1, height)); } QImage OutputGenerator::convertToRGBorRGBA(const QImage& src) { - const QImage::Format fmt = src.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32; + const QImage::Format fmt = src.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32; - return src.convertToFormat(fmt); + return src.convertToFormat(fmt); } void OutputGenerator::fillMarginsInPlace(QImage& image, const QPolygonF& content_poly, const QColor& color, const bool antialiasing) { - if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { - BinaryImage binaryImage(image); - PolygonRasterizer::fillExcept(binaryImage, (color == Qt::black) ? BLACK : WHITE, content_poly, Qt::WindingFill); - image = binaryImage.toQImage(); + if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { + BinaryImage binaryImage(image); + PolygonRasterizer::fillExcept(binaryImage, (color == Qt::black) ? BLACK : WHITE, content_poly, Qt::WindingFill); + image = binaryImage.toQImage(); - return; - } + return; + } - if ((image.format() == QImage::Format_Indexed8) && image.isGrayscale()) { - PolygonRasterizer::grayFillExcept(image, static_cast(qGray(color.rgb())), content_poly, - Qt::WindingFill); + if ((image.format() == QImage::Format_Indexed8) && image.isGrayscale()) { + PolygonRasterizer::grayFillExcept(image, static_cast(qGray(color.rgb())), content_poly, + Qt::WindingFill); - return; - } + return; + } - assert(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32); + assert(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32); - const QImage::Format imageFormat = image.format(); - image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + const QImage::Format imageFormat = image.format(); + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); - { - QPainter painter(&image); - painter.setRenderHint(QPainter::Antialiasing, antialiasing); - painter.setBrush(color); - painter.setPen(Qt::NoPen); + { + QPainter painter(&image); + painter.setRenderHint(QPainter::Antialiasing, antialiasing); + painter.setBrush(color); + painter.setPen(Qt::NoPen); - QPainterPath outer_path; - outer_path.addRect(image.rect()); - QPainterPath inner_path; - inner_path.addPolygon(content_poly); + QPainterPath outer_path; + outer_path.addRect(image.rect()); + QPainterPath inner_path; + inner_path.addPolygon(content_poly); - painter.drawPath(outer_path.subtracted(inner_path)); - } + painter.drawPath(outer_path.subtracted(inner_path)); + } - image = image.convertToFormat(imageFormat); + image = image.convertToFormat(imageFormat); } // OutputGenerator::fillMarginsInPlace void OutputGenerator::fillMarginsInPlace(BinaryImage& image, const QPolygonF& content_poly, const BWColor& color) { - PolygonRasterizer::fillExcept(image, color, content_poly, Qt::WindingFill); + PolygonRasterizer::fillExcept(image, color, content_poly, Qt::WindingFill); } void OutputGenerator::fillMarginsInPlace(QImage& image, const BinaryImage& content_mask, const QColor& color) { - if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { - BinaryImage binaryImage(image); - fillExcept(binaryImage, content_mask, (color == Qt::black) ? BLACK : WHITE); - image = binaryImage.toQImage(); + if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { + BinaryImage binaryImage(image); + fillExcept(binaryImage, content_mask, (color == Qt::black) ? BLACK : WHITE); + image = binaryImage.toQImage(); - return; - } + return; + } - if (image.format() == QImage::Format_Indexed8) { - fillExcept(image, content_mask, color); - } else { - assert(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32); + if (image.format() == QImage::Format_Indexed8) { + fillExcept(image, content_mask, color); + } else { + assert(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32); - fillExcept(image, content_mask, color); - } + fillExcept(image, content_mask, color); + } } void OutputGenerator::fillMarginsInPlace(BinaryImage& image, const BinaryImage& content_mask, const BWColor& color) { - fillExcept(image, content_mask, color); + fillExcept(image, content_mask, color); } GrayImage OutputGenerator::detectPictures(const GrayImage& input_300dpi, const TaskStatus& status, DebugImages* const dbg) const { - // We stretch the range of gray levels to cover the whole - // range of [0, 255]. We do it because we want text - // and background to be equally far from the center - // of the whole range. Otherwise text printed with a big - // font will be considered a picture. - GrayImage stretched(stretchGrayRange(input_300dpi, 0.01, 0.01)); - if (dbg) { - dbg->add(stretched, "stretched"); - } - - status.throwIfCancelled(); - - GrayImage eroded(erodeGray(stretched, QSize(3, 3), 0x00)); - if (dbg) { - dbg->add(eroded, "eroded"); - } - - status.throwIfCancelled(); - - GrayImage dilated(dilateGray(stretched, QSize(3, 3), 0xff)); - if (dbg) { - dbg->add(dilated, "dilated"); - } - - stretched = GrayImage(); // Save memory. - status.throwIfCancelled(); - - grayRasterOp(dilated, eroded); - GrayImage gray_gradient(dilated); - dilated = GrayImage(); - eroded = GrayImage(); - if (dbg) { - dbg->add(gray_gradient, "gray_gradient"); - } - - status.throwIfCancelled(); - - GrayImage marker(erodeGray(gray_gradient, QSize(35, 35), 0x00)); - if (dbg) { - dbg->add(marker, "marker"); - } - - status.throwIfCancelled(); - - seedFillGrayInPlace(marker, gray_gradient, CONN8); - GrayImage reconstructed(marker); - marker = GrayImage(); - if (dbg) { - dbg->add(reconstructed, "reconstructed"); - } - - status.throwIfCancelled(); - - grayRasterOp>(reconstructed, reconstructed); - if (dbg) { - dbg->add(reconstructed, "reconstructed_inverted"); - } - - status.throwIfCancelled(); - - GrayImage holes_filled(createFramedImage(reconstructed.size())); - seedFillGrayInPlace(holes_filled, reconstructed, CONN8); - reconstructed = GrayImage(); + // We stretch the range of gray levels to cover the whole + // range of [0, 255]. We do it because we want text + // and background to be equally far from the center + // of the whole range. Otherwise text printed with a big + // font will be considered a picture. + GrayImage stretched(stretchGrayRange(input_300dpi, 0.01, 0.01)); + if (dbg) { + dbg->add(stretched, "stretched"); + } + + status.throwIfCancelled(); + + GrayImage eroded(erodeGray(stretched, QSize(3, 3), 0x00)); + if (dbg) { + dbg->add(eroded, "eroded"); + } + + status.throwIfCancelled(); + + GrayImage dilated(dilateGray(stretched, QSize(3, 3), 0xff)); + if (dbg) { + dbg->add(dilated, "dilated"); + } + + stretched = GrayImage(); // Save memory. + status.throwIfCancelled(); + + grayRasterOp(dilated, eroded); + GrayImage gray_gradient(dilated); + dilated = GrayImage(); + eroded = GrayImage(); + if (dbg) { + dbg->add(gray_gradient, "gray_gradient"); + } + + status.throwIfCancelled(); + + GrayImage marker(erodeGray(gray_gradient, QSize(35, 35), 0x00)); + if (dbg) { + dbg->add(marker, "marker"); + } + + status.throwIfCancelled(); + + seedFillGrayInPlace(marker, gray_gradient, CONN8); + GrayImage reconstructed(marker); + marker = GrayImage(); + if (dbg) { + dbg->add(reconstructed, "reconstructed"); + } + + status.throwIfCancelled(); + + grayRasterOp>(reconstructed, reconstructed); + if (dbg) { + dbg->add(reconstructed, "reconstructed_inverted"); + } + + status.throwIfCancelled(); + + GrayImage holes_filled(createFramedImage(reconstructed.size())); + seedFillGrayInPlace(holes_filled, reconstructed, CONN8); + reconstructed = GrayImage(); + if (dbg) { + dbg->add(holes_filled, "holes_filled"); + } + + if (m_pictureShapeOptions.isHigherSearchSensitivity()) { + GrayImage stretched2(stretchGrayRange(holes_filled, 5.0, 0.01)); if (dbg) { - dbg->add(holes_filled, "holes_filled"); + dbg->add(stretched2, "stretched2"); } - if (m_pictureShapeOptions.isHigherSearchSensitivity()) { - GrayImage stretched2(stretchGrayRange(holes_filled, 5.0, 0.01)); - if (dbg) { - dbg->add(stretched2, "stretched2"); - } - - return stretched2; - } + return stretched2; + } - return holes_filled; + return holes_filled; } // OutputGenerator::detectPictures QImage OutputGenerator::smoothToGrayscale(const QImage& src, const Dpi& dpi) { - const int min_dpi = std::min(dpi.horizontal(), dpi.vertical()); - int window; - int degree; - if (min_dpi <= 200) { - window = 5; - degree = 3; - } else if (min_dpi <= 400) { - window = 7; - degree = 4; - } else if (min_dpi <= 800) { - window = 11; - degree = 4; - } else { - window = 11; - degree = 2; - } - - return savGolFilter(src, QSize(window, window), degree, degree); + const int min_dpi = std::min(dpi.horizontal(), dpi.vertical()); + int window; + int degree; + if (min_dpi <= 200) { + window = 5; + degree = 3; + } else if (min_dpi <= 400) { + window = 7; + degree = 4; + } else if (min_dpi <= 800) { + window = 11; + degree = 4; + } else { + window = 11; + degree = 2; + } + + return savGolFilter(src, QSize(window, window), degree, degree); } BinaryThreshold OutputGenerator::adjustThreshold(BinaryThreshold threshold) const { - const int adjusted = threshold + m_colorParams.blackWhiteOptions().thresholdAdjustment(); + const int adjusted = threshold + m_colorParams.blackWhiteOptions().thresholdAdjustment(); - // Hard-bounding threshold values is necessary for example - // if all the content went into the picture mask. - return BinaryThreshold(qBound(30, adjusted, 225)); + // Hard-bounding threshold values is necessary for example + // if all the content went into the picture mask. + return BinaryThreshold(qBound(30, adjusted, 225)); } BinaryThreshold OutputGenerator::calcBinarizationThreshold(const QImage& image, const BinaryImage& mask) const { - GrayscaleHistogram hist(image, mask); + GrayscaleHistogram hist(image, mask); - return adjustThreshold(BinaryThreshold::otsuThreshold(hist)); + return adjustThreshold(BinaryThreshold::otsuThreshold(hist)); } BinaryThreshold OutputGenerator::calcBinarizationThreshold(const QImage& image, const QPolygonF& crop_area, const BinaryImage* mask) const { - QPainterPath path; - path.addPolygon(crop_area); - - if (path.contains(image.rect())) { - return adjustThreshold(BinaryThreshold::otsuThreshold(image)); - } else { - BinaryImage modified_mask(image.size(), BLACK); - PolygonRasterizer::fillExcept(modified_mask, WHITE, crop_area, Qt::WindingFill); - modified_mask = erodeBrick(modified_mask, QSize(3, 3), WHITE); + QPainterPath path; + path.addPolygon(crop_area); - if (mask) { - rasterOp>(modified_mask, *mask); - } + if (path.contains(image.rect())) { + return adjustThreshold(BinaryThreshold::otsuThreshold(image)); + } else { + BinaryImage modified_mask(image.size(), BLACK); + PolygonRasterizer::fillExcept(modified_mask, WHITE, crop_area, Qt::WindingFill); + modified_mask = erodeBrick(modified_mask, QSize(3, 3), WHITE); - return calcBinarizationThreshold(image, modified_mask); + if (mask) { + rasterOp>(modified_mask, *mask); } + + return calcBinarizationThreshold(image, modified_mask); + } } BinaryImage OutputGenerator::binarize(const QImage& image) const { - if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { - return BinaryImage(image); - } + if ((image.format() == QImage::Format_Mono) || (image.format() == QImage::Format_MonoLSB)) { + return BinaryImage(image); + } - const BlackWhiteOptions& blackWhiteOptions = m_colorParams.blackWhiteOptions(); - const BinarizationMethod binarizationMethod = blackWhiteOptions.getBinarizationMethod(); + const BlackWhiteOptions& blackWhiteOptions = m_colorParams.blackWhiteOptions(); + const BinarizationMethod binarizationMethod = blackWhiteOptions.getBinarizationMethod(); - QImage imageToBinarize = image; + QImage imageToBinarize = image; - BinaryImage binarized; - switch (binarizationMethod) { - case OTSU: { - GrayscaleHistogram hist(imageToBinarize); - const BinaryThreshold bw_thresh(BinaryThreshold::otsuThreshold(hist)); + BinaryImage binarized; + switch (binarizationMethod) { + case OTSU: { + GrayscaleHistogram hist(imageToBinarize); + const BinaryThreshold bw_thresh(BinaryThreshold::otsuThreshold(hist)); - binarized = BinaryImage(imageToBinarize, adjustThreshold(bw_thresh)); - break; - } - case SAUVOLA: { - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - double sauvolaCoef = blackWhiteOptions.getSauvolaCoef(); + binarized = BinaryImage(imageToBinarize, adjustThreshold(bw_thresh)); + break; + } + case SAUVOLA: { + QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + double sauvolaCoef = blackWhiteOptions.getSauvolaCoef(); - binarized = binarizeSauvola(imageToBinarize, windowsSize, sauvolaCoef); - break; - } - case WOLF: { - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound(); - auto upperBound = (unsigned char) blackWhiteOptions.getWolfUpperBound(); - double wolfCoef = blackWhiteOptions.getWolfCoef(); - - binarized = binarizeWolf(imageToBinarize, windowsSize, lowerBound, upperBound, wolfCoef); - break; - } + binarized = binarizeSauvola(imageToBinarize, windowsSize, sauvolaCoef); + break; } + case WOLF: { + QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound(); + auto upperBound = (unsigned char) blackWhiteOptions.getWolfUpperBound(); + double wolfCoef = blackWhiteOptions.getWolfCoef(); - return binarized; + binarized = binarizeWolf(imageToBinarize, windowsSize, lowerBound, upperBound, wolfCoef); + break; + } + } + + return binarized; } BinaryImage OutputGenerator::binarize(const QImage& image, const BinaryImage& mask) const { - BinaryImage binarized = binarize(image); + BinaryImage binarized = binarize(image); - rasterOp>(binarized, mask); + rasterOp>(binarized, mask); - return binarized; + return binarized; } BinaryImage OutputGenerator::binarize(const QImage& image, const QPolygonF& crop_area, const BinaryImage* mask) const { - QPainterPath path; - path.addPolygon(crop_area); - - if (path.contains(image.rect()) && !mask) { - return binarize(image); - } else { - BinaryImage modified_mask(image.size(), BLACK); - PolygonRasterizer::fillExcept(modified_mask, WHITE, crop_area, Qt::WindingFill); - modified_mask = erodeBrick(modified_mask, QSize(3, 3), WHITE); + QPainterPath path; + path.addPolygon(crop_area); - if (mask) { - rasterOp>(modified_mask, *mask); - } + if (path.contains(image.rect()) && !mask) { + return binarize(image); + } else { + BinaryImage modified_mask(image.size(), BLACK); + PolygonRasterizer::fillExcept(modified_mask, WHITE, crop_area, Qt::WindingFill); + modified_mask = erodeBrick(modified_mask, QSize(3, 3), WHITE); - return binarize(image, modified_mask); + if (mask) { + rasterOp>(modified_mask, *mask); } + + return binarize(image, modified_mask); + } } /** @@ -2101,186 +2189,178 @@ BinaryImage OutputGenerator::binarize(const QImage& image, const QPolygonF& crop void OutputGenerator::maybeDespeckleInPlace(imageproc::BinaryImage& image, const QRect& image_rect, const QRect& mask_rect, - const DespeckleLevel level, + const double level, BinaryImage* speckles_img, const Dpi& dpi, const TaskStatus& status, DebugImages* dbg) const { - const QRect src_rect(mask_rect.translated(-image_rect.topLeft())); - const QRect dst_rect(mask_rect); + const QRect src_rect(mask_rect.translated(-image_rect.topLeft())); + const QRect dst_rect(mask_rect); - if (speckles_img) { - BinaryImage(m_outRect.size(), WHITE).swap(*speckles_img); - if (!mask_rect.isEmpty()) { - rasterOp(*speckles_img, dst_rect, image, src_rect.topLeft()); - } + if (speckles_img) { + BinaryImage(m_outRect.size(), WHITE).swap(*speckles_img); + if (!mask_rect.isEmpty()) { + rasterOp(*speckles_img, dst_rect, image, src_rect.topLeft()); } + } - if (level != DESPECKLE_OFF) { - Despeckle::Level lvl = Despeckle::NORMAL; - switch (level) { - case DESPECKLE_CAUTIOUS: - lvl = Despeckle::CAUTIOUS; - break; - case DESPECKLE_NORMAL: - lvl = Despeckle::NORMAL; - break; - case DESPECKLE_AGGRESSIVE: - lvl = Despeckle::AGGRESSIVE; - break; - default:; - } - - Despeckle::despeckleInPlace(image, dpi, lvl, status, dbg); + if (level != 0) { + Despeckle::despeckleInPlace(image, dpi, level, status, dbg); - if (dbg) { - dbg->add(image, "despeckled"); - } + if (dbg) { + dbg->add(image, "despeckled"); } + } - if (speckles_img) { - if (!mask_rect.isEmpty()) { - rasterOp>(*speckles_img, dst_rect, image, src_rect.topLeft()); - } + if (speckles_img) { + if (!mask_rect.isEmpty()) { + rasterOp>(*speckles_img, dst_rect, image, src_rect.topLeft()); } + } } // OutputGenerator::maybeDespeckleInPlace void OutputGenerator::morphologicalSmoothInPlace(BinaryImage& bin_img, const TaskStatus& status) { - // When removing black noise, remove small ones first. - - { - const char pattern[] = "XXX" - " - " - " "; - hitMissReplaceAllDirections(bin_img, pattern, 3, 3); - } - - status.throwIfCancelled(); - - { - const char pattern[] = "X ?" - "X " - "X- " - "X- " - "X " - "X ?"; - hitMissReplaceAllDirections(bin_img, pattern, 3, 6); - } - - status.throwIfCancelled(); - - { - const char pattern[] = "X ?" - "X ?" - "X " - "X- " - "X- " - "X- " - "X " - "X ?" - "X ?"; - hitMissReplaceAllDirections(bin_img, pattern, 3, 9); - } - - status.throwIfCancelled(); - - { - const char pattern[] = "XX?" - "XX?" - "XX " - "X+ " - "X+ " - "X+ " - "XX " - "XX?" - "XX?"; - hitMissReplaceAllDirections(bin_img, pattern, 3, 9); - } - - status.throwIfCancelled(); - - { - const char pattern[] = "XX?" - "XX " - "X+ " - "X+ " - "XX " - "XX?"; - hitMissReplaceAllDirections(bin_img, pattern, 3, 6); - } - - status.throwIfCancelled(); - - { - const char pattern[] = " " - "X+X" - "XXX"; - hitMissReplaceAllDirections(bin_img, pattern, 3, 3); - } + // When removing black noise, remove small ones first. + + { + const char pattern[] + = "XXX" + " - " + " "; + hitMissReplaceAllDirections(bin_img, pattern, 3, 3); + } + + status.throwIfCancelled(); + + { + const char pattern[] + = "X ?" + "X " + "X- " + "X- " + "X " + "X ?"; + hitMissReplaceAllDirections(bin_img, pattern, 3, 6); + } + + status.throwIfCancelled(); + + { + const char pattern[] + = "X ?" + "X ?" + "X " + "X- " + "X- " + "X- " + "X " + "X ?" + "X ?"; + hitMissReplaceAllDirections(bin_img, pattern, 3, 9); + } + + status.throwIfCancelled(); + + { + const char pattern[] + = "XX?" + "XX?" + "XX " + "X+ " + "X+ " + "X+ " + "XX " + "XX?" + "XX?"; + hitMissReplaceAllDirections(bin_img, pattern, 3, 9); + } + + status.throwIfCancelled(); + + { + const char pattern[] + = "XX?" + "XX " + "X+ " + "X+ " + "XX " + "XX?"; + hitMissReplaceAllDirections(bin_img, pattern, 3, 6); + } + + status.throwIfCancelled(); + + { + const char pattern[] + = " " + "X+X" + "XXX"; + hitMissReplaceAllDirections(bin_img, pattern, 3, 3); + } } // OutputGenerator::morphologicalSmoothInPlace void OutputGenerator::hitMissReplaceAllDirections(imageproc::BinaryImage& img, const char* const pattern, const int pattern_width, const int pattern_height) { - hitMissReplaceInPlace(img, WHITE, pattern, pattern_width, pattern_height); - - std::vector pattern_data(static_cast(pattern_width * pattern_height), ' '); - char* const new_pattern = &pattern_data[0]; - - // Rotate 90 degrees clockwise. - const char* p = pattern; - int new_width = pattern_height; - int new_height = pattern_width; - for (int y = 0; y < pattern_height; ++y) { - for (int x = 0; x < pattern_width; ++x, ++p) { - const int new_x = pattern_height - 1 - y; - const int new_y = x; - new_pattern[new_y * new_width + new_x] = *p; - } - } - hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); - - // Rotate upside down. - p = pattern; - new_width = pattern_width; - new_height = pattern_height; - for (int y = 0; y < pattern_height; ++y) { - for (int x = 0; x < pattern_width; ++x, ++p) { - const int new_x = pattern_width - 1 - x; - const int new_y = pattern_height - 1 - y; - new_pattern[new_y * new_width + new_x] = *p; - } - } - hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); - // Rotate 90 degrees counter-clockwise. - p = pattern; - new_width = pattern_height; - new_height = pattern_width; - for (int y = 0; y < pattern_height; ++y) { - for (int x = 0; x < pattern_width; ++x, ++p) { - const int new_x = y; - const int new_y = pattern_width - 1 - x; - new_pattern[new_y * new_width + new_x] = *p; - } - } - hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); + hitMissReplaceInPlace(img, WHITE, pattern, pattern_width, pattern_height); + + std::vector pattern_data(static_cast(pattern_width * pattern_height), ' '); + char* const new_pattern = &pattern_data[0]; + + // Rotate 90 degrees clockwise. + const char* p = pattern; + int new_width = pattern_height; + int new_height = pattern_width; + for (int y = 0; y < pattern_height; ++y) { + for (int x = 0; x < pattern_width; ++x, ++p) { + const int new_x = pattern_height - 1 - y; + const int new_y = x; + new_pattern[new_y * new_width + new_x] = *p; + } + } + hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); + + // Rotate upside down. + p = pattern; + new_width = pattern_width; + new_height = pattern_height; + for (int y = 0; y < pattern_height; ++y) { + for (int x = 0; x < pattern_width; ++x, ++p) { + const int new_x = pattern_width - 1 - x; + const int new_y = pattern_height - 1 - y; + new_pattern[new_y * new_width + new_x] = *p; + } + } + hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); + // Rotate 90 degrees counter-clockwise. + p = pattern; + new_width = pattern_height; + new_height = pattern_width; + for (int y = 0; y < pattern_height; ++y) { + for (int x = 0; x < pattern_width; ++x, ++p) { + const int new_x = y; + const int new_y = pattern_width - 1 - x; + new_pattern[new_y * new_width + new_x] = *p; + } + } + hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); } // OutputGenerator::hitMissReplaceAllDirections QSize OutputGenerator::calcLocalWindowSize(const Dpi& dpi) { - const QSizeF size_mm(3, 30); - const QSizeF size_inch(size_mm * constants::MM2INCH); - const QSizeF size_pixels_f(dpi.horizontal() * size_inch.width(), dpi.vertical() * size_inch.height()); - QSize size_pixels(size_pixels_f.toSize()); - - if (size_pixels.width() < 3) { - size_pixels.setWidth(3); - } - if (size_pixels.height() < 3) { - size_pixels.setHeight(3); - } - - return size_pixels; + const QSizeF size_mm(3, 30); + const QSizeF size_inch(size_mm * constants::MM2INCH); + const QSizeF size_pixels_f(dpi.horizontal() * size_inch.width(), dpi.vertical() * size_inch.height()); + QSize size_pixels(size_pixels_f.toSize()); + + if (size_pixels.width() < 3) { + size_pixels.setWidth(3); + } + if (size_pixels.height() < 3) { + size_pixels.setHeight(3); + } + + return size_pixels; } void OutputGenerator::applyFillZonesInPlace(QImage& img, @@ -2288,82 +2368,82 @@ void OutputGenerator::applyFillZonesInPlace(QImage& img, const boost::function& orig_to_output, const QTransform& postTransform, const bool antialiasing) const { - if (zones.empty()) { - return; - } + if (zones.empty()) { + return; + } - QImage canvas(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QImage canvas(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - { - QPainter painter(&canvas); - painter.setRenderHint(QPainter::Antialiasing, antialiasing); - painter.setPen(Qt::NoPen); - - for (const Zone& zone : zones) { - const QColor color(zone.properties().locateOrDefault()->color()); - const QPolygonF poly(postTransform.map(zone.spline().transformed(orig_to_output).toPolygon())); - painter.setBrush(color); - painter.drawPolygon(poly, Qt::WindingFill); - } - } + { + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing, antialiasing); + painter.setPen(Qt::NoPen); - if ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()) { - img = toGrayscale(canvas); - } else { - img = canvas.convertToFormat(img.format()); - } + for (const Zone& zone : zones) { + const QColor color(zone.properties().locateOrDefault()->color()); + const QPolygonF poly(postTransform.map(zone.spline().transformed(orig_to_output).toPolygon())); + painter.setBrush(color); + painter.drawPolygon(poly, Qt::WindingFill); + } + } + + if ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()) { + img = toGrayscale(canvas); + } else { + img = canvas.convertToFormat(img.format()); + } } void OutputGenerator::applyFillZonesInPlace(QImage& img, const ZoneSet& zones, const boost::function& orig_to_output, const bool antialiasing) const { - applyFillZonesInPlace(img, zones, orig_to_output, QTransform(), antialiasing); + applyFillZonesInPlace(img, zones, orig_to_output, QTransform(), antialiasing); } void OutputGenerator::applyFillZonesInPlace(QImage& img, const ZoneSet& zones, const QTransform& postTransform, const bool antialiasing) const { - typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; - applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), - postTransform, antialiasing); + typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; + applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), + postTransform, antialiasing); } void OutputGenerator::applyFillZonesInPlace(QImage& img, const ZoneSet& zones, const bool antialiasing) const { - typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; - applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), - antialiasing); + typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; + applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), + antialiasing); } void OutputGenerator::applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones, const boost::function& orig_to_output, const QTransform& postTransform) const { - if (zones.empty()) { - return; - } - - for (const Zone& zone : zones) { - const QColor color(zone.properties().locateOrDefault()->color()); - const BWColor bw_color = qGray(color.rgb()) < 128 ? BLACK : WHITE; - const QPolygonF poly(postTransform.map(zone.spline().transformed(orig_to_output).toPolygon())); - PolygonRasterizer::fill(img, bw_color, poly, Qt::WindingFill); - } + if (zones.empty()) { + return; + } + + for (const Zone& zone : zones) { + const QColor color(zone.properties().locateOrDefault()->color()); + const BWColor bw_color = qGray(color.rgb()) < 128 ? BLACK : WHITE; + const QPolygonF poly(postTransform.map(zone.spline().transformed(orig_to_output).toPolygon())); + PolygonRasterizer::fill(img, bw_color, poly, Qt::WindingFill); + } } void OutputGenerator::applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones, const boost::function& orig_to_output) const { - applyFillZonesInPlace(img, zones, orig_to_output, QTransform()); + applyFillZonesInPlace(img, zones, orig_to_output, QTransform()); } void OutputGenerator::applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones, const QTransform& postTransform) const { - typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; - applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), - postTransform); + typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; + applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1), + postTransform); } /** @@ -2371,177 +2451,177 @@ void OutputGenerator::applyFillZonesInPlace(imageproc::BinaryImage& img, * from original image to output image coordinates. */ void OutputGenerator::applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones) const { - typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; - applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1)); + typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; + applyFillZonesInPlace(img, zones, boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1)); } void OutputGenerator::movePointToTopMargin(BinaryImage& bw_image, XSpline& spline, int idx) const { - QPointF pos = spline.controlPointPosition(idx); + QPointF pos = spline.controlPointPosition(idx); - for (int j = 0; j < pos.y(); j++) { - if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { - int count = 0; - int check_num = 16; - - for (int jj = j; jj < (j + check_num); jj++) { - if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { - count++; - } - } + for (int j = 0; j < pos.y(); j++) { + if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { + int count = 0; + int check_num = 16; - if (count == check_num) { - pos.setY(j); - spline.moveControlPoint(idx, pos); - break; - } + for (int jj = j; jj < (j + check_num); jj++) { + if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { + count++; } + } + + if (count == check_num) { + pos.setY(j); + spline.moveControlPoint(idx, pos); + break; + } } + } } void OutputGenerator::movePointToBottomMargin(BinaryImage& bw_image, XSpline& spline, int idx) const { - QPointF pos = spline.controlPointPosition(idx); + QPointF pos = spline.controlPointPosition(idx); - for (int j = bw_image.height() - 1; j > pos.y(); j--) { - if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { - int count = 0; - int check_num = 16; + for (int j = bw_image.height() - 1; j > pos.y(); j--) { + if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { + int count = 0; + int check_num = 16; - for (int jj = j; jj > (j - check_num); jj--) { - if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { - count++; - } - } + for (int jj = j; jj > (j - check_num); jj--) { + if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { + count++; + } + } - if (count == check_num) { - pos.setY(j); + if (count == check_num) { + pos.setY(j); - spline.moveControlPoint(idx, pos); + spline.moveControlPoint(idx, pos); - break; - } - } + break; + } } + } } void OutputGenerator::drawPoint(QImage& image, const QPointF& pt) const { - QPoint pts = pt.toPoint(); + QPoint pts = pt.toPoint(); - for (int i = pts.x() - 10; i < pts.x() + 10; i++) { - for (int j = pts.y() - 10; j < pts.y() + 10; j++) { - QPoint p1(i, j); + for (int i = pts.x() - 10; i < pts.x() + 10; i++) { + for (int j = pts.y() - 10; j < pts.y() + 10; j++) { + QPoint p1(i, j); - image.setPixel(p1, qRgb(255, 0, 0)); - } + image.setPixel(p1, qRgb(255, 0, 0)); } + } } void OutputGenerator::movePointToTopMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const { - QPointF& pos = polyline[idx]; + QPointF& pos = polyline[idx]; - for (int j = 0; j < pos.y(); j++) { - if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { - int count = 0; - int check_num = 16; + for (int j = 0; j < pos.y(); j++) { + if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { + int count = 0; + int check_num = 16; - for (int jj = j; jj < (j + check_num); jj++) { - if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { - count++; - } - } + for (int jj = j; jj < (j + check_num); jj++) { + if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { + count++; + } + } - if (count == check_num) { - pos.setY(j); + if (count == check_num) { + pos.setY(j); - break; - } - } + break; + } } + } } void OutputGenerator::movePointToBottomMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const { - QPointF& pos = polyline[idx]; + QPointF& pos = polyline[idx]; - for (int j = bw_image.height() - 1; j > pos.y(); j--) { - if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { - int count = 0; - int check_num = 16; + for (int j = bw_image.height() - 1; j > pos.y(); j--) { + if (bw_image.getPixel(static_cast(pos.x()), j) == WHITE) { + int count = 0; + int check_num = 16; - for (int jj = j; jj > (j - check_num); jj--) { - if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { - count++; - } - } + for (int jj = j; jj > (j - check_num); jj--) { + if (bw_image.getPixel(static_cast(pos.x()), jj) == WHITE) { + count++; + } + } - if (count == check_num) { - pos.setY(j); + if (count == check_num) { + pos.setY(j); - break; - } - } + break; + } } + } } float OutputGenerator::vert_border_skew_angle(const QPointF& top, const QPointF& bottom) const { - return static_cast(qFabs(qAtan((bottom.x() - top.x()) / (bottom.y() - top.y())) * 180 / M_PI)); + return static_cast(qFabs(qAtan((bottom.x() - top.x()) / (bottom.y() - top.y())) * 180 / M_PI)); } void OutputGenerator::deskew(QImage* image, const double angle, const QColor& outside_color) const { - if (angle == .0) { - return; - } + if (angle == .0) { + return; + } - QPointF center(image->width() / 2, image->height() / 2); + QPointF center(image->width() / 2.0, image->height() / 2.0); - QTransform rot; - rot.translate(center.x(), center.y()); - rot.rotate(-angle); - rot.translate(-center.x(), -center.y()); + QTransform rot; + rot.translate(center.x(), center.y()); + rot.rotate(-angle); + rot.translate(-center.x(), -center.y()); - *image = imageproc::transform(*image, rot, image->rect(), OutsidePixels::assumeWeakColor(outside_color)); + *image = imageproc::transform(*image, rot, image->rect(), OutsidePixels::assumeWeakColor(outside_color)); } -double OutputGenerator::maybe_deskew(QImage* p_dewarped, +double OutputGenerator::maybe_deskew(QImage* dewarped, DewarpingOptions m_dewarpingOptions, const QColor& outside_color) const { - if (m_dewarpingOptions.needPostDeskew() - && ((m_dewarpingOptions.dewarpingMode() == MARGINAL) || (m_dewarpingOptions.dewarpingMode() == MANUAL))) { - BinaryThreshold bw_threshold(128); - BinaryImage bw_image(*p_dewarped, bw_threshold); + if (m_dewarpingOptions.needPostDeskew() + && ((m_dewarpingOptions.dewarpingMode() == MARGINAL) || (m_dewarpingOptions.dewarpingMode() == MANUAL))) { + BinaryThreshold bw_threshold(128); + BinaryImage bw_image(*dewarped, bw_threshold); - SkewFinder skew_finder; - const Skew skew(skew_finder.findSkew(bw_image)); - if ((skew.angle() != 0.0) && (skew.confidence() >= Skew::GOOD_CONFIDENCE)) { - const double angle_deg = skew.angle(); + SkewFinder skew_finder; + const Skew skew(skew_finder.findSkew(bw_image)); + if ((skew.angle() != 0.0) && (skew.confidence() >= Skew::GOOD_CONFIDENCE)) { + const double angle_deg = skew.angle(); - deskew(p_dewarped, angle_deg, outside_color); + deskew(dewarped, angle_deg, outside_color); - return angle_deg; - } + return angle_deg; } + } - return .0; + return .0; } const QTransform& OutputGenerator::getPostTransform() const { - return postTransform; + return m_postTransform; } void OutputGenerator::applyFillZonesToMixedInPlace(QImage& img, const ZoneSet& zones, const BinaryImage& picture_mask, const bool binary_mode) const { - if (binary_mode) { - BinaryImage bw_content(img, BinaryThreshold(1)); - applyFillZonesInPlace(bw_content, zones); - applyFillZonesInPlace(img, zones); - combineImageMono(img, bw_content, picture_mask); - } else { - QImage content(img); - applyMask(content, picture_mask); - applyFillZonesInPlace(content, zones, false); - applyFillZonesInPlace(img, zones); - combineImageColor(img, content, picture_mask); - } + if (binary_mode) { + BinaryImage bw_content(img, BinaryThreshold(1)); + applyFillZonesInPlace(bw_content, zones); + applyFillZonesInPlace(img, zones); + combineImages(img, bw_content, picture_mask); + } else { + QImage content(img); + applyMask(content, picture_mask); + applyFillZonesInPlace(content, zones, false); + applyFillZonesInPlace(img, zones); + combineImages(img, content, picture_mask); + } } void OutputGenerator::applyFillZonesToMixedInPlace(QImage& img, @@ -2550,41 +2630,40 @@ void OutputGenerator::applyFillZonesToMixedInPlace(QImage& img, const QTransform& postTransform, const BinaryImage& picture_mask, const bool binary_mode) const { - if (binary_mode) { - BinaryImage bw_content(img, BinaryThreshold(1)); - applyFillZonesInPlace(bw_content, zones, orig_to_output, postTransform); - applyFillZonesInPlace(img, zones, orig_to_output, postTransform); - combineImageMono(img, bw_content, picture_mask); - } else { - QImage content(img); - applyMask(content, picture_mask); - applyFillZonesInPlace(content, zones, orig_to_output, postTransform, false); - applyFillZonesInPlace(img, zones, orig_to_output, postTransform); - combineImageColor(img, content, picture_mask); - } + if (binary_mode) { + BinaryImage bw_content(img, BinaryThreshold(1)); + applyFillZonesInPlace(bw_content, zones, orig_to_output, postTransform); + applyFillZonesInPlace(img, zones, orig_to_output, postTransform); + combineImages(img, bw_content, picture_mask); + } else { + QImage content(img); + applyMask(content, picture_mask); + applyFillZonesInPlace(content, zones, orig_to_output, postTransform, false); + applyFillZonesInPlace(img, zones, orig_to_output, postTransform); + combineImages(img, content, picture_mask); + } } QImage OutputGenerator::segmentImage(const BinaryImage& image, const QImage& color_image) const { - const BlackWhiteOptions::ColorSegmenterOptions& segmenterOptions - = m_colorParams.blackWhiteOptions().getColorSegmenterOptions(); - if (!color_image.allGray()) { - return ColorSegmenter(image, color_image, m_dpi, segmenterOptions.getNoiseReduction(), - segmenterOptions.getRedThresholdAdjustment(), - segmenterOptions.getGreenThresholdAdjustment(), - segmenterOptions.getBlueThresholdAdjustment()) - .getImage(); - } else { - return ColorSegmenter(image, GrayImage(color_image), m_dpi, segmenterOptions.getNoiseReduction()).getImage(); - } + const BlackWhiteOptions::ColorSegmenterOptions& segmenterOptions + = m_colorParams.blackWhiteOptions().getColorSegmenterOptions(); + if (!color_image.allGray()) { + return ColorSegmenter(image, color_image, m_dpi, segmenterOptions.getNoiseReduction(), + segmenterOptions.getRedThresholdAdjustment(), segmenterOptions.getGreenThresholdAdjustment(), + segmenterOptions.getBlueThresholdAdjustment()) + .getImage(); + } else { + return ColorSegmenter(image, GrayImage(color_image), m_dpi, segmenterOptions.getNoiseReduction()).getImage(); + } } QImage OutputGenerator::posterizeImage(const QImage& image, const QColor& background_color) const { - const ColorCommonOptions::PosterizationOptions& posterizationOptions - = m_colorParams.colorCommonOptions().getPosterizationOptions(); + const ColorCommonOptions::PosterizationOptions& posterizationOptions + = m_colorParams.colorCommonOptions().getPosterizationOptions(); - return ColorTable(image) - .posterize(posterizationOptions.getLevel(), posterizationOptions.isNormalizationEnabled(), - posterizationOptions.isForceBlackAndWhite(), 0, qRound(background_color.lightnessF() * 255)) - .getImage(); + return ColorTable(image) + .posterize(posterizationOptions.getLevel(), posterizationOptions.isNormalizationEnabled(), + posterizationOptions.isForceBlackAndWhite(), 0, qRound(background_color.lightnessF() * 255)) + .getImage(); } } // namespace output \ No newline at end of file diff --git a/filters/output/OutputGenerator.h b/filters/output/OutputGenerator.h index e6ebd4ed8..b869f198f 100644 --- a/filters/output/OutputGenerator.h +++ b/filters/output/OutputGenerator.h @@ -19,36 +19,35 @@ #ifndef OUTPUT_OUTPUTGENERATOR_H_ #define OUTPUT_OUTPUTGENERATOR_H_ -#include "imageproc/Connectivity.h" -#include "Dpi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "ColorParams.h" -#include "Params.h" #include "DepthPerception.h" #include "DespeckleLevel.h" #include "DewarpingOptions.h" +#include "Dpi.h" #include "ImageTransformation.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Params.h" +#include "OutputProcessingParams.h" #include "PageId.h" -#include "intrusive_ptr.h" +#include "Params.h" #include "Settings.h" -#include +#include "SplitImage.h" #include "TiffWriter.h" -#include -#include +#include "imageproc/Connectivity.h" #include "imageproc/SkewFinder.h" -#include "SplitImage.h" -#include "OutputProcessingParams.h" +#include "intrusive_ptr.h" class TaskStatus; class DebugImages; @@ -73,279 +72,277 @@ using namespace imageproc; namespace output { class OutputGenerator { -public: - OutputGenerator(const Dpi& dpi, - const ColorParams& color_params, - const SplittingOptions& splitting_options, - const PictureShapeOptions& picture_shape_options, - const DewarpingOptions& dewarping_options, - const OutputProcessingParams& output_processing_params, - DespeckleLevel despeckle_level, - const ImageTransformation& xform, - const QPolygonF& content_rect_phys); - - /** - * \brief Produce the output image. - * - * \param status For asynchronous task cancellation. - * \param input The input image plus data produced by previous stages. - * \param picture_zones A set of manual picture zones. - * \param fill_zones A set of manual fill zones. - * \param distortion_model A curved rectangle. - * \param auto_picture_mask If provided, the auto-detected picture mask - * will be written there. It would only happen if automatic picture - * detection actually took place. Otherwise, nothing will be - * written into the provided image. Black areas on the mask - * indicate pictures. The manual zones aren't represented in it. - * \param speckles_image If provided, the speckles removed from the - * binarized image will be written there. It would only happen - * if despeckling was required and actually took place. - * Otherwise, nothing will be written into the provided image. - * Because despeckling is intentionally the last operation on - * the B/W part of the image, the "pre-despeckle" image may be - * restored from the output and speckles images, allowing despeckling - * to be performed again with different settings, without going - * through the whole output generation process again. - * \param dbg An optional sink for debugging images. - */ - QImage process(const TaskStatus& status, - const FilterData& input, - ZoneSet& picture_zones, - const ZoneSet& fill_zones, - dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception, - imageproc::BinaryImage* auto_picture_mask, - imageproc::BinaryImage* speckles_image, - DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, - SplitImage* splitImage); - - QSize outputImageSize() const; - - /** - * \brief Returns the content rectangle in output image coordinates. - */ - QRect outputContentRect() const; - - const QTransform& getPostTransform() const; - -private: - QImage processImpl(const TaskStatus& status, - const FilterData& input, - ZoneSet& picture_zones, - const ZoneSet& fill_zones, - dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception, - imageproc::BinaryImage* auto_picture_mask, - imageproc::BinaryImage* speckles_image, - DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, - SplitImage* splitImage); - - QImage processWithoutDewarping(const TaskStatus& status, - const FilterData& input, - ZoneSet& picture_zones, - const ZoneSet& fill_zones, - imageproc::BinaryImage* auto_picture_mask, - imageproc::BinaryImage* speckles_image, - DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, - SplitImage* splitImage); - - QImage processWithDewarping(const TaskStatus& status, - const FilterData& input, - ZoneSet& picture_zones, - const ZoneSet& fill_zones, - dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception, - imageproc::BinaryImage* auto_picture_mask, - imageproc::BinaryImage* speckles_image, - DebugImages* dbg, - const PageId& p_pageId, - const intrusive_ptr& p_settings, - SplitImage* splitImage); - - void movePointToTopMargin(BinaryImage& bw_image, XSpline& spline, int idx) const; - - void movePointToBottomMargin(BinaryImage& bw_image, XSpline& spline, int idx) const; - - void drawPoint(QImage& image, const QPointF& pt) const; - - void deskew(QImage* image, double angle, const QColor& outside_color) const; - - double maybe_deskew(QImage* p_dewarped, DewarpingOptions dewarping_options, const QColor& outside_color) const; - - void movePointToTopMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const; - - void movePointToBottomMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const; - - float vert_border_skew_angle(const QPointF& top, const QPointF& bottom) const; - - void setupTrivialDistortionModel(dewarping::DistortionModel& distortion_model) const; - - static dewarping::CylindricalSurfaceDewarper createDewarper(const dewarping::DistortionModel& distortion_model, - const QTransform& distortion_model_to_target, - double depth_perception); - - QImage dewarp(const QTransform& orig_to_src, - const QImage& src, - const QTransform& src_to_output, - const dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception, - const QColor& bg_color) const; - - static QSize from300dpi(const QSize& size, const Dpi& target_dpi); - - static QSize to300dpi(const QSize& size, const Dpi& source_dpi); - - static QImage convertToRGBorRGBA(const QImage& src); - - static void fillMarginsInPlace(QImage& image, - const QPolygonF& content_poly, - const QColor& color, - bool antialiasing = true); - - static void fillMarginsInPlace(BinaryImage& image, const QPolygonF& content_poly, const BWColor& color); - - static void fillMarginsInPlace(QImage& image, const BinaryImage& content_mask, const QColor& color); - - static void fillMarginsInPlace(BinaryImage& image, const BinaryImage& content_mask, const BWColor& color); - - static imageproc::GrayImage normalizeIlluminationGray(const TaskStatus& status, - const QImage& input, - const QPolygonF& area_to_consider, - const QTransform& xform, - const QRect& target_rect, - imageproc::GrayImage* background = nullptr, - DebugImages* dbg = nullptr); + public: + OutputGenerator(const Dpi& dpi, + const ColorParams& color_params, + const SplittingOptions& splitting_options, + const PictureShapeOptions& picture_shape_options, + const DewarpingOptions& dewarping_options, + const OutputProcessingParams& output_processing_params, + double despeckle_level, + const ImageTransformation& xform, + const QPolygonF& content_rect_phys); + + /** + * \brief Produce the output image. + * + * \param status For asynchronous task cancellation. + * \param input The input image plus data produced by previous stages. + * \param picture_zones A set of manual picture zones. + * \param fill_zones A set of manual fill zones. + * \param distortion_model A curved rectangle. + * \param auto_picture_mask If provided, the auto-detected picture mask + * will be written there. It would only happen if automatic picture + * detection actually took place. Otherwise, nothing will be + * written into the provided image. Black areas on the mask + * indicate pictures. The manual zones aren't represented in it. + * \param speckles_image If provided, the speckles removed from the + * binarized image will be written there. It would only happen + * if despeckling was required and actually took place. + * Otherwise, nothing will be written into the provided image. + * Because despeckling is intentionally the last operation on + * the B/W part of the image, the "pre-despeckle" image may be + * restored from the output and speckles images, allowing despeckling + * to be performed again with different settings, without going + * through the whole output generation process again. + * \param dbg An optional sink for debugging images. + */ + QImage process(const TaskStatus& status, + const FilterData& input, + ZoneSet& picture_zones, + const ZoneSet& fill_zones, + dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception, + imageproc::BinaryImage* auto_picture_mask, + imageproc::BinaryImage* speckles_image, + DebugImages* dbg, + const PageId& pageId, + const intrusive_ptr& settings, + SplitImage* splitImage); + + QSize outputImageSize() const; + + /** + * \brief Returns the content rectangle in output image coordinates. + */ + QRect outputContentRect() const; + + const QTransform& getPostTransform() const; + + private: + QImage processImpl(const TaskStatus& status, + const FilterData& input, + ZoneSet& picture_zones, + const ZoneSet& fill_zones, + dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception, + imageproc::BinaryImage* auto_picture_mask, + imageproc::BinaryImage* speckles_image, + DebugImages* dbg, + const PageId& pageId, + const intrusive_ptr& settings, + SplitImage* splitImage); + + QImage processWithoutDewarping(const TaskStatus& status, + const FilterData& input, + ZoneSet& picture_zones, + const ZoneSet& fill_zones, + imageproc::BinaryImage* auto_picture_mask, + imageproc::BinaryImage* speckles_image, + DebugImages* dbg, + const PageId& pageId, + const intrusive_ptr& settings, + SplitImage* splitImage); + + QImage processWithDewarping(const TaskStatus& status, + const FilterData& input, + ZoneSet& picture_zones, + const ZoneSet& fill_zones, + dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception, + imageproc::BinaryImage* auto_picture_mask, + imageproc::BinaryImage* speckles_image, + DebugImages* dbg, + const PageId& pageId, + const intrusive_ptr& settings, + SplitImage* splitImage); + + void movePointToTopMargin(BinaryImage& bw_image, XSpline& spline, int idx) const; + + void movePointToBottomMargin(BinaryImage& bw_image, XSpline& spline, int idx) const; + + void drawPoint(QImage& image, const QPointF& pt) const; + + void deskew(QImage* image, double angle, const QColor& outside_color) const; + + double maybe_deskew(QImage* dewarped, DewarpingOptions dewarping_options, const QColor& outside_color) const; + + void movePointToTopMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const; + + void movePointToBottomMargin(BinaryImage& bw_image, std::vector& polyline, int idx) const; + + float vert_border_skew_angle(const QPointF& top, const QPointF& bottom) const; + + void setupTrivialDistortionModel(dewarping::DistortionModel& distortion_model) const; + + static dewarping::CylindricalSurfaceDewarper createDewarper(const dewarping::DistortionModel& distortion_model, + const QTransform& distortion_model_to_target, + double depth_perception); + + QImage dewarp(const QTransform& orig_to_src, + const QImage& src, + const QTransform& src_to_output, + const dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception, + const QColor& bg_color) const; + + static QSize from300dpi(const QSize& size, const Dpi& target_dpi); + + static QSize to300dpi(const QSize& size, const Dpi& source_dpi); + + static QImage convertToRGBorRGBA(const QImage& src); + + static void fillMarginsInPlace(QImage& image, + const QPolygonF& content_poly, + const QColor& color, + bool antialiasing = true); + + static void fillMarginsInPlace(BinaryImage& image, const QPolygonF& content_poly, const BWColor& color); + + static void fillMarginsInPlace(QImage& image, const BinaryImage& content_mask, const QColor& color); + + static void fillMarginsInPlace(BinaryImage& image, const BinaryImage& content_mask, const BWColor& color); + + static imageproc::GrayImage normalizeIlluminationGray(const TaskStatus& status, + const QImage& input, + const QPolygonF& area_to_consider, + const QTransform& xform, + const QRect& target_rect, + imageproc::GrayImage* background = nullptr, + DebugImages* dbg = nullptr); + + imageproc::GrayImage detectPictures(const imageproc::GrayImage& input_300dpi, + const TaskStatus& status, + DebugImages* dbg = nullptr) const; - imageproc::GrayImage detectPictures(const imageproc::GrayImage& input_300dpi, - const TaskStatus& status, - DebugImages* dbg = nullptr) const; + imageproc::BinaryImage estimateBinarizationMask(const TaskStatus& status, + const imageproc::GrayImage& gray_source, + const QRect& source_rect, + const QRect& source_sub_rect, + DebugImages* dbg) const; - imageproc::BinaryImage estimateBinarizationMask(const TaskStatus& status, - const imageproc::GrayImage& gray_source, - const QRect& source_rect, - const QRect& source_sub_rect, - DebugImages* dbg) const; + void modifyBinarizationMask(imageproc::BinaryImage& bw_mask, const QRect& mask_rect, const ZoneSet& zones) const; - void modifyBinarizationMask(imageproc::BinaryImage& bw_mask, const QRect& mask_rect, const ZoneSet& zones) const; + imageproc::BinaryThreshold adjustThreshold(imageproc::BinaryThreshold threshold) const; - imageproc::BinaryThreshold adjustThreshold(imageproc::BinaryThreshold threshold) const; + imageproc::BinaryThreshold calcBinarizationThreshold(const QImage& image, const imageproc::BinaryImage& mask) const; - imageproc::BinaryThreshold calcBinarizationThreshold(const QImage& image, const imageproc::BinaryImage& mask) const; + imageproc::BinaryThreshold calcBinarizationThreshold(const QImage& image, + const QPolygonF& crop_area, + const imageproc::BinaryImage* mask = nullptr) const; - imageproc::BinaryThreshold calcBinarizationThreshold(const QImage& image, - const QPolygonF& crop_area, - const imageproc::BinaryImage* mask = nullptr) const; + BinaryImage binarize(const QImage& image) const; - BinaryImage binarize(const QImage& image) const; + imageproc::BinaryImage binarize(const QImage& image, const imageproc::BinaryImage& mask) const; - imageproc::BinaryImage binarize(const QImage& image, const imageproc::BinaryImage& mask) const; + imageproc::BinaryImage binarize(const QImage& image, + const QPolygonF& crop_area, + const imageproc::BinaryImage* mask = nullptr) const; - imageproc::BinaryImage binarize(const QImage& image, - const QPolygonF& crop_area, - const imageproc::BinaryImage* mask = nullptr) const; + void maybeDespeckleInPlace(imageproc::BinaryImage& image, + const QRect& image_rect, + const QRect& mask_rect, + double level, + imageproc::BinaryImage* speckles_img, + const Dpi& dpi, + const TaskStatus& status, + DebugImages* dbg) const; - void maybeDespeckleInPlace(imageproc::BinaryImage& image, - const QRect& image_rect, - const QRect& mask_rect, - DespeckleLevel level, - imageproc::BinaryImage* speckles_img, - const Dpi& dpi, - const TaskStatus& status, - DebugImages* dbg) const; + static QImage smoothToGrayscale(const QImage& src, const Dpi& dpi); - static QImage smoothToGrayscale(const QImage& src, const Dpi& dpi); + static void morphologicalSmoothInPlace(imageproc::BinaryImage& img, const TaskStatus& status); - static void morphologicalSmoothInPlace(imageproc::BinaryImage& img, const TaskStatus& status); + static void hitMissReplaceAllDirections(imageproc::BinaryImage& img, + const char* pattern, + int pattern_width, + int pattern_height); - static void hitMissReplaceAllDirections(imageproc::BinaryImage& img, - const char* pattern, - int pattern_width, - int pattern_height); + static QSize calcLocalWindowSize(const Dpi& dpi); - static QSize calcLocalWindowSize(const Dpi& dpi); + void applyFillZonesInPlace(QImage& img, + const ZoneSet& zones, + const boost::function& orig_to_output, + const QTransform& postTransform, + bool antialiasing = true) const; - void applyFillZonesInPlace(QImage& img, - const ZoneSet& zones, - const boost::function& orig_to_output, - const QTransform& postTransform, - bool antialiasing = true) const; + void applyFillZonesInPlace(QImage& img, + const ZoneSet& zones, + const boost::function& orig_to_output, + bool antialiasing = true) const; - void applyFillZonesInPlace(QImage& img, - const ZoneSet& zones, - const boost::function& orig_to_output, - bool antialiasing = true) const; + void applyFillZonesInPlace(QImage& img, + const ZoneSet& zones, + const QTransform& postTransform, + bool antialiasing = true) const; - void applyFillZonesInPlace(QImage& img, - const ZoneSet& zones, - const QTransform& postTransform, - bool antialiasing = true) const; + void applyFillZonesInPlace(QImage& img, const ZoneSet& zones, bool antialiasing = true) const; - void applyFillZonesInPlace(QImage& img, const ZoneSet& zones, bool antialiasing = true) const; + void applyFillZonesInPlace(imageproc::BinaryImage& img, + const ZoneSet& zones, + const boost::function& orig_to_output, + const QTransform& postTransform) const; - void applyFillZonesInPlace(imageproc::BinaryImage& img, - const ZoneSet& zones, - const boost::function& orig_to_output, - const QTransform& postTransform) const; + void applyFillZonesInPlace(imageproc::BinaryImage& img, + const ZoneSet& zones, + const boost::function& orig_to_output) const; - void applyFillZonesInPlace(imageproc::BinaryImage& img, - const ZoneSet& zones, - const boost::function& orig_to_output) const; + void applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones, const QTransform& postTransform) const; - void applyFillZonesInPlace(imageproc::BinaryImage& img, - const ZoneSet& zones, - const QTransform& postTransform) const; + void applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones) const; - void applyFillZonesInPlace(imageproc::BinaryImage& img, const ZoneSet& zones) const; + void applyFillZonesToMixedInPlace(QImage& img, + const ZoneSet& zones, + const imageproc::BinaryImage& picture_mask, + bool binary_mode) const; - void applyFillZonesToMixedInPlace(QImage& img, - const ZoneSet& zones, - const imageproc::BinaryImage& picture_mask, - bool binary_mode) const; + void applyFillZonesToMixedInPlace(QImage& img, + const ZoneSet& zones, + const boost::function& orig_to_output, + const QTransform& postTransform, + const imageproc::BinaryImage& picture_mask, + bool binary_mode) const; - void applyFillZonesToMixedInPlace(QImage& img, - const ZoneSet& zones, - const boost::function& orig_to_output, - const QTransform& postTransform, - const imageproc::BinaryImage& picture_mask, - bool binary_mode) const; - - QImage segmentImage(const BinaryImage& image, const QImage& color_image) const; + QImage segmentImage(const BinaryImage& image, const QImage& color_image) const; - QImage posterizeImage(const QImage& image, const QColor& background_color = Qt::white) const; + QImage posterizeImage(const QImage& image, const QColor& background_color = Qt::white) const; - Dpi m_dpi; - ColorParams m_colorParams; - SplittingOptions m_splittingOptions; - PictureShapeOptions m_pictureShapeOptions; - DewarpingOptions m_dewarpingOptions; - OutputProcessingParams m_outputProcessingParams; + Dpi m_dpi; + ColorParams m_colorParams; + SplittingOptions m_splittingOptions; + PictureShapeOptions m_pictureShapeOptions; + DewarpingOptions m_dewarpingOptions; + OutputProcessingParams m_outputProcessingParams; - /** - * Transformation from the input to the output image coordinates. - */ - ImageTransformation m_xform; + /** + * Transformation from the input to the output image coordinates. + */ + ImageTransformation m_xform; - /** - * The rectangle corresponding to the output image. - * The top-left corner will always be at (0, 0). - */ - QRect m_outRect; + /** + * The rectangle corresponding to the output image. + * The top-left corner will always be at (0, 0). + */ + QRect m_outRect; - /** - * The content rectangle in output image coordinates. - */ - QRect m_contentRect; + /** + * The content rectangle in output image coordinates. + */ + QRect m_contentRect; - DespeckleLevel m_despeckleLevel; + double m_despeckleLevel; - // store additional transformations after processing such as post deskew after dewarping - QTransform postTransform; + /** Store additional transformations after processing such as post deskew after dewarping.*/ + QTransform m_postTransform; }; } // namespace output #endif // ifndef OUTPUT_OUTPUTGENERATOR_H_ diff --git a/filters/output/OutputImageParams.cpp b/filters/output/OutputImageParams.cpp index dcb551687..c26b22422 100644 --- a/filters/output/OutputImageParams.cpp +++ b/filters/output/OutputImageParams.cpp @@ -16,11 +16,11 @@ along with this program. If not, see . */ -#include #include "OutputImageParams.h" +#include +#include "../../Utils.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" -#include "../../Utils.h" namespace output { OutputImageParams::OutputImageParams(const QSize& out_image_size, @@ -32,212 +32,219 @@ OutputImageParams::OutputImageParams(const QSize& out_image_size, const DewarpingOptions& dewarping_options, const dewarping::DistortionModel& distortion_model, const DepthPerception& depth_perception, - DespeckleLevel despeckle_level, + const double despeckle_level, const PictureShapeOptions& picture_shape_options, - const OutputProcessingParams& output_processing_params) - : m_size(out_image_size), - m_contentRect(content_rect), - m_cropArea(xform.resultingPreCropArea()), - m_dpi(dpi), - m_colorParams(color_params), - m_splittingOptions(splitting_options), - m_pictureShapeOptions(picture_shape_options), - m_distortionModel(distortion_model), - m_depthPerception(depth_perception), - m_dewarpingOptions(dewarping_options), - m_despeckleLevel(despeckle_level), - m_outputProcessingParams(output_processing_params) { - // For historical reasons, we disregard post-cropping and post-scaling here. - xform.setPostCropArea(QPolygonF()); // Resets post-scale as well. - m_partialXform = xform.transform(); + const OutputProcessingParams& output_processing_params, + bool is_black_on_white) + : m_size(out_image_size), + m_contentRect(content_rect), + m_cropArea(xform.resultingPreCropArea()), + m_dpi(dpi), + m_colorParams(color_params), + m_splittingOptions(splitting_options), + m_pictureShapeOptions(picture_shape_options), + m_distortionModel(distortion_model), + m_depthPerception(depth_perception), + m_dewarpingOptions(dewarping_options), + m_despeckleLevel(despeckle_level), + m_outputProcessingParams(output_processing_params), + m_blackOnWhite(is_black_on_white) { + // For historical reasons, we disregard post-cropping and post-scaling here. + xform.setPostCropArea(QPolygonF()); // Resets post-scale as well. + m_partialXform = xform.transform(); } OutputImageParams::OutputImageParams(const QDomElement& el) - : m_size(XmlUnmarshaller::size(el.namedItem("size").toElement())), - m_contentRect(XmlUnmarshaller::rect(el.namedItem("content-rect").toElement())), - m_cropArea(XmlUnmarshaller::polygonF(el.namedItem("crop-area").toElement())), - m_partialXform(el.namedItem("partial-xform").toElement()), - m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), - m_colorParams(el.namedItem("color-params").toElement()), - m_splittingOptions(el.namedItem("splitting").toElement()), - m_pictureShapeOptions(el.namedItem("picture-shape-options").toElement()), - m_distortionModel(el.namedItem("distortion-model").toElement()), - m_depthPerception(el.attribute("depthPerception")), - m_dewarpingOptions(el.namedItem("dewarping-options").toElement()), - m_despeckleLevel(despeckleLevelFromString(el.attribute("despeckleLevel"))), - m_outputProcessingParams(el.namedItem("processing-params").toElement()) { -} + : m_size(XmlUnmarshaller::size(el.namedItem("size").toElement())), + m_contentRect(XmlUnmarshaller::rect(el.namedItem("content-rect").toElement())), + m_cropArea(XmlUnmarshaller::polygonF(el.namedItem("crop-area").toElement())), + m_partialXform(el.namedItem("partial-xform").toElement()), + m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), + m_colorParams(el.namedItem("color-params").toElement()), + m_splittingOptions(el.namedItem("splitting").toElement()), + m_pictureShapeOptions(el.namedItem("picture-shape-options").toElement()), + m_distortionModel(el.namedItem("distortion-model").toElement()), + m_depthPerception(el.attribute("depthPerception")), + m_dewarpingOptions(el.namedItem("dewarping-options").toElement()), + m_despeckleLevel(el.attribute("despeckleLevel").toDouble()), + m_outputProcessingParams(el.namedItem("processing-params").toElement()), + m_blackOnWhite(el.attribute("blackOnWhite") == "1") {} QDomElement OutputImageParams::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); - - QDomElement el(doc.createElement(name)); - el.appendChild(marshaller.size(m_size, "size")); - el.appendChild(marshaller.rect(m_contentRect, "content-rect")); - el.appendChild(marshaller.polygonF(m_cropArea, "crop-area")); - el.appendChild(m_partialXform.toXml(doc, "partial-xform")); - el.appendChild(marshaller.dpi(m_dpi, "dpi")); - el.appendChild(m_colorParams.toXml(doc, "color-params")); - el.appendChild(m_splittingOptions.toXml(doc, "splitting")); - el.appendChild(m_pictureShapeOptions.toXml(doc, "picture-shape-options")); - el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); - el.setAttribute("depthPerception", m_depthPerception.toString()); - el.appendChild(m_dewarpingOptions.toXml(doc, "dewarping-options")); - el.setAttribute("despeckleLevel", despeckleLevelToString(m_despeckleLevel)); - el.appendChild(m_outputProcessingParams.toXml(doc, "processing-params")); - - return el; + XmlMarshaller marshaller(doc); + + QDomElement el(doc.createElement(name)); + el.appendChild(marshaller.size(m_size, "size")); + el.appendChild(marshaller.rect(m_contentRect, "content-rect")); + el.appendChild(marshaller.polygonF(m_cropArea, "crop-area")); + el.appendChild(m_partialXform.toXml(doc, "partial-xform")); + el.appendChild(marshaller.dpi(m_dpi, "dpi")); + el.appendChild(m_colorParams.toXml(doc, "color-params")); + el.appendChild(m_splittingOptions.toXml(doc, "splitting")); + el.appendChild(m_pictureShapeOptions.toXml(doc, "picture-shape-options")); + el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); + el.setAttribute("depthPerception", m_depthPerception.toString()); + el.appendChild(m_dewarpingOptions.toXml(doc, "dewarping-options")); + el.setAttribute("despeckleLevel", Utils::doubleToString(m_despeckleLevel)); + el.appendChild(m_outputProcessingParams.toXml(doc, "processing-params")); + el.setAttribute("blackOnWhite", m_blackOnWhite ? "1" : "0"); + + return el; } bool OutputImageParams::matches(const OutputImageParams& other) const { - if (m_size != other.m_size) { - return false; - } - - if (m_contentRect != other.m_contentRect) { - return false; - } - - if (m_cropArea != other.m_cropArea) { - return false; - } - - if (!m_partialXform.matches(other.m_partialXform)) { - return false; - } - - if (m_dpi != other.m_dpi) { - return false; - } - - if (!colorParamsMatch(m_colorParams, m_despeckleLevel, m_splittingOptions, other.m_colorParams, - other.m_despeckleLevel, other.m_splittingOptions)) { - return false; + if (m_size != other.m_size) { + return false; + } + + if (m_contentRect != other.m_contentRect) { + return false; + } + + if (m_cropArea != other.m_cropArea) { + return false; + } + + if (!m_partialXform.matches(other.m_partialXform)) { + return false; + } + + if (m_dpi != other.m_dpi) { + return false; + } + + if (!colorParamsMatch(m_colorParams, m_despeckleLevel, m_splittingOptions, other.m_colorParams, + other.m_despeckleLevel, other.m_splittingOptions)) { + return false; + } + + if (m_pictureShapeOptions != other.m_pictureShapeOptions) { + return false; + } + + if (m_dewarpingOptions != other.m_dewarpingOptions) { + return false; + } else if (m_dewarpingOptions.dewarpingMode() != OFF) { + if (!m_distortionModel.matches(other.m_distortionModel)) { + return false; } - - if (m_pictureShapeOptions != other.m_pictureShapeOptions) { - return false; + if (m_depthPerception.value() != other.m_depthPerception.value()) { + return false; } + } - if (m_dewarpingOptions != other.m_dewarpingOptions) { - return false; - } else if (m_dewarpingOptions.dewarpingMode() != OFF) { - if (!m_distortionModel.matches(other.m_distortionModel)) { - return false; - } - if (m_depthPerception.value() != other.m_depthPerception.value()) { - return false; - } - } + if (m_outputProcessingParams != other.m_outputProcessingParams) { + return false; + } - if (m_outputProcessingParams != other.m_outputProcessingParams) { - return false; - } + if (m_blackOnWhite != other.m_blackOnWhite) { + return false; + } - return true; + return true; } // OutputImageParams::matches bool OutputImageParams::colorParamsMatch(const ColorParams& cp1, - const DespeckleLevel dl1, + const double dl1, const SplittingOptions& so1, const ColorParams& cp2, - const DespeckleLevel dl2, + const double dl2, const SplittingOptions& so2) { - if (cp1.colorMode() != cp2.colorMode()) { - return false; - } + if (cp1.colorMode() != cp2.colorMode()) { + return false; + } - switch (cp1.colorMode()) { - case MIXED: - if (so1 != so2) { - return false; - } - // fall into - case BLACK_AND_WHITE: - if (cp1.blackWhiteOptions() != cp2.blackWhiteOptions()) { - return false; - } - if (dl1 != dl2) { - return false; - } - // fall into - case COLOR_GRAYSCALE: - if (cp1.colorCommonOptions() != cp2.colorCommonOptions()) { - return false; - } - break; - } + switch (cp1.colorMode()) { + case MIXED: + if (so1 != so2) { + return false; + } + // fall into + case BLACK_AND_WHITE: + if (cp1.blackWhiteOptions() != cp2.blackWhiteOptions()) { + return false; + } + if (dl1 != dl2) { + return false; + } + // fall into + case COLOR_GRAYSCALE: + if (cp1.colorCommonOptions() != cp2.colorCommonOptions()) { + return false; + } + break; + } - return true; + return true; } void OutputImageParams::setOutputProcessingParams(const OutputProcessingParams& outputProcessingParams) { - OutputImageParams::m_outputProcessingParams = outputProcessingParams; + OutputImageParams::m_outputProcessingParams = outputProcessingParams; } const PictureShapeOptions& OutputImageParams::getPictureShapeOptions() const { - return m_pictureShapeOptions; + return m_pictureShapeOptions; } const QPolygonF& OutputImageParams::getCropArea() const { - return m_cropArea; + return m_cropArea; } const DewarpingOptions& OutputImageParams::dewarpingMode() const { - return m_dewarpingOptions; + return m_dewarpingOptions; } const dewarping::DistortionModel& OutputImageParams::distortionModel() const { - return m_distortionModel; + return m_distortionModel; } void OutputImageParams::setDistortionModel(const dewarping::DistortionModel& model) { - m_distortionModel = model; + m_distortionModel = model; } const DepthPerception& OutputImageParams::depthPerception() const { - return m_depthPerception; + return m_depthPerception; } -DespeckleLevel OutputImageParams::despeckleLevel() const { - return m_despeckleLevel; +double OutputImageParams::despeckleLevel() const { + return m_despeckleLevel; } +void OutputImageParams::setBlackOnWhite(bool blackOnWhite) { + m_blackOnWhite = blackOnWhite; +} /*=============================== PartialXform =============================*/ -OutputImageParams::PartialXform::PartialXform() : m_11(), m_12(), m_21(), m_22() { -} +OutputImageParams::PartialXform::PartialXform() : m_11(), m_12(), m_21(), m_22() {} OutputImageParams::PartialXform::PartialXform(const QTransform& xform) - : m_11(xform.m11()), m_12(xform.m12()), m_21(xform.m21()), m_22(xform.m22()) { -} + : m_11(xform.m11()), m_12(xform.m12()), m_21(xform.m21()), m_22(xform.m22()) {} OutputImageParams::PartialXform::PartialXform(const QDomElement& el) - : m_11(el.namedItem("m11").toElement().text().toDouble()), - m_12(el.namedItem("m12").toElement().text().toDouble()), - m_21(el.namedItem("m21").toElement().text().toDouble()), - m_22(el.namedItem("m22").toElement().text().toDouble()) { -} + : m_11(el.namedItem("m11").toElement().text().toDouble()), + m_12(el.namedItem("m12").toElement().text().toDouble()), + m_21(el.namedItem("m21").toElement().text().toDouble()), + m_22(el.namedItem("m22").toElement().text().toDouble()) {} QDomElement OutputImageParams::PartialXform::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); + XmlMarshaller marshaller(doc); - QDomElement el(doc.createElement(name)); - el.appendChild(marshaller.string(Utils::doubleToString(m_11), "m11")); - el.appendChild(marshaller.string(Utils::doubleToString(m_12), "m12")); - el.appendChild(marshaller.string(Utils::doubleToString(m_21), "m21")); - el.appendChild(marshaller.string(Utils::doubleToString(m_22), "m22")); + QDomElement el(doc.createElement(name)); + el.appendChild(marshaller.string(Utils::doubleToString(m_11), "m11")); + el.appendChild(marshaller.string(Utils::doubleToString(m_12), "m12")); + el.appendChild(marshaller.string(Utils::doubleToString(m_21), "m21")); + el.appendChild(marshaller.string(Utils::doubleToString(m_22), "m22")); - return el; + return el; } bool OutputImageParams::PartialXform::matches(const PartialXform& other) const { - return closeEnough(m_11, other.m_11) && closeEnough(m_12, other.m_12) && closeEnough(m_21, other.m_21) - && closeEnough(m_22, other.m_22); + return closeEnough(m_11, other.m_11) && closeEnough(m_12, other.m_12) && closeEnough(m_21, other.m_21) + && closeEnough(m_22, other.m_22); } bool OutputImageParams::PartialXform::closeEnough(double v1, double v2) { - return std::fabs(v1 - v2) < 0.0001; + return std::fabs(v1 - v2) < 0.0001; } } // namespace output \ No newline at end of file diff --git a/filters/output/OutputImageParams.h b/filters/output/OutputImageParams.h index a32d01c7c..37f4cbc02 100644 --- a/filters/output/OutputImageParams.h +++ b/filters/output/OutputImageParams.h @@ -19,17 +19,17 @@ #ifndef OUTPUT_OUTPUT_IMAGE_PARAMS_H_ #define OUTPUT_OUTPUT_IMAGE_PARAMS_H_ -#include "Dpi.h" +#include +#include +#include #include "ColorParams.h" -#include "Params.h" -#include "DewarpingOptions.h" -#include "dewarping/DistortionModel.h" #include "DepthPerception.h" #include "DespeckleLevel.h" +#include "DewarpingOptions.h" +#include "Dpi.h" #include "OutputProcessingParams.h" -#include -#include -#include +#include "Params.h" +#include "dewarping/DistortionModel.h" class ImageTransformation; class QDomDocument; @@ -41,117 +41,123 @@ namespace output { * \brief Parameters of the output image used to determine if we need to re-generate it. */ class OutputImageParams { -public: - OutputImageParams(const QSize& out_image_size, - const QRect& content_rect, - ImageTransformation xform, - const Dpi& dpi, - const ColorParams& color_params, - const SplittingOptions& splittingOptions, - const DewarpingOptions& dewarping_options, - const dewarping::DistortionModel& distortion_model, - const DepthPerception& depth_perception, - DespeckleLevel despeckle_level, - const PictureShapeOptions& picture_shape_options, - const OutputProcessingParams& output_processing_params); + public: + OutputImageParams(const QSize& out_image_size, + const QRect& content_rect, + ImageTransformation xform, + const Dpi& dpi, + const ColorParams& color_params, + const SplittingOptions& splittingOptions, + const DewarpingOptions& dewarping_options, + const dewarping::DistortionModel& distortion_model, + const DepthPerception& depth_perception, + double despeckle_level, + const PictureShapeOptions& picture_shape_options, + const OutputProcessingParams& output_processing_params, + bool is_black_on_white); - explicit OutputImageParams(const QDomElement& el); + explicit OutputImageParams(const QDomElement& el); - const DewarpingOptions& dewarpingMode() const; + const DewarpingOptions& dewarpingMode() const; - const dewarping::DistortionModel& distortionModel() const; + const dewarping::DistortionModel& distortionModel() const; - void setDistortionModel(const dewarping::DistortionModel& model); + void setDistortionModel(const dewarping::DistortionModel& model); - const DepthPerception& depthPerception() const; + const DepthPerception& depthPerception() const; - DespeckleLevel despeckleLevel() const; + double despeckleLevel() const; - void setOutputProcessingParams(const OutputProcessingParams& outputProcessingParams); + void setOutputProcessingParams(const OutputProcessingParams& outputProcessingParams); - const PictureShapeOptions& getPictureShapeOptions() const; + const PictureShapeOptions& getPictureShapeOptions() const; - const QPolygonF& getCropArea() const; + const QPolygonF& getCropArea() const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + void setBlackOnWhite(bool blackOnWhite); + + QDomElement toXml(QDomDocument& doc, const QString& name) const; - /** - * \brief Returns true if two sets of parameters are close enough - * to avoid re-generating the output image. - */ - bool matches(const OutputImageParams& other) const; + /** + * \brief Returns true if two sets of parameters are close enough + * to avoid re-generating the output image. + */ + bool matches(const OutputImageParams& other) const; -private: - class PartialXform { - public: - PartialXform(); + private: + class PartialXform { + public: + PartialXform(); - PartialXform(const QTransform& xform); + PartialXform(const QTransform& xform); - explicit PartialXform(const QDomElement& el); + explicit PartialXform(const QDomElement& el); + + QDomElement toXml(QDomDocument& doc, const QString& name) const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + bool matches(const PartialXform& other) const; - bool matches(const PartialXform& other) const; + private: + static bool closeEnough(double v1, double v2); - private: - static bool closeEnough(double v1, double v2); + double m_11; + double m_12; + double m_21; + double m_22; + }; - double m_11; - double m_12; - double m_21; - double m_22; - }; + static bool colorParamsMatch(const ColorParams& cp1, + double dl1, + const SplittingOptions& so1, + const ColorParams& cp2, + double dl2, + const SplittingOptions& so2); - static bool colorParamsMatch(const ColorParams& cp1, - DespeckleLevel dl1, - const SplittingOptions& so1, - const ColorParams& cp2, - DespeckleLevel dl2, - const SplittingOptions& so2); + /** Pixel size of the output image. */ + QSize m_size; - /** Pixel size of the output image. */ - QSize m_size; + /** Content rectangle in output image coordinates. */ + QRect m_contentRect; - /** Content rectangle in output image coordinates. */ - QRect m_contentRect; + /** Crop area in output image coordinates. */ + QPolygonF m_cropArea; - /** Crop area in output image coordinates. */ - QPolygonF m_cropArea; + /** + * Some parameters from the transformation matrix that maps + * source image coordinates to unscaled (disregarding dpi changes) + * output image coordinates. + */ + PartialXform m_partialXform; - /** - * Some parameters from the transformation matrix that maps - * source image coordinates to unscaled (disregarding dpi changes) - * output image coordinates. - */ - PartialXform m_partialXform; + /** DPI of the output image. */ + Dpi m_dpi; - /** DPI of the output image. */ - Dpi m_dpi; + /** Non-geometric parameters used to generate the output image. */ + ColorParams m_colorParams; - /** Non-geometric parameters used to generate the output image. */ - ColorParams m_colorParams; + /** Parameters used to generate the split output images. */ + SplittingOptions m_splittingOptions; - /** Parameters used to generate the split output images. */ - SplittingOptions m_splittingOptions; + /** Shape of the pictures in image */ + PictureShapeOptions m_pictureShapeOptions; - /** Shape of the pictures in image */ - PictureShapeOptions m_pictureShapeOptions; + /** Two curves and two lines connecting their endpoints. Used for dewarping. */ + dewarping::DistortionModel m_distortionModel; - /** Two curves and two lines connecting their endpoints. Used for dewarping. */ - dewarping::DistortionModel m_distortionModel; + /** \see imageproc::CylindricalSurfaceDewarper */ + DepthPerception m_depthPerception; - /** \see imageproc::CylindricalSurfaceDewarper */ - DepthPerception m_depthPerception; + /** Dewarping mode (Off / Auto / Manual) and options */ + DewarpingOptions m_dewarpingOptions; - /** Dewarping mode (Off / Auto / Manual) and options */ - DewarpingOptions m_dewarpingOptions; + /** Despeckle level of the output image. */ + double m_despeckleLevel; - /** Despeckle level of the output image. */ - DespeckleLevel m_despeckleLevel; + /** Per-page params set while processing. */ + OutputProcessingParams m_outputProcessingParams; - /** Per-page params set while processing. */ - OutputProcessingParams m_outputProcessingParams; + /** Whether the page has dark content on light background */ + bool m_blackOnWhite; }; } // namespace output #endif // ifndef OUTPUT_OUTPUT_IMAGE_PARAMS_H_ diff --git a/filters/output/OutputMargins.h b/filters/output/OutputMargins.h index 3bf87fe24..aecd63ae5 100644 --- a/filters/output/OutputMargins.h +++ b/filters/output/OutputMargins.h @@ -29,9 +29,8 @@ namespace output { * one-to-one relationship. */ class OutputMargins : public Margins { -public: - OutputMargins() : Margins(10.0, 10.0, 10.0, 10.0) { - } + public: + OutputMargins() : Margins(10.0, 10.0, 10.0, 10.0) {} }; } // namespace output #endif diff --git a/filters/output/OutputParams.cpp b/filters/output/OutputParams.cpp index 1431c9f9b..813d472b8 100644 --- a/filters/output/OutputParams.cpp +++ b/filters/output/OutputParams.cpp @@ -17,9 +17,9 @@ */ #include "OutputParams.h" -#include "PictureZonePropFactory.h" -#include "FillZonePropFactory.h" #include +#include "FillZonePropFactory.h" +#include "PictureZonePropFactory.h" namespace output { OutputParams::OutputParams(const OutputImageParams& output_image_params, @@ -31,77 +31,75 @@ OutputParams::OutputParams(const OutputImageParams& output_image_params, const OutputFileParams& speckles_file_params, const ZoneSet& picture_zones, const ZoneSet& fill_zones) - : m_outputImageParams(output_image_params), - m_outputFileParams(output_file_params), - m_foregroundFileParams(foreground_file_params), - m_backgroundFileParams(background_file_params), - m_originalBackgroundFileParams(original_background_file_params), - m_automaskFileParams(automask_file_params), - m_specklesFileParams(speckles_file_params), - m_pictureZones(picture_zones), - m_fillZones(fill_zones) { -} + : m_outputImageParams(output_image_params), + m_outputFileParams(output_file_params), + m_foregroundFileParams(foreground_file_params), + m_backgroundFileParams(background_file_params), + m_originalBackgroundFileParams(original_background_file_params), + m_automaskFileParams(automask_file_params), + m_specklesFileParams(speckles_file_params), + m_pictureZones(picture_zones), + m_fillZones(fill_zones) {} OutputParams::OutputParams(const QDomElement& el) - : m_outputImageParams(el.namedItem("image").toElement()), - m_outputFileParams(el.namedItem("file").toElement()), - m_foregroundFileParams(el.namedItem("foreground_file").toElement()), - m_backgroundFileParams(el.namedItem("background_file").toElement()), - m_originalBackgroundFileParams(el.namedItem("original_background_file").toElement()), - m_automaskFileParams(el.namedItem("automask").toElement()), - m_specklesFileParams(el.namedItem("speckles").toElement()), - m_pictureZones(el.namedItem("zones").toElement(), PictureZonePropFactory()), - m_fillZones(el.namedItem("fill-zones").toElement(), FillZonePropFactory()) { -} + : m_outputImageParams(el.namedItem("image").toElement()), + m_outputFileParams(el.namedItem("file").toElement()), + m_foregroundFileParams(el.namedItem("foreground_file").toElement()), + m_backgroundFileParams(el.namedItem("background_file").toElement()), + m_originalBackgroundFileParams(el.namedItem("original_background_file").toElement()), + m_automaskFileParams(el.namedItem("automask").toElement()), + m_specklesFileParams(el.namedItem("speckles").toElement()), + m_pictureZones(el.namedItem("zones").toElement(), PictureZonePropFactory()), + m_fillZones(el.namedItem("fill-zones").toElement(), FillZonePropFactory()) {} QDomElement OutputParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(m_outputImageParams.toXml(doc, "image")); - el.appendChild(m_outputFileParams.toXml(doc, "file")); - el.appendChild(m_foregroundFileParams.toXml(doc, "foreground_file")); - el.appendChild(m_backgroundFileParams.toXml(doc, "background_file")); - el.appendChild(m_originalBackgroundFileParams.toXml(doc, "original_background_file")); - el.appendChild(m_automaskFileParams.toXml(doc, "automask")); - el.appendChild(m_specklesFileParams.toXml(doc, "speckles")); - el.appendChild(m_pictureZones.toXml(doc, "zones")); - el.appendChild(m_fillZones.toXml(doc, "fill-zones")); - - return el; + QDomElement el(doc.createElement(name)); + el.appendChild(m_outputImageParams.toXml(doc, "image")); + el.appendChild(m_outputFileParams.toXml(doc, "file")); + el.appendChild(m_foregroundFileParams.toXml(doc, "foreground_file")); + el.appendChild(m_backgroundFileParams.toXml(doc, "background_file")); + el.appendChild(m_originalBackgroundFileParams.toXml(doc, "original_background_file")); + el.appendChild(m_automaskFileParams.toXml(doc, "automask")); + el.appendChild(m_specklesFileParams.toXml(doc, "speckles")); + el.appendChild(m_pictureZones.toXml(doc, "zones")); + el.appendChild(m_fillZones.toXml(doc, "fill-zones")); + + return el; } const OutputImageParams& OutputParams::outputImageParams() const { - return m_outputImageParams; + return m_outputImageParams; } const OutputFileParams& OutputParams::outputFileParams() const { - return m_outputFileParams; + return m_outputFileParams; } const OutputFileParams& OutputParams::foregroundFileParams() const { - return m_foregroundFileParams; + return m_foregroundFileParams; } const OutputFileParams& OutputParams::backgroundFileParams() const { - return m_backgroundFileParams; + return m_backgroundFileParams; } const OutputFileParams& OutputParams::originalBackgroundFileParams() const { - return m_originalBackgroundFileParams; + return m_originalBackgroundFileParams; } const OutputFileParams& OutputParams::automaskFileParams() const { - return m_automaskFileParams; + return m_automaskFileParams; } const OutputFileParams& OutputParams::specklesFileParams() const { - return m_specklesFileParams; + return m_specklesFileParams; } const ZoneSet& OutputParams::pictureZones() const { - return m_pictureZones; + return m_pictureZones; } const ZoneSet& OutputParams::fillZones() const { - return m_fillZones; + return m_fillZones; } } // namespace output \ No newline at end of file diff --git a/filters/output/OutputParams.h b/filters/output/OutputParams.h index 3af4a5bf1..153e4b3ec 100644 --- a/filters/output/OutputParams.h +++ b/filters/output/OutputParams.h @@ -19,8 +19,8 @@ #ifndef OUTPUT_OUTPUT_PARAMS_H_ #define OUTPUT_OUTPUT_PARAMS_H_ -#include "OutputImageParams.h" #include "OutputFileParams.h" +#include "OutputImageParams.h" #include "ZoneSet.h" class QDomDocument; @@ -29,49 +29,49 @@ class QString; namespace output { class OutputParams { -public: - OutputParams(const OutputImageParams& output_image_params, - const OutputFileParams& output_file_params, - const OutputFileParams& foreground_file_params, - const OutputFileParams& background_file_params, - const OutputFileParams& original_background_file_params, - const OutputFileParams& automask_file_params, - const OutputFileParams& speckles_file_params, - const ZoneSet& picture_zones, - const ZoneSet& fill_zones); + public: + OutputParams(const OutputImageParams& output_image_params, + const OutputFileParams& output_file_params, + const OutputFileParams& foreground_file_params, + const OutputFileParams& background_file_params, + const OutputFileParams& original_background_file_params, + const OutputFileParams& automask_file_params, + const OutputFileParams& speckles_file_params, + const ZoneSet& picture_zones, + const ZoneSet& fill_zones); - explicit OutputParams(const QDomElement& el); + explicit OutputParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - const OutputImageParams& outputImageParams() const; + const OutputImageParams& outputImageParams() const; - const OutputFileParams& outputFileParams() const; + const OutputFileParams& outputFileParams() const; - const OutputFileParams& foregroundFileParams() const; + const OutputFileParams& foregroundFileParams() const; - const OutputFileParams& backgroundFileParams() const; + const OutputFileParams& backgroundFileParams() const; - const OutputFileParams& originalBackgroundFileParams() const; + const OutputFileParams& originalBackgroundFileParams() const; - const OutputFileParams& automaskFileParams() const; + const OutputFileParams& automaskFileParams() const; - const OutputFileParams& specklesFileParams() const; + const OutputFileParams& specklesFileParams() const; - const ZoneSet& pictureZones() const; + const ZoneSet& pictureZones() const; - const ZoneSet& fillZones() const; + const ZoneSet& fillZones() const; -private: - OutputImageParams m_outputImageParams; - OutputFileParams m_outputFileParams; - OutputFileParams m_foregroundFileParams; - OutputFileParams m_backgroundFileParams; - OutputFileParams m_originalBackgroundFileParams; - OutputFileParams m_automaskFileParams; - OutputFileParams m_specklesFileParams; - ZoneSet m_pictureZones; - ZoneSet m_fillZones; + private: + OutputImageParams m_outputImageParams; + OutputFileParams m_outputFileParams; + OutputFileParams m_foregroundFileParams; + OutputFileParams m_backgroundFileParams; + OutputFileParams m_originalBackgroundFileParams; + OutputFileParams m_automaskFileParams; + OutputFileParams m_specklesFileParams; + ZoneSet m_pictureZones; + ZoneSet m_fillZones; }; } // namespace output #endif // ifndef OUTPUT_OUTPUT_PARAMS_H_ diff --git a/filters/output/OutputProcessingParams.cpp b/filters/output/OutputProcessingParams.cpp index 507ba0316..b3d56bfae 100644 --- a/filters/output/OutputProcessingParams.cpp +++ b/filters/output/OutputProcessingParams.cpp @@ -1,37 +1,45 @@ -#include "BlackWhiteOptions.h" #include "OutputProcessingParams.h" #include +#include "BlackWhiteOptions.h" namespace output { -OutputProcessingParams::OutputProcessingParams() : autoZonesFound(false) { -} +OutputProcessingParams::OutputProcessingParams() : m_autoZonesFound(false), m_blackOnWhiteSetManually(false) {} OutputProcessingParams::OutputProcessingParams(const QDomElement& el) - : autoZonesFound(el.attribute("autoZonesFound") == "1") { -} + : m_autoZonesFound(el.attribute("autoZonesFound") == "1"), + m_blackOnWhiteSetManually(el.attribute("blackOnWhiteSetManually") == "1") {} QDomElement OutputProcessingParams::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("autoZonesFound", autoZonesFound ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.setAttribute("autoZonesFound", m_autoZonesFound ? "1" : "0"); + el.setAttribute("blackOnWhiteSetManually", m_blackOnWhiteSetManually ? "1" : "0"); - return el; + return el; } bool OutputProcessingParams::operator==(const OutputProcessingParams& other) const { - return (autoZonesFound == other.autoZonesFound); + return (m_autoZonesFound == other.m_autoZonesFound) && (m_blackOnWhiteSetManually == other.m_blackOnWhiteSetManually); } bool OutputProcessingParams::operator!=(const OutputProcessingParams& other) const { - return !(*this == other); + return !(*this == other); } bool output::OutputProcessingParams::isAutoZonesFound() const { - return autoZonesFound; + return m_autoZonesFound; } void output::OutputProcessingParams::setAutoZonesFound(bool autoZonesFound) { - OutputProcessingParams::autoZonesFound = autoZonesFound; + OutputProcessingParams::m_autoZonesFound = autoZonesFound; +} + +bool OutputProcessingParams::isBlackOnWhiteSetManually() const { + return m_blackOnWhiteSetManually; +} + +void OutputProcessingParams::setBlackOnWhiteSetManually(bool blackOnWhiteSetManually) { + OutputProcessingParams::m_blackOnWhiteSetManually = blackOnWhiteSetManually; } } // namespace output \ No newline at end of file diff --git a/filters/output/OutputProcessingParams.h b/filters/output/OutputProcessingParams.h index 15acaddf3..5e6e11a9b 100644 --- a/filters/output/OutputProcessingParams.h +++ b/filters/output/OutputProcessingParams.h @@ -8,23 +8,28 @@ class QDomElement; namespace output { class OutputProcessingParams { -public: - OutputProcessingParams(); + public: + OutputProcessingParams(); - explicit OutputProcessingParams(const QDomElement& el); + explicit OutputProcessingParams(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const OutputProcessingParams& other) const; + bool operator==(const OutputProcessingParams& other) const; - bool operator!=(const OutputProcessingParams& other) const; + bool operator!=(const OutputProcessingParams& other) const; - bool isAutoZonesFound() const; + bool isAutoZonesFound() const; - void setAutoZonesFound(bool autoZonesFound); + void setAutoZonesFound(bool autoZonesFound); -private: - bool autoZonesFound; + bool isBlackOnWhiteSetManually() const; + + void setBlackOnWhiteSetManually(bool blackOnWhiteSetManually); + + private: + bool m_autoZonesFound; + bool m_blackOnWhiteSetManually; }; } // namespace output diff --git a/filters/output/Params.cpp b/filters/output/Params.cpp index 993c74928..273eef8c8 100644 --- a/filters/output/Params.cpp +++ b/filters/output/Params.cpp @@ -17,158 +17,128 @@ */ #include "Params.h" +#include "../../Utils.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" namespace output { -Params::Params() : m_dpi(600, 600), m_despeckleLevel(DESPECKLE_CAUTIOUS) { -} - -Params::Params(const Dpi& m_dpi, - const ColorParams& m_colorParams, - const SplittingOptions& m_splittingOptions, - const PictureShapeOptions& m_pictureShapeOptions, - const dewarping::DistortionModel& m_distortionModel, - const DepthPerception& m_depthPerception, - const DewarpingOptions& m_dewarpingOptions, - DespeckleLevel m_despeckleLevel) - : m_dpi(m_dpi), - m_colorParams(m_colorParams), - m_splittingOptions(m_splittingOptions), - m_pictureShapeOptions(m_pictureShapeOptions), - m_distortionModel(m_distortionModel), - m_depthPerception(m_depthPerception), - m_dewarpingOptions(m_dewarpingOptions), - m_despeckleLevel(m_despeckleLevel) { -} +Params::Params() : m_dpi(600, 600), m_despeckleLevel(1.0) {} + +Params::Params(const Dpi& dpi, + const ColorParams& colorParams, + const SplittingOptions& splittingOptions, + const PictureShapeOptions& pictureShapeOptions, + const dewarping::DistortionModel& distortionModel, + const DepthPerception& depthPerception, + const DewarpingOptions& dewarpingOptions, + const double despeckleLevel) + : m_dpi(dpi), + m_colorParams(colorParams), + m_splittingOptions(splittingOptions), + m_pictureShapeOptions(pictureShapeOptions), + m_distortionModel(distortionModel), + m_depthPerception(depthPerception), + m_dewarpingOptions(dewarpingOptions), + m_despeckleLevel(despeckleLevel), + m_blackOnWhite(true) {} Params::Params(const QDomElement& el) - : m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), - m_distortionModel(el.namedItem("distortion-model").toElement()), - m_depthPerception(el.attribute("depthPerception")), - m_dewarpingOptions(el.namedItem("dewarping-options").toElement()), - m_despeckleLevel(despeckleLevelFromString(el.attribute("despeckleLevel"))), - m_pictureShapeOptions(el.namedItem("picture-shape-options").toElement()), - m_splittingOptions(el.namedItem("splitting").toElement()) { - const QDomElement cp(el.namedItem("color-params").toElement()); - m_colorParams.setColorMode(parseColorMode(cp.attribute("colorMode"))); - m_colorParams.setColorCommonOptions(ColorCommonOptions(cp.namedItem("color-or-grayscale").toElement())); - m_colorParams.setBlackWhiteOptions(BlackWhiteOptions(cp.namedItem("bw").toElement())); -} + : m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), + m_colorParams(el.namedItem("color-params").toElement()), + m_distortionModel(el.namedItem("distortion-model").toElement()), + m_depthPerception(el.attribute("depthPerception")), + m_dewarpingOptions(el.namedItem("dewarping-options").toElement()), + m_despeckleLevel(el.attribute("despeckleLevel").toDouble()), + m_pictureShapeOptions(el.namedItem("picture-shape-options").toElement()), + m_splittingOptions(el.namedItem("splitting").toElement()), + m_blackOnWhite(el.attribute("blackOnWhite") == "1") {} QDomElement Params::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); - - QDomElement el(doc.createElement(name)); - el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); - el.appendChild(m_pictureShapeOptions.toXml(doc, "picture-shape-options")); - el.setAttribute("depthPerception", m_depthPerception.toString()); - el.appendChild(m_dewarpingOptions.toXml(doc, "dewarping-options")); - el.setAttribute("despeckleLevel", despeckleLevelToString(m_despeckleLevel)); - el.appendChild(marshaller.dpi(m_dpi, "dpi")); - el.appendChild(m_splittingOptions.toXml(doc, "splitting")); - - QDomElement cp(doc.createElement("color-params")); - cp.setAttribute("colorMode", formatColorMode(m_colorParams.colorMode())); - - cp.appendChild(m_colorParams.colorCommonOptions().toXml(doc, "color-or-grayscale")); - cp.appendChild(m_colorParams.blackWhiteOptions().toXml(doc, "bw")); - - el.appendChild(cp); - - return el; -} - -ColorMode Params::parseColorMode(const QString& str) { - if (str == "bw") { - return BLACK_AND_WHITE; - } else if (str == "colorOrGray") { - return COLOR_GRAYSCALE; - } else if (str == "mixed") { - return MIXED; - } else { - return BLACK_AND_WHITE; - } -} + XmlMarshaller marshaller(doc); -QString Params::formatColorMode(const ColorMode mode) { - const char* str = ""; - switch (mode) { - case BLACK_AND_WHITE: - str = "bw"; - break; - case COLOR_GRAYSCALE: - str = "colorOrGray"; - break; - case MIXED: - str = "mixed"; - break; - } + QDomElement el(doc.createElement(name)); + el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); + el.appendChild(m_pictureShapeOptions.toXml(doc, "picture-shape-options")); + el.setAttribute("depthPerception", m_depthPerception.toString()); + el.appendChild(m_dewarpingOptions.toXml(doc, "dewarping-options")); + el.setAttribute("despeckleLevel", Utils::doubleToString(m_despeckleLevel)); + el.appendChild(marshaller.dpi(m_dpi, "dpi")); + el.appendChild(m_colorParams.toXml(doc, "color-params")); + el.appendChild(m_splittingOptions.toXml(doc, "splitting")); + el.setAttribute("blackOnWhite", m_blackOnWhite ? "1" : "0"); - return QString::fromLatin1(str); + return el; } const Dpi& Params::outputDpi() const { - return m_dpi; + return m_dpi; } void Params::setOutputDpi(const Dpi& dpi) { - m_dpi = dpi; + m_dpi = dpi; } const ColorParams& Params::colorParams() const { - return m_colorParams; + return m_colorParams; } const PictureShapeOptions& Params::pictureShapeOptions() const { - return m_pictureShapeOptions; + return m_pictureShapeOptions; } void Params::setPictureShapeOptions(const PictureShapeOptions& opt) { - m_pictureShapeOptions = opt; + m_pictureShapeOptions = opt; } void Params::setColorParams(const ColorParams& params) { - m_colorParams = params; + m_colorParams = params; } const SplittingOptions& Params::splittingOptions() const { - return m_splittingOptions; + return m_splittingOptions; } void Params::setSplittingOptions(const SplittingOptions& opt) { - m_splittingOptions = opt; + m_splittingOptions = opt; } const DewarpingOptions& Params::dewarpingOptions() const { - return m_dewarpingOptions; + return m_dewarpingOptions; } void Params::setDewarpingOptions(const DewarpingOptions& opt) { - m_dewarpingOptions = opt; + m_dewarpingOptions = opt; } const dewarping::DistortionModel& Params::distortionModel() const { - return m_distortionModel; + return m_distortionModel; } void Params::setDistortionModel(const dewarping::DistortionModel& model) { - m_distortionModel = model; + m_distortionModel = model; } const DepthPerception& Params::depthPerception() const { - return m_depthPerception; + return m_depthPerception; } void Params::setDepthPerception(DepthPerception depth_perception) { - m_depthPerception = depth_perception; + m_depthPerception = depth_perception; +} + +double Params::despeckleLevel() const { + return m_despeckleLevel; +} + +void Params::setDespeckleLevel(double level) { + m_despeckleLevel = level; } -DespeckleLevel Params::despeckleLevel() const { - return m_despeckleLevel; +bool Params::isBlackOnWhite() const { + return m_blackOnWhite; } -void Params::setDespeckleLevel(DespeckleLevel level) { - m_despeckleLevel = level; +void Params::setBlackOnWhite(bool isBlackOnWhite) { + Params::m_blackOnWhite = isBlackOnWhite; } } // namespace output \ No newline at end of file diff --git a/filters/output/Params.h b/filters/output/Params.h index 5d45121ca..b06010870 100644 --- a/filters/output/Params.h +++ b/filters/output/Params.h @@ -19,80 +19,81 @@ #ifndef OUTPUT_PARAMS_H_ #define OUTPUT_PARAMS_H_ -#include "Dpi.h" #include "ColorParams.h" -#include "DewarpingOptions.h" -#include "dewarping/DistortionModel.h" #include "DepthPerception.h" #include "DespeckleLevel.h" +#include "DewarpingOptions.h" +#include "Dpi.h" #include "PictureShapeOptions.h" +#include "dewarping/DistortionModel.h" class QDomDocument; class QDomElement; namespace output { class Params { -public: - Params(); + public: + Params(); - Params(const Dpi& m_dpi, - const ColorParams& m_colorParams, - const SplittingOptions& m_splittingOptions, - const PictureShapeOptions& m_pictureShapeOptions, - const dewarping::DistortionModel& m_distortionModel, - const DepthPerception& m_depthPerception, - const DewarpingOptions& m_dewarpingOptions, - DespeckleLevel m_despeckleLevel); + Params(const Dpi& dpi, + const ColorParams& colorParams, + const SplittingOptions& splittingOptions, + const PictureShapeOptions& pictureShapeOptions, + const dewarping::DistortionModel& distortionModel, + const DepthPerception& depthPerception, + const DewarpingOptions& dewarpingOptions, + double despeckleLevel); - explicit Params(const QDomElement& el); + explicit Params(const QDomElement& el); - const Dpi& outputDpi() const; + const Dpi& outputDpi() const; - void setOutputDpi(const Dpi& dpi); + void setOutputDpi(const Dpi& dpi); - const ColorParams& colorParams() const; + const ColorParams& colorParams() const; - const PictureShapeOptions& pictureShapeOptions() const; + const PictureShapeOptions& pictureShapeOptions() const; - void setPictureShapeOptions(const PictureShapeOptions& opt); + void setPictureShapeOptions(const PictureShapeOptions& opt); - void setColorParams(const ColorParams& params); + void setColorParams(const ColorParams& params); - const SplittingOptions& splittingOptions() const; + const SplittingOptions& splittingOptions() const; - void setSplittingOptions(const SplittingOptions& opt); + void setSplittingOptions(const SplittingOptions& opt); - const DewarpingOptions& dewarpingOptions() const; + const DewarpingOptions& dewarpingOptions() const; - void setDewarpingOptions(const DewarpingOptions& opt); + void setDewarpingOptions(const DewarpingOptions& opt); - const dewarping::DistortionModel& distortionModel() const; + const dewarping::DistortionModel& distortionModel() const; - void setDistortionModel(const dewarping::DistortionModel& model); + void setDistortionModel(const dewarping::DistortionModel& model); - const DepthPerception& depthPerception() const; + const DepthPerception& depthPerception() const; - void setDepthPerception(DepthPerception depth_perception); + void setDepthPerception(DepthPerception depth_perception); - DespeckleLevel despeckleLevel() const; + double despeckleLevel() const; - void setDespeckleLevel(DespeckleLevel level); + void setDespeckleLevel(double level); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - static ColorMode parseColorMode(const QString& str); + bool isBlackOnWhite() const; - static QString formatColorMode(ColorMode mode); + void setBlackOnWhite(bool isBlackOnWhite); - Dpi m_dpi; - ColorParams m_colorParams; - SplittingOptions m_splittingOptions; - PictureShapeOptions m_pictureShapeOptions; - dewarping::DistortionModel m_distortionModel; - DepthPerception m_depthPerception; - DewarpingOptions m_dewarpingOptions; - DespeckleLevel m_despeckleLevel; + private: + Dpi m_dpi; + ColorParams m_colorParams; + SplittingOptions m_splittingOptions; + PictureShapeOptions m_pictureShapeOptions; + dewarping::DistortionModel m_distortionModel; + DepthPerception m_depthPerception; + DewarpingOptions m_dewarpingOptions; + double m_despeckleLevel; + bool m_blackOnWhite; }; } // namespace output #endif // ifndef OUTPUT_PARAMS_H_ diff --git a/filters/output/PictureLayerProperty.cpp b/filters/output/PictureLayerProperty.cpp index c3fd21cff..75b8e9504 100644 --- a/filters/output/PictureLayerProperty.cpp +++ b/filters/output/PictureLayerProperty.cpp @@ -17,76 +17,74 @@ */ #include "PictureLayerProperty.h" -#include "PropertyFactory.h" #include +#include "PropertyFactory.h" namespace output { const char PictureLayerProperty::m_propertyName[] = "PictureZoneProperty"; -PictureLayerProperty::PictureLayerProperty(const QDomElement& el) : m_layer(layerFromString(el.attribute("layer"))) { -} +PictureLayerProperty::PictureLayerProperty(const QDomElement& el) : m_layer(layerFromString(el.attribute("layer"))) {} void PictureLayerProperty::registerIn(PropertyFactory& factory) { - factory.registerProperty(m_propertyName, &PictureLayerProperty::construct); + factory.registerProperty(m_propertyName, &PictureLayerProperty::construct); } intrusive_ptr PictureLayerProperty::clone() const { - return make_intrusive(*this); + return make_intrusive(*this); } QDomElement PictureLayerProperty::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("type", m_propertyName); - el.setAttribute("layer", layerToString(m_layer)); + QDomElement el(doc.createElement(name)); + el.setAttribute("type", m_propertyName); + el.setAttribute("layer", layerToString(m_layer)); - return el; + return el; } intrusive_ptr PictureLayerProperty::construct(const QDomElement& el) { - return make_intrusive(el); + return make_intrusive(el); } PictureLayerProperty::Layer PictureLayerProperty::layerFromString(const QString& str) { - if (str == "eraser1") { - return ERASER1; - } else if (str == "painter2") { - return PAINTER2; - } else if (str == "eraser3") { - return ERASER3; - } else { - return NO_OP; - } + if (str == "eraser1") { + return ERASER1; + } else if (str == "painter2") { + return PAINTER2; + } else if (str == "eraser3") { + return ERASER3; + } else { + return NO_OP; + } } QString PictureLayerProperty::layerToString(Layer layer) { - const char* str = nullptr; - - switch (layer) { - case ERASER1: - str = "eraser1"; - break; - case PAINTER2: - str = "painter2"; - break; - case ERASER3: - str = "eraser3"; - break; - default: - str = ""; - break; - } - - return str; + const char* str = nullptr; + + switch (layer) { + case ERASER1: + str = "eraser1"; + break; + case PAINTER2: + str = "painter2"; + break; + case ERASER3: + str = "eraser3"; + break; + default: + str = ""; + break; + } + + return str; } -PictureLayerProperty::PictureLayerProperty(PictureLayerProperty::Layer layer) : m_layer(layer) { -} +PictureLayerProperty::PictureLayerProperty(PictureLayerProperty::Layer layer) : m_layer(layer) {} PictureLayerProperty::Layer PictureLayerProperty::layer() const { - return m_layer; + return m_layer; } void PictureLayerProperty::setLayer(PictureLayerProperty::Layer layer) { - m_layer = layer; + m_layer = layer; } } // namespace output \ No newline at end of file diff --git a/filters/output/PictureLayerProperty.h b/filters/output/PictureLayerProperty.h index 81fbc12a4..afff45021 100644 --- a/filters/output/PictureLayerProperty.h +++ b/filters/output/PictureLayerProperty.h @@ -29,33 +29,33 @@ class QString; namespace output { class PictureLayerProperty : public Property { -public: - enum Layer { NO_OP, ERASER1, PAINTER2, ERASER3 }; + public: + enum Layer { NO_OP, ERASER1, PAINTER2, ERASER3 }; - explicit PictureLayerProperty(Layer layer = NO_OP); + explicit PictureLayerProperty(Layer layer = NO_OP); - explicit PictureLayerProperty(const QDomElement& el); + explicit PictureLayerProperty(const QDomElement& el); - static void registerIn(PropertyFactory& factory); + static void registerIn(PropertyFactory& factory); - intrusive_ptr clone() const override; + intrusive_ptr clone() const override; - QDomElement toXml(QDomDocument& doc, const QString& name) const override; + QDomElement toXml(QDomDocument& doc, const QString& name) const override; - Layer layer() const; + Layer layer() const; - void setLayer(Layer layer); + void setLayer(Layer layer); -private: - static intrusive_ptr construct(const QDomElement& el); + private: + static intrusive_ptr construct(const QDomElement& el); - static Layer layerFromString(const QString& str); + static Layer layerFromString(const QString& str); - static QString layerToString(Layer layer); + static QString layerToString(Layer layer); - static const char m_propertyName[]; - Layer m_layer; + static const char m_propertyName[]; + Layer m_layer; }; } // namespace output #endif // ifndef OUTPUT_PICTURE_LAYER_PROPERTY_H_ diff --git a/filters/output/PictureShapeOptions.cpp b/filters/output/PictureShapeOptions.cpp index 5359bd1ba..c01772bbb 100644 --- a/filters/output/PictureShapeOptions.cpp +++ b/filters/output/PictureShapeOptions.cpp @@ -4,82 +4,80 @@ namespace output { PictureShapeOptions::PictureShapeOptions() - : pictureShape(FREE_SHAPE), sensitivity(100), higherSearchSensitivity(false) { -} + : m_pictureShape(FREE_SHAPE), m_sensitivity(100), m_higherSearchSensitivity(false) {} PictureShapeOptions::PictureShapeOptions(const QDomElement& el) - : pictureShape(parsePictureShape(el.attribute("pictureShape"))), - sensitivity(el.attribute("sensitivity").toInt()), - higherSearchSensitivity(el.attribute("higherSearchSensitivity") == "1") { -} + : m_pictureShape(parsePictureShape(el.attribute("pictureShape"))), + m_sensitivity(el.attribute("sensitivity").toInt()), + m_higherSearchSensitivity(el.attribute("higherSearchSensitivity") == "1") {} QDomElement PictureShapeOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("pictureShape", formatPictureShape(pictureShape)); - el.setAttribute("sensitivity", sensitivity); - el.setAttribute("higherSearchSensitivity", higherSearchSensitivity ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.setAttribute("pictureShape", formatPictureShape(m_pictureShape)); + el.setAttribute("sensitivity", m_sensitivity); + el.setAttribute("higherSearchSensitivity", m_higherSearchSensitivity ? "1" : "0"); - return el; + return el; } bool PictureShapeOptions::operator==(const PictureShapeOptions& other) const { - return (pictureShape == other.pictureShape) && (sensitivity == other.sensitivity) - && (higherSearchSensitivity == other.higherSearchSensitivity); + return (m_pictureShape == other.m_pictureShape) && (m_sensitivity == other.m_sensitivity) + && (m_higherSearchSensitivity == other.m_higherSearchSensitivity); } bool PictureShapeOptions::operator!=(const PictureShapeOptions& other) const { - return !(*this == other); + return !(*this == other); } PictureShape PictureShapeOptions::parsePictureShape(const QString& str) { - if (str == "rectangular") { - return RECTANGULAR_SHAPE; - } else if (str == "off") { - return OFF_SHAPE; - } else { - return FREE_SHAPE; - } + if (str == "rectangular") { + return RECTANGULAR_SHAPE; + } else if (str == "off") { + return OFF_SHAPE; + } else { + return FREE_SHAPE; + } } QString PictureShapeOptions::formatPictureShape(PictureShape type) { - QString str = ""; - switch (type) { - case OFF_SHAPE: - str = "off"; - break; - case FREE_SHAPE: - str = "free"; - break; - case RECTANGULAR_SHAPE: - str = "rectangular"; - break; - } - - return str; + QString str = ""; + switch (type) { + case OFF_SHAPE: + str = "off"; + break; + case FREE_SHAPE: + str = "free"; + break; + case RECTANGULAR_SHAPE: + str = "rectangular"; + break; + } + + return str; } PictureShape PictureShapeOptions::getPictureShape() const { - return pictureShape; + return m_pictureShape; } void PictureShapeOptions::setPictureShape(PictureShape pictureShape) { - PictureShapeOptions::pictureShape = pictureShape; + PictureShapeOptions::m_pictureShape = pictureShape; } int PictureShapeOptions::getSensitivity() const { - return sensitivity; + return m_sensitivity; } void PictureShapeOptions::setSensitivity(int sensitivity) { - PictureShapeOptions::sensitivity = sensitivity; + PictureShapeOptions::m_sensitivity = sensitivity; } bool PictureShapeOptions::isHigherSearchSensitivity() const { - return higherSearchSensitivity; + return m_higherSearchSensitivity; } void PictureShapeOptions::setHigherSearchSensitivity(bool higherSearchSensitivity) { - PictureShapeOptions::higherSearchSensitivity = higherSearchSensitivity; + PictureShapeOptions::m_higherSearchSensitivity = higherSearchSensitivity; } } // namespace output diff --git a/filters/output/PictureShapeOptions.h b/filters/output/PictureShapeOptions.h index 8837046d2..4f3d79e13 100644 --- a/filters/output/PictureShapeOptions.h +++ b/filters/output/PictureShapeOptions.h @@ -10,38 +10,38 @@ namespace output { enum PictureShape { OFF_SHAPE, FREE_SHAPE, RECTANGULAR_SHAPE }; class PictureShapeOptions { -public: - PictureShapeOptions(); + public: + PictureShapeOptions(); - explicit PictureShapeOptions(const QDomElement& el); + explicit PictureShapeOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool operator==(const PictureShapeOptions& other) const; + bool operator==(const PictureShapeOptions& other) const; - bool operator!=(const PictureShapeOptions& other) const; + bool operator!=(const PictureShapeOptions& other) const; - PictureShape getPictureShape() const; + PictureShape getPictureShape() const; - void setPictureShape(PictureShape pictureShape); + void setPictureShape(PictureShape pictureShape); - int getSensitivity() const; + int getSensitivity() const; - void setSensitivity(int sensitivity); + void setSensitivity(int sensitivity); - bool isHigherSearchSensitivity() const; + bool isHigherSearchSensitivity() const; - void setHigherSearchSensitivity(bool higherSearchSensitivity); + void setHigherSearchSensitivity(bool higherSearchSensitivity); -private: - static PictureShape parsePictureShape(const QString& str); + private: + static PictureShape parsePictureShape(const QString& str); - static QString formatPictureShape(PictureShape type); + static QString formatPictureShape(PictureShape type); - PictureShape pictureShape; - int sensitivity; - bool higherSearchSensitivity; + PictureShape m_pictureShape; + int m_sensitivity; + bool m_higherSearchSensitivity; }; } // namespace output diff --git a/filters/output/PictureZoneComparator.cpp b/filters/output/PictureZoneComparator.cpp index eef55bcb8..3c5eedb9f 100644 --- a/filters/output/PictureZoneComparator.cpp +++ b/filters/output/PictureZoneComparator.cpp @@ -21,30 +21,30 @@ namespace output { bool PictureZoneComparator::equal(const ZoneSet& lhs, const ZoneSet& rhs) { - ZoneSet::const_iterator lhs_it(lhs.begin()); - ZoneSet::const_iterator rhs_it(rhs.begin()); - const ZoneSet::const_iterator lhs_end(lhs.end()); - const ZoneSet::const_iterator rhs_end(rhs.end()); - for (; lhs_it != lhs_end && rhs_it != rhs_end; ++lhs_it, ++rhs_it) { - if (!equal(*lhs_it, *rhs_it)) { - return false; - } + ZoneSet::const_iterator lhs_it(lhs.begin()); + ZoneSet::const_iterator rhs_it(rhs.begin()); + const ZoneSet::const_iterator lhs_end(lhs.end()); + const ZoneSet::const_iterator rhs_end(rhs.end()); + for (; lhs_it != lhs_end && rhs_it != rhs_end; ++lhs_it, ++rhs_it) { + if (!equal(*lhs_it, *rhs_it)) { + return false; } + } - return lhs_it == lhs_end && rhs_it == rhs_end; + return lhs_it == lhs_end && rhs_it == rhs_end; } bool PictureZoneComparator::equal(const Zone& lhs, const Zone& rhs) { - if (lhs.spline().toPolygon() != rhs.spline().toPolygon()) { - return false; - } + if (lhs.spline().toPolygon() != rhs.spline().toPolygon()) { + return false; + } - return equal(lhs.properties(), rhs.properties()); + return equal(lhs.properties(), rhs.properties()); } bool PictureZoneComparator::equal(const PropertySet& lhs, const PropertySet& rhs) { - typedef PictureLayerProperty PLP; + typedef PictureLayerProperty PLP; - return lhs.locateOrDefault()->layer() == rhs.locateOrDefault()->layer(); + return lhs.locateOrDefault()->layer() == rhs.locateOrDefault()->layer(); } } // namespace output \ No newline at end of file diff --git a/filters/output/PictureZoneComparator.h b/filters/output/PictureZoneComparator.h index f403d0665..ffd5d9a5d 100644 --- a/filters/output/PictureZoneComparator.h +++ b/filters/output/PictureZoneComparator.h @@ -25,12 +25,12 @@ class PropertySet; namespace output { class PictureZoneComparator { -public: - static bool equal(const ZoneSet& lhs, const ZoneSet& rhs); + public: + static bool equal(const ZoneSet& lhs, const ZoneSet& rhs); - static bool equal(const Zone& lhs, const Zone& rhs); + static bool equal(const Zone& lhs, const Zone& rhs); - static bool equal(const PropertySet& lhs, const PropertySet& rhs); + static bool equal(const PropertySet& lhs, const PropertySet& rhs); }; } // namespace output #endif diff --git a/filters/output/PictureZoneEditor.cpp b/filters/output/PictureZoneEditor.cpp index bb0ce5f6a..ef505382e 100644 --- a/filters/output/PictureZoneEditor.cpp +++ b/filters/output/PictureZoneEditor.cpp @@ -17,22 +17,22 @@ */ #include "PictureZoneEditor.h" -#include "Zone.h" -#include "ZoneSet.h" -#include "PictureZonePropDialog.h" -#include "Settings.h" -#include "ImageTransformation.h" +#include +#include +#include +#include +#include "BackgroundExecutor.h" #include "ImagePresentation.h" +#include "ImageTransformation.h" #include "OutputMargins.h" +#include "PictureZonePropDialog.h" #include "PixmapRenderer.h" -#include "BackgroundExecutor.h" -#include "imageproc/Transform.h" +#include "Settings.h" +#include "Zone.h" +#include "ZoneSet.h" #include "imageproc/Constants.h" #include "imageproc/GrayImage.h" -#include -#include -#include -#include +#include "imageproc/Transform.h" namespace output { static const QRgb mask_color = 0xff587ff4; @@ -41,53 +41,45 @@ using namespace imageproc; class PictureZoneEditor::MaskTransformTask : public AbstractCommand>>, public QObject { - DECLARE_NON_COPYABLE(MaskTransformTask) + DECLARE_NON_COPYABLE(MaskTransformTask) -public: - MaskTransformTask(PictureZoneEditor* zone_editor, - const BinaryImage& mask, - const QTransform& xform, - const QSize& target_size); + public: + MaskTransformTask(PictureZoneEditor* zone_editor, + const BinaryImage& mask, + const QTransform& xform, + const QSize& target_size); - void cancel() { - m_ptrResult->cancel(); - } + void cancel() { m_result->cancel(); } - const bool isCancelled() const { - return m_ptrResult->isCancelled(); - } + const bool isCancelled() const { return m_result->isCancelled(); } - intrusive_ptr> operator()() override; + intrusive_ptr> operator()() override; -private: - class Result : public AbstractCommand { - public: - explicit Result(PictureZoneEditor* zone_editor); + private: + class Result : public AbstractCommand { + public: + explicit Result(PictureZoneEditor* zone_editor); - void setData(const QPoint& origin, const QImage& mask); + void setData(const QPoint& origin, const QImage& mask); - void cancel() { - m_cancelFlag.fetchAndStoreRelaxed(1); - } + void cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } - bool isCancelled() const { - return m_cancelFlag.fetchAndAddRelaxed(0) != 0; - } + bool isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } - void operator()() override; + void operator()() override; - private: - QPointer m_ptrZoneEditor; - QPoint m_origin; - QImage m_mask; - mutable QAtomicInt m_cancelFlag; - }; + private: + QPointer m_zoneEditor; + QPoint m_origin; + QImage m_mask; + mutable QAtomicInt m_cancelFlag; + }; - intrusive_ptr m_ptrResult; - BinaryImage m_origMask; - QTransform m_xform; - QSize m_targetSize; + intrusive_ptr m_result; + BinaryImage m_origMask; + QTransform m_xform; + QSize m_targetSize; }; @@ -98,203 +90,203 @@ PictureZoneEditor::PictureZoneEditor(const QImage& image, const QPolygonF& virt_display_area, const PageId& page_id, intrusive_ptr settings) - : ImageViewBase(image, downscaled_image, ImagePresentation(image_to_virt, virt_display_area), OutputMargins()), - m_context(*this, m_zones), - m_dragHandler(*this), - m_zoomHandler(*this), - m_origPictureMask(picture_mask), - m_pictureMaskAnimationPhase(270), - m_pageId(page_id), - m_ptrSettings(std::move(settings)) { - m_zones.setDefaultProperties(m_ptrSettings->defaultPictureZoneProperties()); + : ImageViewBase(image, downscaled_image, ImagePresentation(image_to_virt, virt_display_area), OutputMargins()), + m_context(*this, m_zones), + m_dragHandler(*this), + m_zoomHandler(*this), + m_origPictureMask(picture_mask), + m_pictureMaskAnimationPhase(270), + m_pageId(page_id), + m_settings(std::move(settings)) { + m_zones.setDefaultProperties(m_settings->defaultPictureZoneProperties()); - setMouseTracking(true); + setMouseTracking(true); - m_context.setShowPropertiesCommand(boost::bind(&PictureZoneEditor::showPropertiesDialog, this, _1)); + m_context.setShowPropertiesCommand(boost::bind(&PictureZoneEditor::showPropertiesDialog, this, _1)); - connect(&m_zones, SIGNAL(committed()), SLOT(commitZones())); + connect(&m_zones, SIGNAL(committed()), SLOT(commitZones())); - makeLastFollower(*m_context.createDefaultInteraction()); + makeLastFollower(*m_context.createDefaultInteraction()); - rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(*this); - // We want these handlers after zone interaction handlers, - // as some of those have their own drag and zoom handlers, - // which need to get events before these standard ones. - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + // We want these handlers after zone interaction handlers, + // as some of those have their own drag and zoom handlers, + // which need to get events before these standard ones. + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); - connect(&m_pictureMaskAnimateTimer, SIGNAL(timeout()), SLOT(advancePictureMaskAnimation())); - m_pictureMaskAnimateTimer.setSingleShot(true); - m_pictureMaskAnimateTimer.setInterval(120); + connect(&m_pictureMaskAnimateTimer, SIGNAL(timeout()), SLOT(advancePictureMaskAnimation())); + m_pictureMaskAnimateTimer.setSingleShot(true); + m_pictureMaskAnimateTimer.setInterval(120); - connect(&m_pictureMaskRebuildTimer, SIGNAL(timeout()), SLOT(initiateBuildingScreenPictureMask())); - m_pictureMaskRebuildTimer.setSingleShot(true); - m_pictureMaskRebuildTimer.setInterval(150); + connect(&m_pictureMaskRebuildTimer, SIGNAL(timeout()), SLOT(initiateBuildingScreenPictureMask())); + m_pictureMaskRebuildTimer.setSingleShot(true); + m_pictureMaskRebuildTimer.setInterval(150); - for (const Zone& zone : m_ptrSettings->pictureZonesForPage(page_id)) { - auto spline = make_intrusive(zone.spline()); - m_zones.addZone(spline, zone.properties()); - } + for (const Zone& zone : m_settings->pictureZonesForPage(page_id)) { + auto spline = make_intrusive(zone.spline()); + m_zones.addZone(spline, zone.properties()); + } } PictureZoneEditor::~PictureZoneEditor() { - m_ptrSettings->setDefaultPictureZoneProperties(m_zones.defaultProperties()); + m_settings->setDefaultPictureZoneProperties(m_zones.defaultProperties()); } void PictureZoneEditor::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldTransform(QTransform()); - painter.setRenderHint(QPainter::Antialiasing); - - if (!validateScreenPictureMask()) { - schedulePictureMaskRebuild(); - } else { - const double sn = std::sin(constants::DEG2RAD * m_pictureMaskAnimationPhase); - const double scale = 0.5 * (sn + 1.0); // 0 .. 1 - const double opacity = 0.35 * scale + 0.15; - - QPixmap mask(m_screenPictureMask.rect().size()); - mask.fill(Qt::transparent); - - { - QPainter mask_painter(&mask); - mask_painter.drawPixmap(QPoint(0, 0), m_screenPictureMask); - mask_painter.translate(-m_screenPictureMaskOrigin); - paintOverPictureMask(mask_painter); - } - - painter.setOpacity(opacity); - painter.drawPixmap(m_screenPictureMaskOrigin, mask); - painter.setOpacity(1.0); - - if (!m_pictureMaskAnimateTimer.isActive()) { - m_pictureMaskAnimateTimer.start(); - } + painter.setWorldTransform(QTransform()); + painter.setRenderHint(QPainter::Antialiasing); + + if (!validateScreenPictureMask()) { + schedulePictureMaskRebuild(); + } else { + const double sn = std::sin(constants::DEG2RAD * m_pictureMaskAnimationPhase); + const double scale = 0.5 * (sn + 1.0); // 0 .. 1 + const double opacity = 0.35 * scale + 0.15; + + QPixmap mask(m_screenPictureMask.rect().size()); + mask.fill(Qt::transparent); + + { + QPainter mask_painter(&mask); + mask_painter.drawPixmap(QPoint(0, 0), m_screenPictureMask); + mask_painter.translate(-m_screenPictureMaskOrigin); + paintOverPictureMask(mask_painter); } + + painter.setOpacity(opacity); + painter.drawPixmap(m_screenPictureMaskOrigin, mask); + painter.setOpacity(1.0); + + if (!m_pictureMaskAnimateTimer.isActive()) { + m_pictureMaskAnimateTimer.start(); + } + } } void PictureZoneEditor::advancePictureMaskAnimation() { - m_pictureMaskAnimationPhase = (m_pictureMaskAnimationPhase + 40) % 360; - update(); + m_pictureMaskAnimationPhase = (m_pictureMaskAnimationPhase + 40) % 360; + update(); } bool PictureZoneEditor::validateScreenPictureMask() const { - return !m_screenPictureMask.isNull() && m_screenPictureMaskXform == virtualToWidget(); + return !m_screenPictureMask.isNull() && m_screenPictureMaskXform == virtualToWidget(); } void PictureZoneEditor::schedulePictureMaskRebuild() { - if (!m_pictureMaskRebuildTimer.isActive() || (m_potentialPictureMaskXform != virtualToWidget())) { - if (m_ptrMaskTransformTask) { - m_ptrMaskTransformTask->cancel(); - m_ptrMaskTransformTask.reset(); - } - m_potentialPictureMaskXform = virtualToWidget(); + if (!m_pictureMaskRebuildTimer.isActive() || (m_potentialPictureMaskXform != virtualToWidget())) { + if (m_maskTransformTask) { + m_maskTransformTask->cancel(); + m_maskTransformTask.reset(); } - m_pictureMaskRebuildTimer.start(); + m_potentialPictureMaskXform = virtualToWidget(); + } + m_pictureMaskRebuildTimer.start(); } void PictureZoneEditor::initiateBuildingScreenPictureMask() { - if (validateScreenPictureMask()) { - return; - } + if (validateScreenPictureMask()) { + return; + } - m_screenPictureMask = QPixmap(); + m_screenPictureMask = QPixmap(); - if (m_ptrMaskTransformTask) { - m_ptrMaskTransformTask->cancel(); - m_ptrMaskTransformTask.reset(); - } + if (m_maskTransformTask) { + m_maskTransformTask->cancel(); + m_maskTransformTask.reset(); + } - const QTransform xform(virtualToWidget()); - const auto task = make_intrusive(this, m_origPictureMask, xform, viewport()->size()); + const QTransform xform(virtualToWidget()); + const auto task = make_intrusive(this, m_origPictureMask, xform, viewport()->size()); - backgroundExecutor().enqueueTask(task); + backgroundExecutor().enqueueTask(task); - m_screenPictureMask = QPixmap(); - m_ptrMaskTransformTask = task; - m_screenPictureMaskXform = xform; + m_screenPictureMask = QPixmap(); + m_maskTransformTask = task; + m_screenPictureMaskXform = xform; } void PictureZoneEditor::screenPictureMaskBuilt(const QPoint& origin, const QImage& mask) { - m_screenPictureMask = QPixmap::fromImage(mask); - m_screenPictureMaskOrigin = origin; - m_pictureMaskAnimationPhase = 270; + m_screenPictureMask = QPixmap::fromImage(mask); + m_screenPictureMaskOrigin = origin; + m_pictureMaskAnimationPhase = 270; - m_ptrMaskTransformTask.reset(); - update(); + m_maskTransformTask.reset(); + update(); } void PictureZoneEditor::paintOverPictureMask(QPainter& painter) { - painter.setRenderHint(QPainter::Antialiasing); - painter.setTransform(imageToVirtual() * virtualToWidget(), true); - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(mask_color)); + painter.setRenderHint(QPainter::Antialiasing); + painter.setTransform(imageToVirtual() * virtualToWidget(), true); + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(mask_color)); - typedef PictureLayerProperty PLP; + typedef PictureLayerProperty PLP; - painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.setCompositionMode(QPainter::CompositionMode_Clear); - // First pass: ERASER1 - for (const EditableZoneSet::Zone& zone : m_zones) { - if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER1) { - painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); - } + // First pass: ERASER1 + for (const EditableZoneSet::Zone& zone : m_zones) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER1) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } + } - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - // Second pass: PAINTER2 - for (const EditableZoneSet::Zone& zone : m_zones) { - if (zone.properties()->locateOrDefault()->layer() == PLP::PAINTER2) { - painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); - } + // Second pass: PAINTER2 + for (const EditableZoneSet::Zone& zone : m_zones) { + if (zone.properties()->locateOrDefault()->layer() == PLP::PAINTER2) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } + } - painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.setCompositionMode(QPainter::CompositionMode_Clear); - // Third pass: ERASER3 - for (const EditableZoneSet::Zone& zone : m_zones) { - if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER3) { - painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); - } + // Third pass: ERASER3 + for (const EditableZoneSet::Zone& zone : m_zones) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER3) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } + } } // PictureZoneEditor::paintOverPictureMask void PictureZoneEditor::showPropertiesDialog(const EditableZoneSet::Zone& zone) { - PropertySet saved_properties; + PropertySet saved_properties; + zone.properties()->swap(saved_properties); + *zone.properties() = saved_properties; + + PictureZonePropDialog dialog(zone.properties(), this); + // We can't connect to the update() slot directly, as since some time, + // Qt ignores such update requests on inactive windows. Updating + // it through a proxy slot does work though. + connect(&dialog, SIGNAL(updated()), SLOT(updateRequested())); + + if (dialog.exec() == QDialog::Accepted) { + m_zones.setDefaultProperties(*zone.properties()); + m_zones.commit(); + m_settings->setDefaultPictureZoneProperties(m_zones.defaultProperties()); + } else { zone.properties()->swap(saved_properties); - *zone.properties() = saved_properties; - - PictureZonePropDialog dialog(zone.properties(), this); - // We can't connect to the update() slot directly, as since some time, - // Qt ignores such update requests on inactive windows. Updating - // it through a proxy slot does work though. - connect(&dialog, SIGNAL(updated()), SLOT(updateRequested())); - - if (dialog.exec() == QDialog::Accepted) { - m_zones.setDefaultProperties(*zone.properties()); - m_zones.commit(); - m_ptrSettings->setDefaultPictureZoneProperties(m_zones.defaultProperties()); - } else { - zone.properties()->swap(saved_properties); - update(); - } + update(); + } } void PictureZoneEditor::commitZones() { - ZoneSet zones; + ZoneSet zones; - for (const EditableZoneSet::Zone& zone : m_zones) { - zones.add(Zone(*zone.spline(), *zone.properties())); - } + for (const EditableZoneSet::Zone& zone : m_zones) { + zones.add(Zone(*zone.spline(), *zone.properties())); + } - m_ptrSettings->setPictureZones(m_pageId, zones); + m_settings->setPictureZones(m_pageId, zones); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void PictureZoneEditor::updateRequested() { - update(); + update(); } /*============================= MaskTransformTask ===============================*/ @@ -303,44 +295,40 @@ PictureZoneEditor::MaskTransformTask::MaskTransformTask(PictureZoneEditor* zone_ const BinaryImage& mask, const QTransform& xform, const QSize& target_size) - : m_ptrResult(new Result(zone_editor)), m_origMask(mask), m_xform(xform), m_targetSize(target_size) { -} + : m_result(new Result(zone_editor)), m_origMask(mask), m_xform(xform), m_targetSize(target_size) {} intrusive_ptr> PictureZoneEditor::MaskTransformTask::operator()() { - if (isCancelled()) { - return nullptr; - } + if (isCancelled()) { + return nullptr; + } - const QRect target_rect(m_xform.map(QRectF(m_origMask.rect())) - .boundingRect() - .toRect() - .intersected(QRect(QPoint(0, 0), m_targetSize))); + const QRect target_rect( + m_xform.map(QRectF(m_origMask.rect())).boundingRect().toRect().intersected(QRect(QPoint(0, 0), m_targetSize))); - QImage gray_mask(transformToGray(m_origMask.toQImage(), m_xform, target_rect, - OutsidePixels::assumeWeakColor(Qt::black), QSizeF(0.0, 0.0))); + QImage gray_mask(transformToGray(m_origMask.toQImage(), m_xform, target_rect, + OutsidePixels::assumeWeakColor(Qt::black), QSizeF(0.0, 0.0))); - QImage mask(gray_mask.size(), QImage::Format_ARGB32_Premultiplied); - mask.fill(mask_color); - mask.setAlphaChannel(gray_mask); + QImage mask(gray_mask.size(), QImage::Format_ARGB32_Premultiplied); + mask.fill(mask_color); + mask.setAlphaChannel(gray_mask); - m_ptrResult->setData(target_rect.topLeft(), mask); + m_result->setData(target_rect.topLeft(), mask); - return m_ptrResult; + return m_result; } /*===================== MaskTransformTask::Result ===================*/ -PictureZoneEditor::MaskTransformTask::Result::Result(PictureZoneEditor* zone_editor) : m_ptrZoneEditor(zone_editor) { -} +PictureZoneEditor::MaskTransformTask::Result::Result(PictureZoneEditor* zone_editor) : m_zoneEditor(zone_editor) {} void PictureZoneEditor::MaskTransformTask::Result::setData(const QPoint& origin, const QImage& mask) { - m_mask = mask; - m_origin = origin; + m_mask = mask; + m_origin = origin; } void PictureZoneEditor::MaskTransformTask::Result::operator()() { - if (m_ptrZoneEditor && !isCancelled()) { - m_ptrZoneEditor->screenPictureMaskBuilt(m_origin, m_mask); - } + if (m_zoneEditor && !isCancelled()) { + m_zoneEditor->screenPictureMaskBuilt(m_origin, m_mask); + } } } // namespace output \ No newline at end of file diff --git a/filters/output/PictureZoneEditor.h b/filters/output/PictureZoneEditor.h index 38bd7675b..60d7c9484 100644 --- a/filters/output/PictureZoneEditor.h +++ b/filters/output/PictureZoneEditor.h @@ -20,22 +20,22 @@ #ifndef OUTPUT_PICTURE_ZONE_EDITOR_H_ #define OUTPUT_PICTURE_ZONE_EDITOR_H_ +#include +#include +#include +#include +#include "DragHandler.h" +#include "EditableSpline.h" +#include "EditableZoneSet.h" +#include "ImagePixmapUnion.h" #include "ImageViewBase.h" #include "NonCopyable.h" -#include "ref_countable.h" -#include "intrusive_ptr.h" #include "PageId.h" #include "ZoneInteractionContext.h" -#include "EditableSpline.h" -#include "EditableZoneSet.h" #include "ZoomHandler.h" -#include "DragHandler.h" -#include "ImagePixmapUnion.h" #include "imageproc/BinaryImage.h" -#include -#include -#include -#include +#include "intrusive_ptr.h" +#include "ref_countable.h" class ImageTransformation; class InteractionState; @@ -47,68 +47,68 @@ class Settings; class PictureZoneEditor : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - PictureZoneEditor(const QImage& image, - const ImagePixmapUnion& downscaled_image, - const imageproc::BinaryImage& picture_mask, - const QTransform& image_to_virt, - const QPolygonF& virt_display_area, - const PageId& page_id, - intrusive_ptr settings); + Q_OBJECT + public: + PictureZoneEditor(const QImage& image, + const ImagePixmapUnion& downscaled_image, + const imageproc::BinaryImage& picture_mask, + const QTransform& image_to_virt, + const QPolygonF& virt_display_area, + const PageId& page_id, + intrusive_ptr settings); - ~PictureZoneEditor() override; + ~PictureZoneEditor() override; -signals: + signals: - void invalidateThumbnail(const PageId& page_id); + void invalidateThumbnail(const PageId& page_id); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; -private slots: + private slots: - void advancePictureMaskAnimation(); + void advancePictureMaskAnimation(); - void initiateBuildingScreenPictureMask(); + void initiateBuildingScreenPictureMask(); - void commitZones(); + void commitZones(); - void updateRequested(); + void updateRequested(); -private: - class MaskTransformTask; + private: + class MaskTransformTask; - bool validateScreenPictureMask() const; + bool validateScreenPictureMask() const; - void schedulePictureMaskRebuild(); + void schedulePictureMaskRebuild(); - void screenPictureMaskBuilt(const QPoint& origin, const QImage& mask); + void screenPictureMaskBuilt(const QPoint& origin, const QImage& mask); - void paintOverPictureMask(QPainter& painter); + void paintOverPictureMask(QPainter& painter); - void showPropertiesDialog(const EditableZoneSet::Zone& zone); + void showPropertiesDialog(const EditableZoneSet::Zone& zone); - EditableZoneSet m_zones; + EditableZoneSet m_zones; - // Must go after m_zones. - ZoneInteractionContext m_context; + // Must go after m_zones. + ZoneInteractionContext m_context; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; - imageproc::BinaryImage m_origPictureMask; - QPixmap m_screenPictureMask; - QPoint m_screenPictureMaskOrigin; - QTransform m_screenPictureMaskXform; - QTransform m_potentialPictureMaskXform; - QTimer m_pictureMaskRebuildTimer; - QTimer m_pictureMaskAnimateTimer; - int m_pictureMaskAnimationPhase; // degrees - intrusive_ptr m_ptrMaskTransformTask; + imageproc::BinaryImage m_origPictureMask; + QPixmap m_screenPictureMask; + QPoint m_screenPictureMaskOrigin; + QTransform m_screenPictureMaskXform; + QTransform m_potentialPictureMaskXform; + QTimer m_pictureMaskRebuildTimer; + QTimer m_pictureMaskAnimateTimer; + int m_pictureMaskAnimationPhase; // degrees + intrusive_ptr m_maskTransformTask; - PageId m_pageId; - intrusive_ptr m_ptrSettings; + PageId m_pageId; + intrusive_ptr m_settings; }; } // namespace output #endif // ifndef OUTPUT_PICTURE_ZONE_EDITOR_H_ diff --git a/filters/output/PictureZonePropDialog.cpp b/filters/output/PictureZonePropDialog.cpp index 11da02b78..0947b4cf4 100644 --- a/filters/output/PictureZonePropDialog.cpp +++ b/filters/output/PictureZonePropDialog.cpp @@ -23,42 +23,42 @@ namespace output { PictureZonePropDialog::PictureZonePropDialog(intrusive_ptr props, QWidget* parent) - : QDialog(parent), m_ptrProps(std::move(props)) { - ui.setupUi(this); + : QDialog(parent), m_props(std::move(props)) { + ui.setupUi(this); - switch (m_ptrProps->locateOrDefault()->layer()) { - case PictureLayerProperty::NO_OP: - break; - case PictureLayerProperty::ERASER1: - ui.eraser1->setChecked(true); - break; - case PictureLayerProperty::PAINTER2: - ui.painter2->setChecked(true); - break; - case PictureLayerProperty::ERASER3: - ui.eraser3->setChecked(true); - break; - } + switch (m_props->locateOrDefault()->layer()) { + case PictureLayerProperty::NO_OP: + break; + case PictureLayerProperty::ERASER1: + ui.eraser1->setChecked(true); + break; + case PictureLayerProperty::PAINTER2: + ui.painter2->setChecked(true); + break; + case PictureLayerProperty::ERASER3: + ui.eraser3->setChecked(true); + break; + } - connect(ui.eraser1, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); - connect(ui.painter2, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); - connect(ui.eraser3, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + connect(ui.eraser1, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + connect(ui.painter2, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + connect(ui.eraser3, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); } void PictureZonePropDialog::itemToggled(bool selected) { - PictureLayerProperty::Layer layer = PictureLayerProperty::NO_OP; + PictureLayerProperty::Layer layer = PictureLayerProperty::NO_OP; - QObject* const obj = sender(); - if (obj == ui.eraser1) { - layer = PictureLayerProperty::ERASER1; - } else if (obj == ui.painter2) { - layer = PictureLayerProperty::PAINTER2; - } else if (obj == ui.eraser3) { - layer = PictureLayerProperty::ERASER3; - } + QObject* const obj = sender(); + if (obj == ui.eraser1) { + layer = PictureLayerProperty::ERASER1; + } else if (obj == ui.painter2) { + layer = PictureLayerProperty::PAINTER2; + } else if (obj == ui.eraser3) { + layer = PictureLayerProperty::ERASER3; + } - m_ptrProps->locateOrCreate()->setLayer(layer); + m_props->locateOrCreate()->setLayer(layer); - emit updated(); + emit updated(); } } // namespace output \ No newline at end of file diff --git a/filters/output/PictureZonePropDialog.h b/filters/output/PictureZonePropDialog.h index 9e36dac07..fec367e86 100644 --- a/filters/output/PictureZonePropDialog.h +++ b/filters/output/PictureZonePropDialog.h @@ -19,28 +19,28 @@ #ifndef OUTPUT_PICTURE_ZONE_PROP_DIALOG_H_ #define OUTPUT_PICTURE_ZONE_PROP_DIALOG_H_ -#include "ui_PictureZonePropDialog.h" +#include #include "PropertySet.h" #include "intrusive_ptr.h" -#include +#include "ui_PictureZonePropDialog.h" namespace output { class PictureZonePropDialog : public QDialog { - Q_OBJECT -public: - explicit PictureZonePropDialog(intrusive_ptr props, QWidget* parent = nullptr); + Q_OBJECT + public: + explicit PictureZonePropDialog(intrusive_ptr props, QWidget* parent = nullptr); -signals: + signals: - void updated(); + void updated(); -private slots: + private slots: - void itemToggled(bool selected); + void itemToggled(bool selected); -private: - Ui::PictureZonePropDialog ui; - intrusive_ptr m_ptrProps; + private: + Ui::PictureZonePropDialog ui; + intrusive_ptr m_props; }; } // namespace output #endif diff --git a/filters/output/PictureZonePropFactory.cpp b/filters/output/PictureZonePropFactory.cpp index c8be8a893..eb1ea46d4 100644 --- a/filters/output/PictureZonePropFactory.cpp +++ b/filters/output/PictureZonePropFactory.cpp @@ -22,7 +22,7 @@ namespace output { PictureZonePropFactory::PictureZonePropFactory() { - PictureLayerProperty::registerIn(*this); - ZoneCategoryProperty::registerIn(*this); + PictureLayerProperty::registerIn(*this); + ZoneCategoryProperty::registerIn(*this); } } // namespace output diff --git a/filters/output/PictureZonePropFactory.h b/filters/output/PictureZonePropFactory.h index 30e1db62c..6188f820a 100644 --- a/filters/output/PictureZonePropFactory.h +++ b/filters/output/PictureZonePropFactory.h @@ -23,8 +23,8 @@ namespace output { class PictureZonePropFactory : public PropertyFactory { -public: - PictureZonePropFactory(); + public: + PictureZonePropFactory(); }; } // namespace output #endif diff --git a/filters/output/RenderParams.cpp b/filters/output/RenderParams.cpp index 15b1429ac..2c130326a 100644 --- a/filters/output/RenderParams.cpp +++ b/filters/output/RenderParams.cpp @@ -21,98 +21,105 @@ namespace output { RenderParams::RenderParams(const ColorParams& colorParams, const SplittingOptions& splittingOptions) : m_mask(0) { - const BlackWhiteOptions blackWhiteOptions(colorParams.blackWhiteOptions()); - const ColorCommonOptions colorCommonOptions(colorParams.colorCommonOptions()); - const ColorMode colorMode = colorParams.colorMode(); - - if ((colorMode == BLACK_AND_WHITE) || (colorMode == MIXED)) { - m_mask |= NEED_BINARIZATION; - if ((colorMode == MIXED) && splittingOptions.isSplitOutput()) { - m_mask |= SPLIT_OUTPUT; - if (splittingOptions.getSplittingMode() == COLOR_FOREGROUND) { - m_mask ^= NEED_BINARIZATION; - } - if ((splittingOptions.getSplittingMode() == BLACK_AND_WHITE_FOREGROUND) - && (splittingOptions.isOriginalBackground())) { - m_mask |= ORIGINAL_BACKGROUND; - } - } - if (colorMode == MIXED) { - m_mask |= MIXED_OUTPUT; - } - if (blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()) { - m_mask |= SAVITZKY_GOLAY_SMOOTHING; - } - if (blackWhiteOptions.isMorphologicalSmoothingEnabled()) { - m_mask |= MORPHOLOGICAL_SMOOTHING; - } - if (blackWhiteOptions.normalizeIllumination()) { - m_mask |= NORMALIZE_ILLUMINATION; - } - if (blackWhiteOptions.getColorSegmenterOptions().isEnabled()) { - m_mask |= COLOR_SEGMENTATION; - if (colorCommonOptions.getPosterizationOptions().isEnabled()) { - m_mask |= POSTERIZE; - } - } - } else { - if (colorCommonOptions.getPosterizationOptions().isEnabled()) { - m_mask |= POSTERIZE; - } + const BlackWhiteOptions blackWhiteOptions(colorParams.blackWhiteOptions()); + const ColorCommonOptions colorCommonOptions(colorParams.colorCommonOptions()); + const ColorMode colorMode = colorParams.colorMode(); + + if ((colorMode == BLACK_AND_WHITE) || (colorMode == MIXED)) { + m_mask |= NEED_BINARIZATION; + if ((colorMode == MIXED) && splittingOptions.isSplitOutput()) { + m_mask |= SPLIT_OUTPUT; + if (splittingOptions.getSplittingMode() == COLOR_FOREGROUND) { + m_mask ^= NEED_BINARIZATION; + } + if ((splittingOptions.getSplittingMode() == BLACK_AND_WHITE_FOREGROUND) + && (splittingOptions.isOriginalBackgroundEnabled())) { + m_mask |= ORIGINAL_BACKGROUND; + } } - if (colorCommonOptions.cutMargins()) { - m_mask |= CUT_MARGINS; + if (colorMode == MIXED) { + m_mask |= MIXED_OUTPUT; } - if (colorCommonOptions.normalizeIllumination()) { - m_mask |= NORMALIZE_ILLUMINATION_COLOR; + if (blackWhiteOptions.isSavitzkyGolaySmoothingEnabled()) { + m_mask |= SAVITZKY_GOLAY_SMOOTHING; } + if (blackWhiteOptions.isMorphologicalSmoothingEnabled()) { + m_mask |= MORPHOLOGICAL_SMOOTHING; + } + if (blackWhiteOptions.normalizeIllumination()) { + m_mask |= NORMALIZE_ILLUMINATION; + } + if (blackWhiteOptions.getColorSegmenterOptions().isEnabled()) { + m_mask |= COLOR_SEGMENTATION; + if (colorCommonOptions.getPosterizationOptions().isEnabled()) { + m_mask |= POSTERIZE; + } + } + } else { + if (colorCommonOptions.getPosterizationOptions().isEnabled()) { + m_mask |= POSTERIZE; + } + } + if (colorCommonOptions.fillMargins()) { + m_mask |= FILL_MARGINS; + } + if (colorCommonOptions.fillOffcut()) { + m_mask |= FILL_OFFCUT; + } + if (colorCommonOptions.normalizeIllumination()) { + m_mask |= NORMALIZE_ILLUMINATION_COLOR; + } } -bool RenderParams::cutMargins() const { - return (m_mask & CUT_MARGINS) != 0; +bool RenderParams::fillMargins() const { + return (m_mask & FILL_MARGINS) != 0; } bool RenderParams::normalizeIllumination() const { - return (m_mask & NORMALIZE_ILLUMINATION) != 0; + return (m_mask & NORMALIZE_ILLUMINATION) != 0; } bool RenderParams::normalizeIlluminationColor() const { - return (m_mask & NORMALIZE_ILLUMINATION_COLOR) != 0; + return (m_mask & NORMALIZE_ILLUMINATION_COLOR) != 0; } bool RenderParams::needBinarization() const { - return (m_mask & NEED_BINARIZATION) != 0; + return (m_mask & NEED_BINARIZATION) != 0; } bool RenderParams::mixedOutput() const { - return (m_mask & MIXED_OUTPUT) != 0; + return (m_mask & MIXED_OUTPUT) != 0; } bool RenderParams::binaryOutput() const { - return (m_mask & (NEED_BINARIZATION | MIXED_OUTPUT)) == NEED_BINARIZATION; + return (m_mask & (NEED_BINARIZATION | MIXED_OUTPUT)) == NEED_BINARIZATION; } bool RenderParams::needSavitzkyGolaySmoothing() const { - return (m_mask & SAVITZKY_GOLAY_SMOOTHING) != 0; + return (m_mask & SAVITZKY_GOLAY_SMOOTHING) != 0; } bool RenderParams::needMorphologicalSmoothing() const { - return (m_mask & MORPHOLOGICAL_SMOOTHING) != 0; + return (m_mask & MORPHOLOGICAL_SMOOTHING) != 0; } bool RenderParams::splitOutput() const { - return (m_mask & SPLIT_OUTPUT) != 0; + return (m_mask & SPLIT_OUTPUT) != 0; } bool RenderParams::originalBackground() const { - return (m_mask & ORIGINAL_BACKGROUND) != 0; + return (m_mask & ORIGINAL_BACKGROUND) != 0; } bool RenderParams::needColorSegmentation() const { - return (m_mask & COLOR_SEGMENTATION) != 0; + return (m_mask & COLOR_SEGMENTATION) != 0; } bool RenderParams::posterize() const { - return (m_mask & POSTERIZE) != 0; + return (m_mask & POSTERIZE) != 0; +} + +bool RenderParams::fillOffcut() const { + return (m_mask & FILL_OFFCUT) != 0; } } // namespace output diff --git a/filters/output/RenderParams.h b/filters/output/RenderParams.h index 14bf97db8..ff9228395 100644 --- a/filters/output/RenderParams.h +++ b/filters/output/RenderParams.h @@ -25,52 +25,54 @@ namespace output { class ColorParams; class RenderParams { -public: - RenderParams() : m_mask(0) { - } + public: + RenderParams() : m_mask(0) {} - explicit RenderParams(const ColorParams& colorParams, const SplittingOptions& splittingOptions); + explicit RenderParams(const ColorParams& colorParams, const SplittingOptions& splittingOptions); - bool cutMargins() const; + bool fillOffcut() const; - bool normalizeIllumination() const; + bool fillMargins() const; - bool normalizeIlluminationColor() const; + bool normalizeIllumination() const; - bool needBinarization() const; + bool normalizeIlluminationColor() const; - bool mixedOutput() const; + bool needBinarization() const; - bool binaryOutput() const; + bool mixedOutput() const; - bool needSavitzkyGolaySmoothing() const; + bool binaryOutput() const; - bool needMorphologicalSmoothing() const; + bool needSavitzkyGolaySmoothing() const; - bool splitOutput() const; + bool needMorphologicalSmoothing() const; - bool originalBackground() const; + bool splitOutput() const; - bool needColorSegmentation() const; + bool originalBackground() const; - bool posterize() const; + bool needColorSegmentation() const; -private: - enum { - CUT_MARGINS = 1, - NORMALIZE_ILLUMINATION = 1 << 1, - NEED_BINARIZATION = 1 << 2, - MIXED_OUTPUT = 1 << 3, - NORMALIZE_ILLUMINATION_COLOR = 1 << 4, - SAVITZKY_GOLAY_SMOOTHING = 1 << 5, - MORPHOLOGICAL_SMOOTHING = 1 << 6, - SPLIT_OUTPUT = 1 << 7, - ORIGINAL_BACKGROUND = 1 << 8, - COLOR_SEGMENTATION = 1 << 9, - POSTERIZE = 1 << 10 - }; + bool posterize() const; - int m_mask; + private: + enum { + FILL_MARGINS = 1, + NORMALIZE_ILLUMINATION = 1 << 1, + NEED_BINARIZATION = 1 << 2, + MIXED_OUTPUT = 1 << 3, + NORMALIZE_ILLUMINATION_COLOR = 1 << 4, + SAVITZKY_GOLAY_SMOOTHING = 1 << 5, + MORPHOLOGICAL_SMOOTHING = 1 << 6, + SPLIT_OUTPUT = 1 << 7, + ORIGINAL_BACKGROUND = 1 << 8, + COLOR_SEGMENTATION = 1 << 9, + POSTERIZE = 1 << 10, + FILL_OFFCUT = 1 << 11 + }; + + int m_mask; }; } // namespace output #endif // ifndef OUTPUT_RENDER_PARAMS_H_ diff --git a/filters/output/SauvolaBinarizationOptionsWidget.cpp b/filters/output/SauvolaBinarizationOptionsWidget.cpp index ba9a24ea0..e93c1e6e4 100644 --- a/filters/output/SauvolaBinarizationOptionsWidget.cpp +++ b/filters/output/SauvolaBinarizationOptionsWidget.cpp @@ -7,64 +7,69 @@ namespace output { SauvolaBinarizationOptionsWidget::SauvolaBinarizationOptionsWidget(intrusive_ptr settings) - : m_ptrSettings(std::move(settings)) { - setupUi(this); + : m_settings(std::move(settings)) { + setupUi(this); - delayedStateChanger.setSingleShot(true); + m_delayedStateChanger.setSingleShot(true); - setupUiConnections(); + setupUiConnections(); } -void SauvolaBinarizationOptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); +void SauvolaBinarizationOptionsWidget::updateUi(const PageId& page_id) { + removeUiConnections(); - const Params params(m_ptrSettings->getParams(page_id)); - m_pageId = page_id; - m_colorParams = params.colorParams(); - m_outputProcessingParams = m_ptrSettings->getOutputProcessingParams(page_id); + const Params params(m_settings->getParams(page_id)); + m_pageId = page_id; + m_colorParams = params.colorParams(); + m_outputProcessingParams = m_settings->getOutputProcessingParams(page_id); - updateView(); + updateView(); - setupUiConnections(); + setupUiConnections(); } void SauvolaBinarizationOptionsWidget::windowSizeChanged(int value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setWindowSize(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setWindowSize(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void SauvolaBinarizationOptionsWidget::sauvolaCoefChanged(double value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setSauvolaCoef(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setSauvolaCoef(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void SauvolaBinarizationOptionsWidget::updateView() { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - windowSize->setValue(blackWhiteOptions.getWindowSize()); - sauvolaCoef->setValue(blackWhiteOptions.getSauvolaCoef()); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + windowSize->setValue(blackWhiteOptions.getWindowSize()); + sauvolaCoef->setValue(blackWhiteOptions.getSauvolaCoef()); } void SauvolaBinarizationOptionsWidget::sendStateChanged() { - emit stateChanged(); + emit stateChanged(); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void SauvolaBinarizationOptionsWidget::setupUiConnections() { - connect(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); - connect(sauvolaCoef, SIGNAL(valueChanged(double)), this, SLOT(sauvolaCoefChanged(double))); - connect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + CONNECT(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); + CONNECT(sauvolaCoef, SIGNAL(valueChanged(double)), this, SLOT(sauvolaCoefChanged(double))); + CONNECT(&m_delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); } +#undef CONNECT + void SauvolaBinarizationOptionsWidget::removeUiConnections() { - disconnect(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); - disconnect(sauvolaCoef, SIGNAL(valueChanged(double)), this, SLOT(sauvolaCoefChanged(double))); - disconnect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } } // namespace output \ No newline at end of file diff --git a/filters/output/SauvolaBinarizationOptionsWidget.h b/filters/output/SauvolaBinarizationOptionsWidget.h index a69d4ea67..29dcdf2c9 100644 --- a/filters/output/SauvolaBinarizationOptionsWidget.h +++ b/filters/output/SauvolaBinarizationOptionsWidget.h @@ -3,46 +3,47 @@ #ifndef SCANTAILOR_SAUVOLABINARIZATIONOPTIONS_H #define SCANTAILOR_SAUVOLABINARIZATIONOPTIONS_H -#include "ui_SauvolaBinarizationOptionsWidget.h" +#include +#include #include "BinarizationOptionsWidget.h" #include "ColorParams.h" -#include "intrusive_ptr.h" #include "Settings.h" -#include +#include "intrusive_ptr.h" +#include "ui_SauvolaBinarizationOptionsWidget.h" namespace output { class SauvolaBinarizationOptionsWidget : public BinarizationOptionsWidget, private Ui::SauvolaBinarizationOptionsWidget { - Q_OBJECT + Q_OBJECT + public: + explicit SauvolaBinarizationOptionsWidget(intrusive_ptr settings); -private: - intrusive_ptr m_ptrSettings; - PageId m_pageId; - ColorParams m_colorParams; - QTimer delayedStateChanger; - OutputProcessingParams m_outputProcessingParams; + ~SauvolaBinarizationOptionsWidget() override = default; -public: - explicit SauvolaBinarizationOptionsWidget(intrusive_ptr settings); + void updateUi(const PageId& m_pageId) override; - ~SauvolaBinarizationOptionsWidget() override = default; + private slots: - void preUpdateUI(const PageId& m_pageId) override; + void windowSizeChanged(int value); -private slots: + void sauvolaCoefChanged(double value); - void windowSizeChanged(int value); + void sendStateChanged(); - void sauvolaCoefChanged(double value); + private: + void updateView(); - void sendStateChanged(); + void setupUiConnections(); -private: - void updateView(); + void removeUiConnections(); - void setupUiConnections(); + intrusive_ptr m_settings; + PageId m_pageId; + ColorParams m_colorParams; + QTimer m_delayedStateChanger; + OutputProcessingParams m_outputProcessingParams; - void removeUiConnections(); + std::list m_connectionList; }; } // namespace output diff --git a/filters/output/Settings.cpp b/filters/output/Settings.cpp index bd5646b19..83ea47d6d 100644 --- a/filters/output/Settings.cpp +++ b/filters/output/Settings.cpp @@ -17,310 +17,322 @@ */ #include "Settings.h" +#include "../../Utils.h" +#include "AbstractRelinker.h" #include "FillColorProperty.h" #include "RelinkablePath.h" -#include "AbstractRelinker.h" -#include "../../Utils.h" namespace output { Settings::Settings() - : m_defaultPictureZoneProps(initialPictureZoneProps()), m_defaultFillZoneProps(initialFillZoneProps()) { -} + : m_defaultPictureZoneProps(initialPictureZoneProps()), m_defaultFillZoneProps(initialFillZoneProps()) {} Settings::~Settings() = default; void Settings::clear() { - const QMutexLocker locker(&m_mutex); - - initialPictureZoneProps().swap(m_defaultPictureZoneProps); - initialFillZoneProps().swap(m_defaultFillZoneProps); - m_perPageParams.clear(); - m_perPageOutputParams.clear(); - m_perPagePictureZones.clear(); - m_perPageFillZones.clear(); - m_perPageOutputProcessingParams.clear(); + const QMutexLocker locker(&m_mutex); + + initialPictureZoneProps().swap(m_defaultPictureZoneProps); + initialFillZoneProps().swap(m_defaultFillZoneProps); + m_perPageParams.clear(); + m_perPageOutputParams.clear(); + m_perPagePictureZones.clear(); + m_perPageFillZones.clear(); + m_perPageOutputProcessingParams.clear(); } void Settings::performRelinking(const AbstractRelinker& relinker) { - const QMutexLocker locker(&m_mutex); - - PerPageParams new_params; - PerPageOutputParams new_output_params; - PerPageZones new_picture_zones; - PerPageZones new_fill_zones; - PerPageOutputProcessingParams new_output_processing_params; - - for (const PerPageParams::value_type& kv : m_perPageParams) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_params.insert(PerPageParams::value_type(new_page_id, kv.second)); - } - - for (const PerPageOutputParams::value_type& kv : m_perPageOutputParams) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_output_params.insert(PerPageOutputParams::value_type(new_page_id, kv.second)); - } - - for (const PerPageZones::value_type& kv : m_perPagePictureZones) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_picture_zones.insert(PerPageZones::value_type(new_page_id, kv.second)); - } - - for (const PerPageZones::value_type& kv : m_perPageFillZones) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_fill_zones.insert(PerPageZones::value_type(new_page_id, kv.second)); - } - - for (const PerPageOutputProcessingParams::value_type& kv : m_perPageOutputProcessingParams) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_output_processing_params.insert(PerPageOutputProcessingParams::value_type(new_page_id, kv.second)); - } - - m_perPageParams.swap(new_params); - m_perPageOutputParams.swap(new_output_params); - m_perPagePictureZones.swap(new_picture_zones); - m_perPageFillZones.swap(new_fill_zones); - m_perPageOutputProcessingParams.swap(new_output_processing_params); + const QMutexLocker locker(&m_mutex); + + PerPageParams new_params; + PerPageOutputParams new_output_params; + PerPageZones new_picture_zones; + PerPageZones new_fill_zones; + PerPageOutputProcessingParams new_output_processing_params; + + for (const PerPageParams::value_type& kv : m_perPageParams) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_params.insert(PerPageParams::value_type(new_page_id, kv.second)); + } + + for (const PerPageOutputParams::value_type& kv : m_perPageOutputParams) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_output_params.insert(PerPageOutputParams::value_type(new_page_id, kv.second)); + } + + for (const PerPageZones::value_type& kv : m_perPagePictureZones) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_picture_zones.insert(PerPageZones::value_type(new_page_id, kv.second)); + } + + for (const PerPageZones::value_type& kv : m_perPageFillZones) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_fill_zones.insert(PerPageZones::value_type(new_page_id, kv.second)); + } + + for (const PerPageOutputProcessingParams::value_type& kv : m_perPageOutputProcessingParams) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_output_processing_params.insert(PerPageOutputProcessingParams::value_type(new_page_id, kv.second)); + } + + m_perPageParams.swap(new_params); + m_perPageOutputParams.swap(new_output_params); + m_perPagePictureZones.swap(new_picture_zones); + m_perPageFillZones.swap(new_fill_zones); + m_perPageOutputProcessingParams.swap(new_output_processing_params); } // Settings::performRelinking Params Settings::getParams(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it != m_perPageParams.end()) { - return it->second; - } else { - return Params(); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it != m_perPageParams.end()) { + return it->second; + } else { + return Params(); + } } void Settings::setParams(const PageId& page_id, const Params& params) { - const QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPageParams, page_id, params); + const QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageParams, page_id, params); } void Settings::setColorParams(const PageId& page_id, const ColorParams& prms) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setColorParams(prms); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setColorParams(prms); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setColorParams(prms); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setColorParams(prms); + } } void Settings::setPictureShapeOptions(const PageId& page_id, PictureShapeOptions picture_shape_options) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setPictureShapeOptions(picture_shape_options); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setPictureShapeOptions(picture_shape_options); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setPictureShapeOptions(picture_shape_options); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setPictureShapeOptions(picture_shape_options); + } } void Settings::setDpi(const PageId& page_id, const Dpi& dpi) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setOutputDpi(dpi); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setOutputDpi(dpi); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setOutputDpi(dpi); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setOutputDpi(dpi); + } } void Settings::setDewarpingOptions(const PageId& page_id, const DewarpingOptions& opt) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setDewarpingOptions(opt); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setDewarpingOptions(opt); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setDewarpingOptions(opt); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setDewarpingOptions(opt); + } } void Settings::setSplittingOptions(const PageId& page_id, const SplittingOptions& opt) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setSplittingOptions(opt); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setSplittingOptions(opt); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setSplittingOptions(opt); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setSplittingOptions(opt); + } } void Settings::setDistortionModel(const PageId& page_id, const dewarping::DistortionModel& model) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setDistortionModel(model); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setDistortionModel(model); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setDistortionModel(model); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setDistortionModel(model); + } } void Settings::setDepthPerception(const PageId& page_id, const DepthPerception& depth_perception) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setDepthPerception(depth_perception); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setDepthPerception(depth_perception); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setDepthPerception(depth_perception); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setDepthPerception(depth_perception); + } } -void Settings::setDespeckleLevel(const PageId& page_id, DespeckleLevel level) { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageParams.find(page_id)); - if (it == m_perPageParams.end()) { - Params params; - params.setDespeckleLevel(level); - m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); - } else { - it->second.setDespeckleLevel(level); - } +void Settings::setDespeckleLevel(const PageId& page_id, double level) { + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setDespeckleLevel(level); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setDespeckleLevel(level); + } } std::unique_ptr Settings::getOutputParams(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageOutputParams.find(page_id)); - if (it != m_perPageOutputParams.end()) { - return std::make_unique(it->second); - } else { - return nullptr; - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageOutputParams.find(page_id)); + if (it != m_perPageOutputParams.end()) { + return std::make_unique(it->second); + } else { + return nullptr; + } } void Settings::removeOutputParams(const PageId& page_id) { - const QMutexLocker locker(&m_mutex); - m_perPageOutputParams.erase(page_id); + const QMutexLocker locker(&m_mutex); + m_perPageOutputParams.erase(page_id); } void Settings::setOutputParams(const PageId& page_id, const OutputParams& params) { - const QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPageOutputParams, page_id, params); + const QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageOutputParams, page_id, params); } ZoneSet Settings::pictureZonesForPage(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPagePictureZones.find(page_id)); - if (it != m_perPagePictureZones.end()) { - return it->second; - } else { - return ZoneSet(); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPagePictureZones.find(page_id)); + if (it != m_perPagePictureZones.end()) { + return it->second; + } else { + return ZoneSet(); + } } ZoneSet Settings::fillZonesForPage(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageFillZones.find(page_id)); - if (it != m_perPageFillZones.end()) { - return it->second; - } else { - return ZoneSet(); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageFillZones.find(page_id)); + if (it != m_perPageFillZones.end()) { + return it->second; + } else { + return ZoneSet(); + } } void Settings::setPictureZones(const PageId& page_id, const ZoneSet& zones) { - const QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPagePictureZones, page_id, zones); + const QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPagePictureZones, page_id, zones); } void Settings::setFillZones(const PageId& page_id, const ZoneSet& zones) { - const QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPageFillZones, page_id, zones); + const QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageFillZones, page_id, zones); } PropertySet Settings::defaultPictureZoneProperties() const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - return m_defaultPictureZoneProps; + return m_defaultPictureZoneProps; } PropertySet Settings::defaultFillZoneProperties() const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - return m_defaultFillZoneProps; + return m_defaultFillZoneProps; } void Settings::setDefaultPictureZoneProperties(const PropertySet& props) { - const QMutexLocker locker(&m_mutex); - m_defaultPictureZoneProps = props; + const QMutexLocker locker(&m_mutex); + m_defaultPictureZoneProps = props; } void Settings::setDefaultFillZoneProperties(const PropertySet& props) { - const QMutexLocker locker(&m_mutex); - m_defaultFillZoneProps = props; + const QMutexLocker locker(&m_mutex); + m_defaultFillZoneProps = props; } PropertySet Settings::initialPictureZoneProps() { - PropertySet props; - props.locateOrCreate()->setLayer(PictureLayerProperty::PAINTER2); + PropertySet props; + props.locateOrCreate()->setLayer(PictureLayerProperty::PAINTER2); - return props; + return props; } PropertySet Settings::initialFillZoneProps() { - PropertySet props; - props.locateOrCreate()->setColor(Qt::white); + PropertySet props; + props.locateOrCreate()->setColor(Qt::white); - return props; + return props; } OutputProcessingParams Settings::getOutputProcessingParams(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); - - const auto it(m_perPageOutputProcessingParams.find(page_id)); - if (it != m_perPageOutputProcessingParams.end()) { - return it->second; - } else { - return OutputProcessingParams(); - } + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageOutputProcessingParams.find(page_id)); + if (it != m_perPageOutputProcessingParams.end()) { + return it->second; + } else { + return OutputProcessingParams(); + } } void Settings::setOutputProcessingParams(const PageId& page_id, const OutputProcessingParams& output_processing_params) { - const QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_perPageOutputProcessingParams, page_id, output_processing_params); + const QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_perPageOutputProcessingParams, page_id, output_processing_params); } bool Settings::isParamsNull(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); + + return m_perPageParams.find(page_id) == m_perPageParams.end(); +} - return m_perPageParams.find(page_id) == m_perPageParams.end(); +void Settings::setBlackOnWhite(const PageId& page_id, const bool black_on_white) { + const QMutexLocker locker(&m_mutex); + + const auto it(m_perPageParams.find(page_id)); + if (it == m_perPageParams.end()) { + Params params; + params.setBlackOnWhite(black_on_white); + m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); + } else { + it->second.setBlackOnWhite(black_on_white); + } } } // namespace output \ No newline at end of file diff --git a/filters/output/Settings.h b/filters/output/Settings.h index 7f9346745..227af41c7 100644 --- a/filters/output/Settings.h +++ b/filters/output/Settings.h @@ -19,22 +19,22 @@ #ifndef OUTPUT_SETTINGS_H_ #define OUTPUT_SETTINGS_H_ -#include "ref_countable.h" +#include +#include +#include +#include "ColorParams.h" +#include "DespeckleLevel.h" +#include "DewarpingOptions.h" +#include "Dpi.h" #include "NonCopyable.h" +#include "OutputParams.h" +#include "OutputProcessingParams.h" #include "PageId.h" -#include "Dpi.h" -#include "ColorParams.h" #include "Params.h" -#include "OutputParams.h" -#include "DewarpingOptions.h" -#include "dewarping/DistortionModel.h" -#include "DespeckleLevel.h" -#include "ZoneSet.h" #include "PropertySet.h" -#include "OutputProcessingParams.h" -#include -#include -#include +#include "ZoneSet.h" +#include "dewarping/DistortionModel.h" +#include "ref_countable.h" class AbstractRelinker; @@ -42,87 +42,89 @@ namespace output { class Params; class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) + DECLARE_NON_COPYABLE(Settings) + + public: + Settings(); -public: - Settings(); + ~Settings() override; - ~Settings() override; + void clear(); - void clear(); + void performRelinking(const AbstractRelinker& relinker); - void performRelinking(const AbstractRelinker& relinker); + Params getParams(const PageId& page_id) const; - Params getParams(const PageId& page_id) const; + void setParams(const PageId& page_id, const Params& params); - void setParams(const PageId& page_id, const Params& params); + bool isParamsNull(const PageId& page_id) const; - bool isParamsNull(const PageId& page_id) const; + void setColorParams(const PageId& page_id, const ColorParams& prms); - void setColorParams(const PageId& page_id, const ColorParams& prms); + void setPictureShapeOptions(const PageId& page_id, PictureShapeOptions picture_shape_options); - void setPictureShapeOptions(const PageId& page_id, PictureShapeOptions picture_shape_options); + void setDpi(const PageId& page_id, const Dpi& dpi); - void setDpi(const PageId& page_id, const Dpi& dpi); + void setDewarpingOptions(const PageId& page_id, const DewarpingOptions& opt); - void setDewarpingOptions(const PageId& page_id, const DewarpingOptions& opt); + void setSplittingOptions(const PageId& page_id, const SplittingOptions& opt); - void setSplittingOptions(const PageId& page_id, const SplittingOptions& opt); + void setDistortionModel(const PageId& page_id, const dewarping::DistortionModel& model); - void setDistortionModel(const PageId& page_id, const dewarping::DistortionModel& model); + void setDepthPerception(const PageId& page_id, const DepthPerception& depth_perception); - void setDepthPerception(const PageId& page_id, const DepthPerception& depth_perception); + void setDespeckleLevel(const PageId& page_id, double level); - void setDespeckleLevel(const PageId& page_id, DespeckleLevel level); + std::unique_ptr getOutputParams(const PageId& page_id) const; - std::unique_ptr getOutputParams(const PageId& page_id) const; + void removeOutputParams(const PageId& page_id); - void removeOutputParams(const PageId& page_id); + void setOutputParams(const PageId& page_id, const OutputParams& params); - void setOutputParams(const PageId& page_id, const OutputParams& params); + ZoneSet pictureZonesForPage(const PageId& page_id) const; - ZoneSet pictureZonesForPage(const PageId& page_id) const; + ZoneSet fillZonesForPage(const PageId& page_id) const; - ZoneSet fillZonesForPage(const PageId& page_id) const; + void setPictureZones(const PageId& page_id, const ZoneSet& zones); - void setPictureZones(const PageId& page_id, const ZoneSet& zones); + void setFillZones(const PageId& page_id, const ZoneSet& zones); - void setFillZones(const PageId& page_id, const ZoneSet& zones); + /** + * For now, default zone properties are not persistent. + * They may become persistent later though. + */ + PropertySet defaultPictureZoneProperties() const; - /** - * For now, default zone properties are not persistent. - * They may become persistent later though. - */ - PropertySet defaultPictureZoneProperties() const; + PropertySet defaultFillZoneProperties() const; - PropertySet defaultFillZoneProperties() const; + void setDefaultPictureZoneProperties(const PropertySet& props); - void setDefaultPictureZoneProperties(const PropertySet& props); + void setDefaultFillZoneProperties(const PropertySet& props); - void setDefaultFillZoneProperties(const PropertySet& props); + OutputProcessingParams getOutputProcessingParams(const PageId& page_id) const; - OutputProcessingParams getOutputProcessingParams(const PageId& page_id) const; + void setOutputProcessingParams(const PageId& page_id, const OutputProcessingParams& output_processing_params); - void setOutputProcessingParams(const PageId& page_id, const OutputProcessingParams& output_processing_params); + void setBlackOnWhite(const PageId& page_id, bool black_on_white); -private: - typedef std::unordered_map PerPageParams; - typedef std::unordered_map PerPageOutputParams; - typedef std::unordered_map PerPageZones; - typedef std::unordered_map PerPageOutputProcessingParams; + private: + typedef std::unordered_map PerPageParams; + typedef std::unordered_map PerPageOutputParams; + typedef std::unordered_map PerPageZones; + typedef std::unordered_map PerPageOutputProcessingParams; - static PropertySet initialPictureZoneProps(); + static PropertySet initialPictureZoneProps(); - static PropertySet initialFillZoneProps(); + static PropertySet initialFillZoneProps(); - mutable QMutex m_mutex; - PerPageParams m_perPageParams; - PerPageOutputParams m_perPageOutputParams; - PerPageZones m_perPagePictureZones; - PerPageZones m_perPageFillZones; - PropertySet m_defaultPictureZoneProps; - PropertySet m_defaultFillZoneProps; - PerPageOutputProcessingParams m_perPageOutputProcessingParams; + mutable QMutex m_mutex; + PerPageParams m_perPageParams; + PerPageOutputParams m_perPageOutputParams; + PerPageZones m_perPagePictureZones; + PerPageZones m_perPageFillZones; + PropertySet m_defaultPictureZoneProps; + PropertySet m_defaultFillZoneProps; + PerPageOutputProcessingParams m_perPageOutputProcessingParams; }; } // namespace output #endif // ifndef OUTPUT_SETTINGS_H_ diff --git a/filters/output/SplitImage.cpp b/filters/output/SplitImage.cpp index 471f71ddb..9370f4b80 100644 --- a/filters/output/SplitImage.cpp +++ b/filters/output/SplitImage.cpp @@ -1,9 +1,9 @@ -#include -#include -#include -#include #include "SplitImage.h" +#include +#include +#include +#include using namespace imageproc; @@ -11,145 +11,145 @@ namespace output { SplitImage::SplitImage() = default; SplitImage::SplitImage(const QImage& foreground, const QImage& background) { - if ((foreground.format() != QImage::Format_Mono) && (foreground.format() != QImage::Format_MonoLSB) - && (foreground.format() != QImage::Format_Indexed8) && (foreground.format() != QImage::Format_RGB32) - && (foreground.format() != QImage::Format_ARGB32)) { - return; - } - if ((background.format() != QImage::Format_Indexed8) && (background.format() != QImage::Format_RGB32) - && (background.format() != QImage::Format_ARGB32)) { - return; - } - - if (foreground.size() != background.size()) { - return; - } - - foregroundImage = foreground; - backgroundImage = background; + if ((foreground.format() != QImage::Format_Mono) && (foreground.format() != QImage::Format_MonoLSB) + && (foreground.format() != QImage::Format_Indexed8) && (foreground.format() != QImage::Format_RGB32) + && (foreground.format() != QImage::Format_ARGB32)) { + return; + } + if ((background.format() != QImage::Format_Indexed8) && (background.format() != QImage::Format_RGB32) + && (background.format() != QImage::Format_ARGB32)) { + return; + } + + if (foreground.size() != background.size()) { + return; + } + + m_foregroundImage = foreground; + m_backgroundImage = background; } SplitImage::SplitImage(const QImage& foreground, const QImage& background, const QImage& originalBackground) - : SplitImage(foreground, background) { - if (isNull()) { - return; - } + : SplitImage(foreground, background) { + if (isNull()) { + return; + } - if (originalBackground.size() != background.size()) { - return; - } + if (originalBackground.size() != background.size()) { + return; + } - if (originalBackground.format() != background.format()) { - return; - } + if (originalBackground.format() != background.format()) { + return; + } - if ((originalBackground.format() != QImage::Format_Indexed8) - && (originalBackground.format() != QImage::Format_RGB32) - && (originalBackground.format() != QImage::Format_ARGB32)) { - return; - } + if ((originalBackground.format() != QImage::Format_Indexed8) && (originalBackground.format() != QImage::Format_RGB32) + && (originalBackground.format() != QImage::Format_ARGB32)) { + return; + } - originalBackgroundImage = originalBackground; + m_originalBackgroundImage = originalBackground; } QImage SplitImage::toImage() const { - if (isNull()) { - return QImage(); - } + if (isNull()) { + return QImage(); + } - if (originalBackgroundImage.isNull()) { - if (!mask.isNull()) { - return backgroundImage; - } - - QImage dst(backgroundImage); - combineImage(dst, foregroundImage); + if (m_originalBackgroundImage.isNull()) { + if (!m_mask.isNull()) { + return m_backgroundImage; + } - return dst; - } else { - QImage dst(originalBackgroundImage); + QImage dst(m_backgroundImage); + combineImages(dst, m_foregroundImage); - { - BinaryImage backgroundMask = BinaryImage(originalBackgroundImage, BinaryThreshold(1)); - combineImage(dst, backgroundImage, backgroundMask); - } - { - BinaryImage foregroundMask = BinaryImage(originalBackgroundImage, BinaryThreshold(255)).inverted(); - combineImage(dst, (mask.isNull()) ? foregroundImage : backgroundImage, foregroundMask); - } + return dst; + } else { + QImage dst(m_originalBackgroundImage); - return dst; + { + BinaryImage backgroundMask = BinaryImage(m_originalBackgroundImage, BinaryThreshold(1)); + combineImages(dst, m_backgroundImage, backgroundMask); } + { + BinaryImage foregroundMask = BinaryImage(m_originalBackgroundImage, BinaryThreshold(255)).inverted(); + combineImages(dst, (m_mask.isNull()) ? m_foregroundImage : m_backgroundImage, foregroundMask); + } + + return dst; + } } QImage SplitImage::getForegroundImage() const { - if (!mask.isNull()) { - QImage foreground(backgroundImage); - applyMask(foreground, mask); - - if (binaryForeground) { - foreground = foreground.convertToFormat(QImage::Format_Mono); - } else if (indexedForeground) { - foreground = ColorTable(foreground).toIndexedImage(); - } - - return foreground; + if (!m_mask.isNull()) { + QImage foreground(m_backgroundImage); + applyMask(foreground, m_mask); + + switch (m_foregroundType) { + case BINARY_FOREGROUND: + foreground = foreground.convertToFormat(QImage::Format_Mono); + break; + case INDEXED_FOREGROUND: + foreground = ColorTable(foreground).toIndexedImage(); + break; + default: + break; } - return foregroundImage; + return foreground; + } + + return m_foregroundImage; } void SplitImage::setForegroundImage(const QImage& foregroundImage) { - mask = BinaryImage(); - SplitImage::foregroundImage = foregroundImage; + m_mask = BinaryImage(); + m_foregroundImage = foregroundImage; } QImage SplitImage::getBackgroundImage() const { - if (!mask.isNull()) { - QImage background(backgroundImage); - applyMask(background, mask.inverted()); + if (!m_mask.isNull()) { + QImage background(m_backgroundImage); + applyMask(background, m_mask.inverted()); - return background; - } + return background; + } - return backgroundImage; + return m_backgroundImage; } void SplitImage::setBackgroundImage(const QImage& backgroundImage) { - SplitImage::backgroundImage = backgroundImage; + m_backgroundImage = backgroundImage; } void SplitImage::applyToLayerImages(const std::function& consumer) { - if (!foregroundImage.isNull()) { - consumer(foregroundImage); - } - if (!backgroundImage.isNull()) { - consumer(backgroundImage); - } - if (!originalBackgroundImage.isNull()) { - consumer(originalBackgroundImage); - } + if (!m_foregroundImage.isNull()) { + consumer(m_foregroundImage); + } + if (!m_backgroundImage.isNull()) { + consumer(m_backgroundImage); + } + if (!m_originalBackgroundImage.isNull()) { + consumer(m_originalBackgroundImage); + } } bool SplitImage::isNull() const { - return (foregroundImage.isNull() && mask.isNull()) || backgroundImage.isNull(); + return (m_foregroundImage.isNull() && m_mask.isNull()) || m_backgroundImage.isNull(); } -void SplitImage::setMask(const BinaryImage& mask, bool binaryForeground) { - foregroundImage = QImage(); - SplitImage::mask = mask; - SplitImage::binaryForeground = binaryForeground; +void SplitImage::setMask(const BinaryImage& mask, const ForegroundType foregroundType) { + m_foregroundImage = QImage(); + m_mask = mask; + m_foregroundType = foregroundType; } const QImage& SplitImage::getOriginalBackgroundImage() const { - return originalBackgroundImage; + return m_originalBackgroundImage; } void SplitImage::setOriginalBackgroundImage(const QImage& originalBackgroundImage) { - SplitImage::originalBackgroundImage = originalBackgroundImage; -} - -void SplitImage::setIndexedForeground(bool indexedForeground) { - SplitImage::indexedForeground = indexedForeground; + m_originalBackgroundImage = originalBackgroundImage; } } // namespace output diff --git a/filters/output/SplitImage.h b/filters/output/SplitImage.h index ac116fca0..c2834b807 100644 --- a/filters/output/SplitImage.h +++ b/filters/output/SplitImage.h @@ -3,10 +3,10 @@ #define SCANTAILOR_SPLITIMAGE_H +#include #include -#include #include -#include +#include namespace output { @@ -20,42 +20,45 @@ namespace output { * SplitImage::getBackgroundImage() to get the image layers. */ class SplitImage { -public: - SplitImage(); + public: + enum ForegroundType { + COLOR_FOREGROUND, + BINARY_FOREGROUND, + INDEXED_FOREGROUND + }; - SplitImage(const QImage& foreground, const QImage& background); + SplitImage(); - SplitImage(const QImage& foreground, const QImage& background, const QImage& originalBackground); + SplitImage(const QImage& foreground, const QImage& background); - QImage toImage() const; + SplitImage(const QImage& foreground, const QImage& background, const QImage& originalBackground); - QImage getForegroundImage() const; + QImage toImage() const; - void setForegroundImage(const QImage& foregroundImage); + QImage getForegroundImage() const; - QImage getBackgroundImage() const; + void setForegroundImage(const QImage& foregroundImage); - void setBackgroundImage(const QImage& backgroundImage); + QImage getBackgroundImage() const; - void setMask(const imageproc::BinaryImage& mask, bool binaryForeground); + void setBackgroundImage(const QImage& backgroundImage); - void applyToLayerImages(const std::function& consumer); + void setMask(const imageproc::BinaryImage& mask, ForegroundType foregroundType); - bool isNull() const; + void applyToLayerImages(const std::function& consumer); - const QImage& getOriginalBackgroundImage() const; + bool isNull() const; - void setOriginalBackgroundImage(const QImage& originalBackgroundImage); + const QImage& getOriginalBackgroundImage() const; - void setIndexedForeground(bool indexedForeground); + void setOriginalBackgroundImage(const QImage& originalBackgroundImage); -private: - bool binaryForeground; - bool indexedForeground; - imageproc::BinaryImage mask; - QImage foregroundImage; - QImage backgroundImage; - QImage originalBackgroundImage; + private: + imageproc::BinaryImage m_mask; + QImage m_foregroundImage; + QImage m_backgroundImage; + QImage m_originalBackgroundImage; + ForegroundType m_foregroundType = COLOR_FOREGROUND; }; } // namespace output diff --git a/filters/output/SplittingOptions.cpp b/filters/output/SplittingOptions.cpp index ce0d242d9..d3ab0e08a 100644 --- a/filters/output/SplittingOptions.cpp +++ b/filters/output/SplittingOptions.cpp @@ -4,76 +4,74 @@ namespace output { SplittingOptions::SplittingOptions() - : splitOutput(false), splittingMode(BLACK_AND_WHITE_FOREGROUND), originalBackground(false) { -} + : m_isSplitOutput(false), m_splittingMode(BLACK_AND_WHITE_FOREGROUND), m_isOriginalBackgroundEnabled(false) {} SplittingOptions::SplittingOptions(const QDomElement& el) - : splitOutput(el.attribute("splitOutput") == "1"), - splittingMode(parseSplittingMode(el.attribute("splittingMode"))), - originalBackground(el.attribute("originalBackground") == "1") { -} + : m_isSplitOutput(el.attribute("splitOutput") == "1"), + m_splittingMode(parseSplittingMode(el.attribute("splittingMode"))), + m_isOriginalBackgroundEnabled(el.attribute("originalBackground") == "1") {} QDomElement SplittingOptions::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("splitOutput", splitOutput ? "1" : "0"); - el.setAttribute("splittingMode", formatSplittingMode(splittingMode)); - el.setAttribute("originalBackground", originalBackground ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.setAttribute("splitOutput", m_isSplitOutput ? "1" : "0"); + el.setAttribute("splittingMode", formatSplittingMode(m_splittingMode)); + el.setAttribute("originalBackground", m_isOriginalBackgroundEnabled ? "1" : "0"); - return el; + return el; } bool SplittingOptions::isSplitOutput() const { - return splitOutput; + return m_isSplitOutput; } void SplittingOptions::setSplitOutput(bool splitOutput) { - SplittingOptions::splitOutput = splitOutput; + SplittingOptions::m_isSplitOutput = splitOutput; } SplittingMode SplittingOptions::getSplittingMode() const { - return splittingMode; + return m_splittingMode; } void SplittingOptions::setSplittingMode(SplittingMode foregroundType) { - SplittingOptions::splittingMode = foregroundType; + SplittingOptions::m_splittingMode = foregroundType; } -bool SplittingOptions::isOriginalBackground() const { - return originalBackground; +bool SplittingOptions::isOriginalBackgroundEnabled() const { + return m_isOriginalBackgroundEnabled; } -void SplittingOptions::setOriginalBackground(bool originalBackground) { - SplittingOptions::originalBackground = originalBackground; +void SplittingOptions::setOriginalBackgroundEnabled(bool enable) { + SplittingOptions::m_isOriginalBackgroundEnabled = enable; } SplittingMode SplittingOptions::parseSplittingMode(const QString& str) { - if (str == "color") { - return COLOR_FOREGROUND; - } else { - return BLACK_AND_WHITE_FOREGROUND; - } + if (str == "color") { + return COLOR_FOREGROUND; + } else { + return BLACK_AND_WHITE_FOREGROUND; + } } QString SplittingOptions::formatSplittingMode(const SplittingMode type) { - QString str = ""; - switch (type) { - case BLACK_AND_WHITE_FOREGROUND: - str = "bw"; - break; - case COLOR_FOREGROUND: - str = "color"; - break; - } - - return str; + QString str = ""; + switch (type) { + case BLACK_AND_WHITE_FOREGROUND: + str = "bw"; + break; + case COLOR_FOREGROUND: + str = "color"; + break; + } + + return str; } bool SplittingOptions::operator==(const SplittingOptions& other) const { - return (splitOutput == other.splitOutput) && (splittingMode == other.splittingMode) - && (originalBackground == other.originalBackground); + return (m_isSplitOutput == other.m_isSplitOutput) && (m_splittingMode == other.m_splittingMode) + && (m_isOriginalBackgroundEnabled == other.m_isOriginalBackgroundEnabled); } bool SplittingOptions::operator!=(const SplittingOptions& other) const { - return !(*this == other); + return !(*this == other); } } // namespace output \ No newline at end of file diff --git a/filters/output/SplittingOptions.h b/filters/output/SplittingOptions.h index 1ad340def..2f27494e7 100644 --- a/filters/output/SplittingOptions.h +++ b/filters/output/SplittingOptions.h @@ -8,38 +8,37 @@ namespace output { enum SplittingMode { BLACK_AND_WHITE_FOREGROUND, COLOR_FOREGROUND }; class SplittingOptions { -public: - SplittingOptions(); + public: + SplittingOptions(); - explicit SplittingOptions(const QDomElement& el); + explicit SplittingOptions(const QDomElement& el); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool isSplitOutput() const; + bool isSplitOutput() const; - void setSplitOutput(bool splitOutput); + void setSplitOutput(bool splitOutput); - SplittingMode getSplittingMode() const; + SplittingMode getSplittingMode() const; - void setSplittingMode(SplittingMode foregroundType); + void setSplittingMode(SplittingMode foregroundType); - bool operator==(const SplittingOptions& other) const; + bool operator==(const SplittingOptions& other) const; - bool operator!=(const SplittingOptions& other) const; + bool operator!=(const SplittingOptions& other) const; - bool isOriginalBackground() const; + bool isOriginalBackgroundEnabled() const; - void setOriginalBackground(bool originalBackground); + void setOriginalBackgroundEnabled(bool enable); -private: - static SplittingMode parseSplittingMode(const QString& str); + private: + static SplittingMode parseSplittingMode(const QString& str); - static QString formatSplittingMode(SplittingMode type); + static QString formatSplittingMode(SplittingMode type); - - bool splitOutput; - SplittingMode splittingMode; - bool originalBackground; + bool m_isSplitOutput; + SplittingMode m_splittingMode; + bool m_isOriginalBackgroundEnabled; }; } // namespace output diff --git a/filters/output/TabbedImageView.cpp b/filters/output/TabbedImageView.cpp index f84739d28..c4afb4077 100644 --- a/filters/output/TabbedImageView.cpp +++ b/filters/output/TabbedImageView.cpp @@ -16,157 +16,156 @@ along with this program. If not, see . */ -#include #include -#include "DespeckleView.h" +#include #include #include "../../Utils.h" +#include "DespeckleView.h" namespace output { TabbedImageView::TabbedImageView(QWidget* parent) : QTabWidget(parent), m_prevImageViewTabIndex(0) { - connect(this, SIGNAL(currentChanged(int)), SLOT(tabChangedSlot(int))); - setStatusTip(tr("Use Ctrl+1..5 to switch the tabs.")); + connect(this, SIGNAL(currentChanged(int)), SLOT(tabChangedSlot(int))); + setStatusTip(tr("Use Ctrl+1..5 to switch the tabs.")); } void TabbedImageView::addTab(QWidget* widget, const QString& label, ImageViewTab tab) { - QTabWidget::addTab(widget, label); - m_registry[widget] = tab; + QTabWidget::addTab(widget, label); + m_registry[widget] = tab; - if (auto* despeckleView = dynamic_cast(widget)) { - connect(despeckleView, &DespeckleView::imageViewCreated, - [this](ImageViewBase*) { copyViewZoomAndPos(m_prevImageViewTabIndex, currentIndex()); }); - } + if (auto* despeckleView = dynamic_cast(widget)) { + connect(despeckleView, &DespeckleView::imageViewCreated, + [this](ImageViewBase*) { copyViewZoomAndPos(m_prevImageViewTabIndex, currentIndex()); }); + } } void TabbedImageView::setCurrentTab(const ImageViewTab tab) { - const int cnt = count(); - for (int i = 0; i < cnt; ++i) { - QWidget* wgt = widget(i); - auto it = m_registry.find(wgt); - if (it != m_registry.end()) { - if (it->second == tab) { - setCurrentIndex(i); - break; - } - } + const int cnt = count(); + for (int i = 0; i < cnt; ++i) { + QWidget* wgt = widget(i); + auto it = m_registry.find(wgt); + if (it != m_registry.end()) { + if (it->second == tab) { + setCurrentIndex(i); + break; + } } + } } void TabbedImageView::tabChangedSlot(const int idx) { - QWidget* wgt = widget(idx); - auto it = m_registry.find(wgt); - if (it != m_registry.end()) { - emit tabChanged(it->second); - } + QWidget* wgt = widget(idx); + auto it = m_registry.find(wgt); + if (it != m_registry.end()) { + emit tabChanged(it->second); + } - copyViewZoomAndPos(m_prevImageViewTabIndex, idx); + copyViewZoomAndPos(m_prevImageViewTabIndex, idx); - if (Utils::castOrFindChild(widget(idx)) != nullptr) { - m_prevImageViewTabIndex = idx; - } + if (Utils::castOrFindChild(widget(idx)) != nullptr) { + m_prevImageViewTabIndex = idx; + } } void TabbedImageView::setImageRectMap(std::unique_ptr tab_image_rect_map) { - m_tabImageRectMap = std::move(tab_image_rect_map); + m_tabImageRectMap = std::move(tab_image_rect_map); } void TabbedImageView::copyViewZoomAndPos(const int old_idx, const int new_idx) const { - if (m_tabImageRectMap == nullptr) { - return; - } - - if ((m_registry.find(widget(old_idx)) == m_registry.end()) - || (m_registry.find(widget(new_idx)) == m_registry.end())) { - return; - } - const ImageViewTab old_view_tab = m_registry.at(widget(old_idx)); - const ImageViewTab new_view_tab = m_registry.at(widget(new_idx)); - - if ((m_tabImageRectMap->find(old_view_tab) == m_tabImageRectMap->end()) - || (m_tabImageRectMap->find(new_view_tab) == m_tabImageRectMap->end())) { - return; - } - const QRectF& old_view_rect = m_tabImageRectMap->at(old_view_tab); - const QRectF& new_view_rect = m_tabImageRectMap->at(new_view_tab); - - auto* old_image_view = Utils::castOrFindChild(widget(old_idx)); - auto* new_image_view = Utils::castOrFindChild(widget(new_idx)); - if ((old_image_view == nullptr) || (new_image_view == nullptr)) { - return; - } - if (old_image_view == new_image_view) { - return; - } - - if (old_image_view->zoomLevel() != 1.0) { - const QPointF view_focus - = getFocus(old_view_rect, *old_image_view->horizontalScrollBar(), *old_image_view->verticalScrollBar()); - const double zoom_factor = std::max(new_view_rect.width(), new_view_rect.height()) - / std::max(old_view_rect.width(), old_view_rect.height()); - new_image_view->setZoomLevel(qMax(1., old_image_view->zoomLevel() * zoom_factor)); - setFocus(*new_image_view->horizontalScrollBar(), *new_image_view->verticalScrollBar(), new_view_rect, - view_focus); - } + if (m_tabImageRectMap == nullptr) { + return; + } + + if ((m_registry.find(widget(old_idx)) == m_registry.end()) + || (m_registry.find(widget(new_idx)) == m_registry.end())) { + return; + } + const ImageViewTab old_view_tab = m_registry.at(widget(old_idx)); + const ImageViewTab new_view_tab = m_registry.at(widget(new_idx)); + + if ((m_tabImageRectMap->find(old_view_tab) == m_tabImageRectMap->end()) + || (m_tabImageRectMap->find(new_view_tab) == m_tabImageRectMap->end())) { + return; + } + const QRectF& old_view_rect = m_tabImageRectMap->at(old_view_tab); + const QRectF& new_view_rect = m_tabImageRectMap->at(new_view_tab); + + auto* old_image_view = Utils::castOrFindChild(widget(old_idx)); + auto* new_image_view = Utils::castOrFindChild(widget(new_idx)); + if ((old_image_view == nullptr) || (new_image_view == nullptr)) { + return; + } + if (old_image_view == new_image_view) { + return; + } + + if (old_image_view->zoomLevel() != 1.0) { + const QPointF view_focus + = getFocus(old_view_rect, *old_image_view->horizontalScrollBar(), *old_image_view->verticalScrollBar()); + const double zoom_factor = std::max(new_view_rect.width(), new_view_rect.height()) + / std::max(old_view_rect.width(), old_view_rect.height()); + new_image_view->setZoomLevel(qMax(1., old_image_view->zoomLevel() * zoom_factor)); + setFocus(*new_image_view->horizontalScrollBar(), *new_image_view->verticalScrollBar(), new_view_rect, view_focus); + } } QPointF TabbedImageView::getFocus(const QRectF& rect, const QScrollBar& hor_bar, const QScrollBar& ver_bar) const { - const int hor_bar_length = hor_bar.maximum() - hor_bar.minimum() + hor_bar.pageStep(); - const int ver_bar_length = ver_bar.maximum() - ver_bar.minimum() + ver_bar.pageStep(); + const int hor_bar_length = hor_bar.maximum() - hor_bar.minimum() + hor_bar.pageStep(); + const int ver_bar_length = ver_bar.maximum() - ver_bar.minimum() + ver_bar.pageStep(); - qreal x = ((hor_bar.value() + (hor_bar.pageStep() / 2.0)) / hor_bar_length) * rect.width() + rect.left(); - qreal y = ((ver_bar.value() + (ver_bar.pageStep() / 2.0)) / ver_bar_length) * rect.height() + rect.top(); + qreal x = ((hor_bar.value() + (hor_bar.pageStep() / 2.0)) / hor_bar_length) * rect.width() + rect.left(); + qreal y = ((ver_bar.value() + (ver_bar.pageStep() / 2.0)) / ver_bar_length) * rect.height() + rect.top(); - return QPointF(x, y); + return QPointF(x, y); } void TabbedImageView::setFocus(QScrollBar& hor_bar, QScrollBar& ver_bar, const QRectF& rect, const QPointF& focal) const { - const int hor_bar_length = hor_bar.maximum() - hor_bar.minimum() + hor_bar.pageStep(); - const int ver_bar_length = ver_bar.maximum() - ver_bar.minimum() + ver_bar.pageStep(); + const int hor_bar_length = hor_bar.maximum() - hor_bar.minimum() + hor_bar.pageStep(); + const int ver_bar_length = ver_bar.maximum() - ver_bar.minimum() + ver_bar.pageStep(); - auto hor_value = (int) std::round(((focal.x() - rect.left()) / rect.width()) * hor_bar_length - - (hor_bar.pageStep() / 2.0)); - auto ver_value = (int) std::round(((focal.y() - rect.top()) / rect.height()) * ver_bar_length - - (ver_bar.pageStep() / 2.0)); + auto hor_value + = (int) std::round(((focal.x() - rect.left()) / rect.width()) * hor_bar_length - (hor_bar.pageStep() / 2.0)); + auto ver_value + = (int) std::round(((focal.y() - rect.top()) / rect.height()) * ver_bar_length - (ver_bar.pageStep() / 2.0)); - hor_value = qBound(hor_bar.minimum(), hor_value, hor_bar.maximum()); - ver_value = qBound(ver_bar.minimum(), ver_value, ver_bar.maximum()); + hor_value = qBound(hor_bar.minimum(), hor_value, hor_bar.maximum()); + ver_value = qBound(ver_bar.minimum(), ver_value, ver_bar.maximum()); - hor_bar.setValue(hor_value); - ver_bar.setValue(ver_value); + hor_bar.setValue(hor_value); + ver_bar.setValue(ver_value); } void TabbedImageView::keyReleaseEvent(QKeyEvent* event) { - event->setAccepted(false); - if (event->modifiers() != Qt::ControlModifier) { - return; - } - - switch (event->key()) { - case Qt::Key_1: - setCurrentIndex(0); - event->accept(); - break; - case Qt::Key_2: - setCurrentIndex(1); - event->accept(); - break; - case Qt::Key_3: - setCurrentIndex(2); - event->accept(); - break; - case Qt::Key_4: - setCurrentIndex(3); - event->accept(); - break; - case Qt::Key_5: - setCurrentIndex(4); - event->accept(); - break; - default: - break; - } + event->setAccepted(false); + if (event->modifiers() != Qt::ControlModifier) { + return; + } + + switch (event->key()) { + case Qt::Key_1: + setCurrentIndex(0); + event->accept(); + break; + case Qt::Key_2: + setCurrentIndex(1); + event->accept(); + break; + case Qt::Key_3: + setCurrentIndex(2); + event->accept(); + break; + case Qt::Key_4: + setCurrentIndex(3); + event->accept(); + break; + case Qt::Key_5: + setCurrentIndex(4); + event->accept(); + break; + default: + break; + } } } // namespace output diff --git a/filters/output/TabbedImageView.h b/filters/output/TabbedImageView.h index 9ee770876..b29b27a18 100644 --- a/filters/output/TabbedImageView.h +++ b/filters/output/TabbedImageView.h @@ -19,53 +19,53 @@ #ifndef OUTPUT_TABBED_IMAGE_VIEW_H_ #define OUTPUT_TABBED_IMAGE_VIEW_H_ -#include "ImageViewTab.h" -#include -#include #include -#include +#include +#include #include +#include +#include "ImageViewTab.h" class ImageViewBase; namespace output { class TabbedImageView : public QTabWidget { - Q_OBJECT -private: - typedef std::unordered_map> TabImageRectMap; + Q_OBJECT + private: + typedef std::unordered_map> TabImageRectMap; -public: - explicit TabbedImageView(QWidget* parent = nullptr); + public: + explicit TabbedImageView(QWidget* parent = nullptr); - void addTab(QWidget* widget, const QString& label, ImageViewTab tab); + void addTab(QWidget* widget, const QString& label, ImageViewTab tab); - void setImageRectMap(std::unique_ptr tab_image_rect_map); + void setImageRectMap(std::unique_ptr tab_image_rect_map); -public slots: + public slots: - void setCurrentTab(ImageViewTab tab); + void setCurrentTab(ImageViewTab tab); -signals: + signals: - void tabChanged(ImageViewTab tab); + void tabChanged(ImageViewTab tab); -protected: - void keyReleaseEvent(QKeyEvent* event) override; + protected: + void keyReleaseEvent(QKeyEvent* event) override; -private slots: + private slots: - void tabChangedSlot(int idx); + void tabChangedSlot(int idx); -private: - void copyViewZoomAndPos(int old_idx, int new_idx) const; + private: + void copyViewZoomAndPos(int old_idx, int new_idx) const; - QPointF getFocus(const QRectF& rect, const QScrollBar& hor_bar, const QScrollBar& ver_bar) const; + QPointF getFocus(const QRectF& rect, const QScrollBar& hor_bar, const QScrollBar& ver_bar) const; - void setFocus(QScrollBar& hor_bar, QScrollBar& ver_bar, const QRectF& rect, const QPointF& focal) const; + void setFocus(QScrollBar& hor_bar, QScrollBar& ver_bar, const QRectF& rect, const QPointF& focal) const; - std::unordered_map m_registry; - std::unique_ptr m_tabImageRectMap; - int m_prevImageViewTabIndex; + std::unordered_map m_registry; + std::unique_ptr m_tabImageRectMap; + int m_prevImageViewTabIndex; }; } // namespace output #endif // ifndef OUTPUT_TABBED_IMAGE_VIEW_H_ diff --git a/filters/output/Task.cpp b/filters/output/Task.cpp index 6fc8f3dfc..ade9d3562 100644 --- a/filters/output/Task.cpp +++ b/filters/output/Task.cpp @@ -16,86 +16,84 @@ along with this program. If not, see . */ -#include "CommandLine.h" #include "Task.h" -#include "Filter.h" -#include "OptionsWidget.h" -#include "Settings.h" -#include "RenderParams.h" -#include "FilterUiInterface.h" -#include "TaskStatus.h" -#include "FilterData.h" -#include "ImageView.h" -#include "TabbedImageView.h" -#include "PictureZoneComparator.h" -#include "PictureZoneEditor.h" -#include "FillZoneComparator.h" -#include "FillZoneEditor.h" +#include +#include +#include +#include +#include "CommandLine.h" +#include "DebugImages.h" #include "DespeckleState.h" #include "DespeckleView.h" #include "DespeckleVisualization.h" -#include "dewarping/DewarpingPointMapper.h" #include "DewarpingView.h" #include "Dpm.h" -#include "Utils.h" -#include "ThumbnailPixmapCache.h" -#include "DebugImages.h" -#include "OutputGenerator.h" -#include "ImageLoader.h" #include "ErrorWidget.h" +#include "FillZoneComparator.h" +#include "FillZoneEditor.h" +#include "Filter.h" +#include "FilterData.h" +#include "FilterUiInterface.h" +#include "ImageLoader.h" +#include "ImageView.h" +#include "OptionsWidget.h" +#include "OutputGenerator.h" +#include "PictureZoneComparator.h" +#include "PictureZoneEditor.h" +#include "RenderParams.h" +#include "Settings.h" +#include "TabbedImageView.h" +#include "TaskStatus.h" +#include "ThumbnailPixmapCache.h" +#include "Utils.h" +#include "dewarping/DewarpingPointMapper.h" #include "imageproc/PolygonUtils.h" -#include -#include -#include -#include using namespace imageproc; using namespace dewarping; namespace output { class Task::UiUpdater : public FilterResult { - Q_DECLARE_TR_FUNCTIONS(output::Task::UiUpdater) -public: - UiUpdater(intrusive_ptr filter, - intrusive_ptr settings, - std::unique_ptr dbg_img, - const Params& params, - const ImageTransformation& xform, - const QTransform& postTransform, - const QRect& virt_content_rect, - const PageId& page_id, - const QImage& orig_image, - const QImage& output_image, - const BinaryImage& picture_mask, - const DespeckleState& despeckle_state, - const DespeckleVisualization& despeckle_visualization, - bool batch, - bool debug); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrSettings; - std::unique_ptr m_ptrDbg; - Params m_params; - ImageTransformation m_xform; - QTransform m_outputPostTransform; - QRect m_virtContentRect; - PageId m_pageId; - QImage m_origImage; - QImage m_downscaledOrigImage; - QImage m_outputImage; - QImage m_downscaledOutputImage; - BinaryImage m_pictureMask; - DespeckleState m_despeckleState; - DespeckleVisualization m_despeckleVisualization; - bool m_batchProcessing; - bool m_debug; + Q_DECLARE_TR_FUNCTIONS(output::Task::UiUpdater) + public: + UiUpdater(intrusive_ptr filter, + intrusive_ptr settings, + std::unique_ptr dbg_img, + const Params& params, + const ImageTransformation& xform, + const QTransform& postTransform, + const QRect& virt_content_rect, + const PageId& page_id, + const QImage& orig_image, + const QImage& output_image, + const BinaryImage& picture_mask, + const DespeckleState& despeckle_state, + const DespeckleVisualization& despeckle_visualization, + bool batch, + bool debug); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + intrusive_ptr m_settings; + std::unique_ptr m_dbg; + Params m_params; + ImageTransformation m_xform; + QTransform m_outputPostTransform; + QRect m_virtContentRect; + PageId m_pageId; + QImage m_origImage; + QImage m_downscaledOrigImage; + QImage m_outputImage; + QImage m_downscaledOutputImage; + BinaryImage m_pictureMask; + DespeckleState m_despeckleState; + DespeckleVisualization m_despeckleVisualization; + bool m_batchProcessing; + bool m_debug; }; @@ -107,369 +105,372 @@ Task::Task(intrusive_ptr filter, const ImageViewTab last_tab, const bool batch, const bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrSettings(std::move(settings)), - m_ptrThumbnailCache(std::move(thumbnail_cache)), - m_pageId(page_id), - m_outFileNameGen(out_file_name_gen), - m_lastTab(last_tab), - m_batchProcessing(batch), - m_debug(debug) { - if (debug) { - m_ptrDbg = std::make_unique(); - } + : m_filter(std::move(filter)), + m_settings(std::move(settings)), + m_thumbnailCache(std::move(thumbnail_cache)), + m_pageId(page_id), + m_outFileNameGen(out_file_name_gen), + m_lastTab(last_tab), + m_batchProcessing(batch), + m_debug(debug) { + if (debug) { + m_dbg = std::make_unique(); + } } Task::~Task() = default; FilterResultPtr Task::process(const TaskStatus& status, const FilterData& data, const QPolygonF& content_rect_phys) { - status.throwIfCancelled(); - - Params params(m_ptrSettings->getParams(m_pageId)); - - RenderParams render_params(params.colorParams(), params.splittingOptions()); - const QString out_file_path(m_outFileNameGen.filePathFor(m_pageId)); - const QFileInfo out_file_info(out_file_path); - - ImageTransformation new_xform(data.xform()); - new_xform.postScaleToDpi(params.outputDpi()); - - const QString foreground_dir(Utils::foregroundDir(m_outFileNameGen.outDir())); - const QString background_dir(Utils::backgroundDir(m_outFileNameGen.outDir())); - const QString original_background_dir(Utils::originalBackgroundDir(m_outFileNameGen.outDir())); - const QString foreground_file_path(QDir(foreground_dir).absoluteFilePath(out_file_info.fileName())); - const QString background_file_path(QDir(background_dir).absoluteFilePath(out_file_info.fileName())); - const QString original_background_file_path( - QDir(original_background_dir).absoluteFilePath(out_file_info.fileName())); - const QFileInfo foreground_file_info(foreground_file_path); - const QFileInfo background_file_info(background_file_path); - const QFileInfo original_background_file_info(original_background_file_path); - - const QString automask_dir(Utils::automaskDir(m_outFileNameGen.outDir())); - const QString automask_file_path(QDir(automask_dir).absoluteFilePath(out_file_info.fileName())); - QFileInfo automask_file_info(automask_file_path); - - const QString speckles_dir(Utils::specklesDir(m_outFileNameGen.outDir())); - const QString speckles_file_path(QDir(speckles_dir).absoluteFilePath(out_file_info.fileName())); - QFileInfo speckles_file_info(speckles_file_path); - - const bool need_picture_editor = render_params.mixedOutput() && !m_batchProcessing; - const bool need_speckles_image - = params.despeckleLevel() != DESPECKLE_OFF && render_params.needBinarization() && !m_batchProcessing; - - { - std::unique_ptr stored_output_params(m_ptrSettings->getOutputParams(m_pageId)); - if (stored_output_params != nullptr) { - if (stored_output_params->outputImageParams().getPictureShapeOptions() != params.pictureShapeOptions()) { - // if picture shape options changed, reset auto picture zones - OutputProcessingParams outputProcessingParams = m_ptrSettings->getOutputProcessingParams(m_pageId); - outputProcessingParams.setAutoZonesFound(false); - m_ptrSettings->setOutputProcessingParams(m_pageId, outputProcessingParams); - } - } + status.throwIfCancelled(); + + Params params(m_settings->getParams(m_pageId)); + + RenderParams render_params(params.colorParams(), params.splittingOptions()); + const QString out_file_path(m_outFileNameGen.filePathFor(m_pageId)); + const QFileInfo out_file_info(out_file_path); + + ImageTransformation new_xform(data.xform()); + new_xform.postScaleToDpi(params.outputDpi()); + + const QString foreground_dir(Utils::foregroundDir(m_outFileNameGen.outDir())); + const QString background_dir(Utils::backgroundDir(m_outFileNameGen.outDir())); + const QString original_background_dir(Utils::originalBackgroundDir(m_outFileNameGen.outDir())); + const QString foreground_file_path(QDir(foreground_dir).absoluteFilePath(out_file_info.fileName())); + const QString background_file_path(QDir(background_dir).absoluteFilePath(out_file_info.fileName())); + const QString original_background_file_path(QDir(original_background_dir).absoluteFilePath(out_file_info.fileName())); + const QFileInfo foreground_file_info(foreground_file_path); + const QFileInfo background_file_info(background_file_path); + const QFileInfo original_background_file_info(original_background_file_path); + + const QString automask_dir(Utils::automaskDir(m_outFileNameGen.outDir())); + const QString automask_file_path(QDir(automask_dir).absoluteFilePath(out_file_info.fileName())); + QFileInfo automask_file_info(automask_file_path); + + const QString speckles_dir(Utils::specklesDir(m_outFileNameGen.outDir())); + const QString speckles_file_path(QDir(speckles_dir).absoluteFilePath(out_file_info.fileName())); + QFileInfo speckles_file_info(speckles_file_path); + + const bool need_picture_editor = render_params.mixedOutput() && !m_batchProcessing; + const bool need_speckles_image + = params.despeckleLevel() != DESPECKLE_OFF && render_params.needBinarization() && !m_batchProcessing; + + { + std::unique_ptr stored_output_params(m_settings->getOutputParams(m_pageId)); + if (stored_output_params != nullptr) { + if (stored_output_params->outputImageParams().getPictureShapeOptions() != params.pictureShapeOptions()) { + // if picture shape options changed, reset auto picture zones + OutputProcessingParams outputProcessingParams = m_settings->getOutputProcessingParams(m_pageId); + outputProcessingParams.setAutoZonesFound(false); + m_settings->setOutputProcessingParams(m_pageId, outputProcessingParams); + } } + } - OutputGenerator generator(params.outputDpi(), params.colorParams(), params.splittingOptions(), - params.pictureShapeOptions(), params.dewarpingOptions(), - m_ptrSettings->getOutputProcessingParams(m_pageId), params.despeckleLevel(), new_xform, - content_rect_phys); + OutputGenerator generator(params.outputDpi(), params.colorParams(), params.splittingOptions(), + params.pictureShapeOptions(), params.dewarpingOptions(), + m_settings->getOutputProcessingParams(m_pageId), params.despeckleLevel(), new_xform, + content_rect_phys); - OutputImageParams new_output_image_params( - generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), - params.colorParams(), params.splittingOptions(), params.dewarpingOptions(), params.distortionModel(), - params.depthPerception(), params.despeckleLevel(), params.pictureShapeOptions(), - m_ptrSettings->getOutputProcessingParams(m_pageId)); + OutputImageParams new_output_image_params( + generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), params.colorParams(), + params.splittingOptions(), params.dewarpingOptions(), params.distortionModel(), params.depthPerception(), + params.despeckleLevel(), params.pictureShapeOptions(), m_settings->getOutputProcessingParams(m_pageId), + params.isBlackOnWhite()); - ZoneSet new_picture_zones(m_ptrSettings->pictureZonesForPage(m_pageId)); - const ZoneSet new_fill_zones(m_ptrSettings->fillZonesForPage(m_pageId)); + ZoneSet new_picture_zones(m_settings->pictureZonesForPage(m_pageId)); + const ZoneSet new_fill_zones(m_settings->fillZonesForPage(m_pageId)); - bool need_reprocess = false; - do { // Just to be able to break from it. - std::unique_ptr stored_output_params(m_ptrSettings->getOutputParams(m_pageId)); - - if (!stored_output_params) { - need_reprocess = true; - break; - } + bool need_reprocess = false; + do { // Just to be able to break from it. + std::unique_ptr stored_output_params(m_settings->getOutputParams(m_pageId)); - if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { - need_reprocess = true; - break; - } + if (!stored_output_params) { + need_reprocess = true; + break; + } - if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { - need_reprocess = true; - break; - } + if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { + need_reprocess = true; + break; + } - if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { - need_reprocess = true; - break; - } + if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { + need_reprocess = true; + break; + } - if (!render_params.splitOutput()) { - if (!out_file_info.exists()) { - need_reprocess = true; - break; - } - - if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { - need_reprocess = true; - break; - } - } else { - if (!foreground_file_info.exists() || !background_file_info.exists()) { - need_reprocess = true; - break; - } - if (!(stored_output_params->foregroundFileParams().matches(OutputFileParams(foreground_file_info))) - || !(stored_output_params->backgroundFileParams().matches(OutputFileParams(background_file_info)))) { - need_reprocess = true; - break; - } - - if (render_params.originalBackground()) { - if (!original_background_file_info.exists()) { - need_reprocess = true; - break; - } - if (!(stored_output_params->originalBackgroundFileParams().matches( - OutputFileParams(original_background_file_info)))) { - need_reprocess = true; - break; - } - } - } + if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { + need_reprocess = true; + break; + } - if (need_picture_editor) { - if (!automask_file_info.exists()) { - need_reprocess = true; - break; - } + if (!render_params.splitOutput()) { + if (!out_file_info.exists()) { + need_reprocess = true; + break; + } - if (!stored_output_params->automaskFileParams().matches(OutputFileParams(automask_file_info))) { - need_reprocess = true; - break; - } + if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { + need_reprocess = true; + break; + } + } else { + if (!foreground_file_info.exists() || !background_file_info.exists()) { + need_reprocess = true; + break; + } + if (!(stored_output_params->foregroundFileParams().matches(OutputFileParams(foreground_file_info))) + || !(stored_output_params->backgroundFileParams().matches(OutputFileParams(background_file_info)))) { + need_reprocess = true; + break; + } + + if (render_params.originalBackground()) { + if (!original_background_file_info.exists()) { + need_reprocess = true; + break; } - - if (need_speckles_image) { - if (!speckles_file_info.exists()) { - need_reprocess = true; - break; - } - if (!stored_output_params->specklesFileParams().matches(OutputFileParams(speckles_file_info))) { - need_reprocess = true; - break; - } + if (!(stored_output_params->originalBackgroundFileParams().matches( + OutputFileParams(original_background_file_info)))) { + need_reprocess = true; + break; } - } while (false); + } + } - QImage out_img; - BinaryImage automask_img; - BinaryImage speckles_img; + if (need_picture_editor) { + if (!automask_file_info.exists()) { + need_reprocess = true; + break; + } - if (!need_reprocess) { - QFile out_file(out_file_path); - if (out_file.open(QIODevice::ReadOnly)) { - out_img = ImageLoader::load(out_file, 0); - } - if (out_img.isNull() && render_params.splitOutput()) { - QImage foreground_image; - QImage background_image; - QFile foreground_file(foreground_file_path); - QFile background_file(background_file_path); - if (foreground_file.open(QIODevice::ReadOnly)) { - foreground_image = ImageLoader::load(foreground_file, 0); - } - if (background_file.open(QIODevice::ReadOnly)) { - background_image = ImageLoader::load(background_file, 0); - } - - SplitImage tmpSplitImage; - if (!render_params.originalBackground()) { - tmpSplitImage = SplitImage(foreground_image, background_image); - } else { - QImage original_background_image; - QFile original_background_file(original_background_file_path); - if (original_background_file.open(QIODevice::ReadOnly)) { - original_background_image = ImageLoader::load(original_background_file, 0); - } - tmpSplitImage = SplitImage(foreground_image, background_image, original_background_image); - } - if (!tmpSplitImage.isNull()) { - out_img = tmpSplitImage.toImage(); - } - } - need_reprocess = out_img.isNull(); - - if (need_picture_editor && !need_reprocess) { - QFile automask_file(automask_file_path); - if (automask_file.open(QIODevice::ReadOnly)) { - automask_img = BinaryImage(ImageLoader::load(automask_file, 0)); - } - need_reprocess = automask_img.isNull() || automask_img.size() != out_img.size(); - } - - if (need_speckles_image && !need_reprocess) { - QFile speckles_file(speckles_file_path); - if (speckles_file.open(QIODevice::ReadOnly)) { - speckles_img = BinaryImage(ImageLoader::load(speckles_file, 0)); - } - need_reprocess = speckles_img.isNull(); - } + if (!stored_output_params->automaskFileParams().matches(OutputFileParams(automask_file_info))) { + need_reprocess = true; + break; + } } - if (need_reprocess) { - // Even in batch processing mode we should still write automask, because it - // will be needed when we view the results back in interactive mode. - // The same applies even more to speckles file, as we need it not only - // for visualization purposes, but also for re-doing despeckling at - // different levels without going through the whole output generation process. - const bool write_automask = render_params.mixedOutput(); - const bool write_speckles_file = params.despeckleLevel() != DESPECKLE_OFF && render_params.needBinarization(); - - automask_img = BinaryImage(); - speckles_img = BinaryImage(); - - // OutputGenerator will write a new distortion model - // there, if dewarping mode is AUTO. - DistortionModel distortion_model; - if (params.dewarpingOptions().dewarpingMode() == MANUAL) { - distortion_model = params.distortionModel(); - } - - SplitImage splitImage; + if (need_speckles_image) { + if (!speckles_file_info.exists()) { + need_reprocess = true; + break; + } + if (!stored_output_params->specklesFileParams().matches(OutputFileParams(speckles_file_info))) { + need_reprocess = true; + break; + } + } + } while (false); - out_img = generator.process(status, data, new_picture_zones, new_fill_zones, distortion_model, - params.depthPerception(), write_automask ? &automask_img : nullptr, - write_speckles_file ? &speckles_img : nullptr, m_ptrDbg.get(), m_pageId, - m_ptrSettings, &splitImage); + QImage out_img; + BinaryImage automask_img; + BinaryImage speckles_img; - if (((params.dewarpingOptions().dewarpingMode() == AUTO) && distortion_model.isValid()) - || ((params.dewarpingOptions().dewarpingMode() == MARGINAL) && distortion_model.isValid())) { - // A new distortion model was generated. - // We need to save it to be able to modify it manually. - params.setDistortionModel(distortion_model); - m_ptrSettings->setParams(m_pageId, params); - new_output_image_params.setDistortionModel(distortion_model); + if (!need_reprocess) { + QFile out_file(out_file_path); + if (out_file.open(QIODevice::ReadOnly)) { + out_img = ImageLoader::load(out_file, 0); + } + if (out_img.isNull() && render_params.splitOutput()) { + QImage foreground_image; + QImage background_image; + QFile foreground_file(foreground_file_path); + QFile background_file(background_file_path); + if (foreground_file.open(QIODevice::ReadOnly)) { + foreground_image = ImageLoader::load(foreground_file, 0); + } + if (background_file.open(QIODevice::ReadOnly)) { + background_image = ImageLoader::load(background_file, 0); + } + + SplitImage tmpSplitImage; + if (!render_params.originalBackground()) { + tmpSplitImage = SplitImage(foreground_image, background_image); + } else { + QImage original_background_image; + QFile original_background_file(original_background_file_path); + if (original_background_file.open(QIODevice::ReadOnly)) { + original_background_image = ImageLoader::load(original_background_file, 0); } + tmpSplitImage = SplitImage(foreground_image, background_image, original_background_image); + } + if (!tmpSplitImage.isNull()) { + out_img = tmpSplitImage.toImage(); + } + } + need_reprocess = out_img.isNull(); + + if (need_picture_editor && !need_reprocess) { + QFile automask_file(automask_file_path); + if (automask_file.open(QIODevice::ReadOnly)) { + automask_img = BinaryImage(ImageLoader::load(automask_file, 0)); + } + need_reprocess = automask_img.isNull() || automask_img.size() != out_img.size(); + } - // Saving refreshed output processing params. - new_output_image_params.setOutputProcessingParams(m_ptrSettings->getOutputProcessingParams(m_pageId)); + if (need_speckles_image && !need_reprocess) { + QFile speckles_file(speckles_file_path); + if (speckles_file.open(QIODevice::ReadOnly)) { + speckles_img = BinaryImage(ImageLoader::load(speckles_file, 0)); + } + need_reprocess = speckles_img.isNull(); + } + } + + if (need_reprocess) { + // Even in batch processing mode we should still write automask, because it + // will be needed when we view the results back in interactive mode. + // The same applies even more to speckles file, as we need it not only + // for visualization purposes, but also for re-doing despeckling at + // different levels without going through the whole output generation process. + const bool write_automask = render_params.mixedOutput(); + const bool write_speckles_file = params.despeckleLevel() != DESPECKLE_OFF && render_params.needBinarization(); + + automask_img = BinaryImage(); + speckles_img = BinaryImage(); + + // OutputGenerator will write a new distortion model + // there, if dewarping mode is AUTO. + DistortionModel distortion_model; + if (params.dewarpingOptions().dewarpingMode() == MANUAL) { + distortion_model = params.distortionModel(); + } - bool invalidate_params = false; + SplitImage splitImage; - if (render_params.splitOutput()) { - QDir().mkdir(foreground_dir); - QDir().mkdir(background_dir); + out_img + = generator.process(status, data, new_picture_zones, new_fill_zones, distortion_model, params.depthPerception(), + write_automask ? &automask_img : nullptr, write_speckles_file ? &speckles_img : nullptr, + m_dbg.get(), m_pageId, m_settings, &splitImage); - if (!TiffWriter::writeImage(foreground_file_path, splitImage.getForegroundImage()) - || !TiffWriter::writeImage(background_file_path, splitImage.getBackgroundImage())) { - invalidate_params = true; - } + if (((params.dewarpingOptions().dewarpingMode() == AUTO) || (params.dewarpingOptions().dewarpingMode() == MARGINAL)) + && distortion_model.isValid()) { + // A new distortion model was generated. + // We need to save it to be able to modify it manually. + params.setDistortionModel(distortion_model); + m_settings->setParams(m_pageId, params); + new_output_image_params.setDistortionModel(distortion_model); + } - if (render_params.originalBackground()) { - QDir().mkdir(original_background_dir); + // Saving refreshed params and output processing params. + new_output_image_params.setBlackOnWhite(m_settings->getParams(m_pageId).isBlackOnWhite()); + new_output_image_params.setOutputProcessingParams(m_settings->getOutputProcessingParams(m_pageId)); - if (!TiffWriter::writeImage(original_background_file_path, splitImage.getOriginalBackgroundImage())) { - invalidate_params = true; - } - } + bool invalidate_params = false; - out_img = splitImage.toImage(); - splitImage = SplitImage(); - } - if (!TiffWriter::writeImage(out_file_path, out_img)) { - invalidate_params = true; - } else { - deleteMutuallyExclusiveOutputFiles(); - } + if (render_params.splitOutput()) { + QDir().mkdir(foreground_dir); + QDir().mkdir(background_dir); - if (write_speckles_file && speckles_img.isNull()) { - // Even if despeckling didn't actually take place, we still need - // to write an empty speckles file. Making it a special case - // is simply not worth it. - BinaryImage(out_img.size(), WHITE).swap(speckles_img); - } + if (!TiffWriter::writeImage(foreground_file_path, splitImage.getForegroundImage()) + || !TiffWriter::writeImage(background_file_path, splitImage.getBackgroundImage())) { + invalidate_params = true; + } - if (write_automask) { - // Note that QDir::mkdir() will fail if the parent directory, - // that is $OUT/cache doesn't exist. We want that behaviour, - // as otherwise when loading a project from a different machine, - // a whole bunch of bogus directories would be created. - QDir().mkdir(automask_dir); - // Also note that QDir::mkdir() will fail if the directory already exists, - // so we ignore its return value here. - if (!TiffWriter::writeImage(automask_file_path, automask_img.toQImage())) { - invalidate_params = true; - } - } - if (write_speckles_file) { - if (!QDir().mkpath(speckles_dir)) { - invalidate_params = true; - } else if (!TiffWriter::writeImage(speckles_file_path, speckles_img.toQImage())) { - invalidate_params = true; - } - } + if (render_params.originalBackground()) { + QDir().mkdir(original_background_dir); - if (invalidate_params) { - m_ptrSettings->removeOutputParams(m_pageId); - } else { - // Note that we can't reuse *_file_info objects - // as we've just overwritten those files. - const OutputParams out_params( - new_output_image_params, OutputFileParams(QFileInfo(out_file_path)), - render_params.splitOutput() ? OutputFileParams(QFileInfo(foreground_file_path)) - : OutputFileParams(), - render_params.splitOutput() ? OutputFileParams(QFileInfo(background_file_path)) - : OutputFileParams(), - render_params.originalBackground() ? OutputFileParams(QFileInfo(original_background_file_path)) - : OutputFileParams(), - write_automask ? OutputFileParams(QFileInfo(automask_file_path)) : OutputFileParams(), - write_speckles_file ? OutputFileParams(QFileInfo(speckles_file_path)) : OutputFileParams(), - new_picture_zones, new_fill_zones); - - m_ptrSettings->setOutputParams(m_pageId, out_params); + if (!TiffWriter::writeImage(original_background_file_path, splitImage.getOriginalBackgroundImage())) { + invalidate_params = true; } + } - m_ptrThumbnailCache->recreateThumbnail(ImageId(out_file_path), out_img); + out_img = splitImage.toImage(); + splitImage = SplitImage(); + } else { + // Remove layers if the mode was changed. + QFile(foreground_file_path).remove(); + QFile(background_file_path).remove(); + QFile(original_background_file_path).remove(); + } + if (!TiffWriter::writeImage(out_file_path, out_img)) { + invalidate_params = true; + } else { + deleteMutuallyExclusiveOutputFiles(); } - const DespeckleState despeckle_state(out_img, speckles_img, params.despeckleLevel(), params.outputDpi()); + if (write_speckles_file && speckles_img.isNull()) { + // Even if despeckling didn't actually take place, we still need + // to write an empty speckles file. Making it a special case + // is simply not worth it. + BinaryImage(out_img.size(), WHITE).swap(speckles_img); + } - DespeckleVisualization despeckle_visualization; - if (m_lastTab == TAB_DESPECKLING) { - // Because constructing DespeckleVisualization takes a noticeable - // amount of time, we only do it if we are sure we'll need it. - // Otherwise it will get constructed on demand. - despeckle_visualization = despeckle_state.visualize(); + if (write_automask) { + // Note that QDir::mkdir() will fail if the parent directory, + // that is $OUT/cache doesn't exist. We want that behaviour, + // as otherwise when loading a project from a different machine, + // a whole bunch of bogus directories would be created. + QDir().mkdir(automask_dir); + // Also note that QDir::mkdir() will fail if the directory already exists, + // so we ignore its return value here. + if (!TiffWriter::writeImage(automask_file_path, automask_img.toQImage())) { + invalidate_params = true; + } + } + if (write_speckles_file) { + if (!QDir().mkpath(speckles_dir)) { + invalidate_params = true; + } else if (!TiffWriter::writeImage(speckles_file_path, speckles_img.toQImage())) { + invalidate_params = true; + } } - if (CommandLine::get().isGui()) { - return make_intrusive(m_ptrFilter, m_ptrSettings, std::move(m_ptrDbg), params, new_xform, - generator.getPostTransform(), generator.outputContentRect(), m_pageId, - data.origImage(), out_img, automask_img, despeckle_state, - despeckle_visualization, m_batchProcessing, m_debug); + if (invalidate_params) { + m_settings->removeOutputParams(m_pageId); } else { - return nullptr; + // Note that we can't reuse *_file_info objects + // as we've just overwritten those files. + const OutputParams out_params( + new_output_image_params, OutputFileParams(QFileInfo(out_file_path)), + render_params.splitOutput() ? OutputFileParams(QFileInfo(foreground_file_path)) : OutputFileParams(), + render_params.splitOutput() ? OutputFileParams(QFileInfo(background_file_path)) : OutputFileParams(), + render_params.originalBackground() ? OutputFileParams(QFileInfo(original_background_file_path)) + : OutputFileParams(), + write_automask ? OutputFileParams(QFileInfo(automask_file_path)) : OutputFileParams(), + write_speckles_file ? OutputFileParams(QFileInfo(speckles_file_path)) : OutputFileParams(), new_picture_zones, + new_fill_zones); + + m_settings->setOutputParams(m_pageId, out_params); } + + m_thumbnailCache->recreateThumbnail(ImageId(out_file_path), out_img); + } + + const DespeckleState despeckle_state(out_img, speckles_img, params.despeckleLevel(), params.outputDpi()); + + DespeckleVisualization despeckle_visualization; + if (m_lastTab == TAB_DESPECKLING) { + // Because constructing DespeckleVisualization takes a noticeable + // amount of time, we only do it if we are sure we'll need it. + // Otherwise it will get constructed on demand. + despeckle_visualization = despeckle_state.visualize(); + } + + if (CommandLine::get().isGui()) { + return make_intrusive(m_filter, m_settings, std::move(m_dbg), params, new_xform, + generator.getPostTransform(), generator.outputContentRect(), m_pageId, + data.origImage(), out_img, automask_img, despeckle_state, despeckle_visualization, + m_batchProcessing, m_debug); + } else { + return nullptr; + } } // Task::process /** * Delete output files mutually exclusive to m_pageId. */ void Task::deleteMutuallyExclusiveOutputFiles() { - switch (m_pageId.subPage()) { - case PageId::SINGLE_PAGE: - QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::LEFT_PAGE))); - QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::RIGHT_PAGE))); - break; - case PageId::LEFT_PAGE: - case PageId::RIGHT_PAGE: - QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::SINGLE_PAGE))); - break; - } + switch (m_pageId.subPage()) { + case PageId::SINGLE_PAGE: + QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::LEFT_PAGE))); + QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::RIGHT_PAGE))); + break; + case PageId::LEFT_PAGE: + case PageId::RIGHT_PAGE: + QFile::remove(m_outFileNameGen.filePathFor(PageId(m_pageId.imageId(), PageId::SINGLE_PAGE))); + break; + } } /*============================ Task::UiUpdater ==========================*/ @@ -489,120 +490,115 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const DespeckleVisualization& despeckle_visualization, const bool batch, const bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrSettings(std::move(settings)), - m_ptrDbg(std::move(dbg_img)), - m_params(params), - m_xform(xform), - m_outputPostTransform(postTransform), - m_virtContentRect(virt_content_rect), - m_pageId(page_id), - m_origImage(orig_image), - m_downscaledOrigImage(ImageView::createDownscaledImage(orig_image)), - m_outputImage(output_image), - m_downscaledOutputImage(ImageView::createDownscaledImage(output_image)), - m_pictureMask(picture_mask), - m_despeckleState(despeckle_state), - m_despeckleVisualization(despeckle_visualization), - m_batchProcessing(batch), - m_debug(debug) { -} + : m_filter(std::move(filter)), + m_settings(std::move(settings)), + m_dbg(std::move(dbg_img)), + m_params(params), + m_xform(xform), + m_outputPostTransform(postTransform), + m_virtContentRect(virt_content_rect), + m_pageId(page_id), + m_origImage(orig_image), + m_downscaledOrigImage(ImageView::createDownscaledImage(orig_image)), + m_outputImage(output_image), + m_downscaledOutputImage(ImageView::createDownscaledImage(output_image)), + m_pictureMask(picture_mask), + m_despeckleState(despeckle_state), + m_despeckleVisualization(despeckle_visualization), + m_batchProcessing(batch), + m_debug(debug) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - - ui->invalidateThumbnail(m_pageId); - - if (m_batchProcessing) { - return; - } - - auto tab_image_rect_map = std::make_unique>>(); - - std::unique_ptr image_view(new ImageView(m_outputImage, m_downscaledOutputImage)); - const QPixmap downscaled_output_pixmap(image_view->downscaledPixmap()); - tab_image_rect_map->insert(std::pair(TAB_OUTPUT, m_xform.resultingRect())); - - std::unique_ptr dewarping_view(new DewarpingView( - m_origImage, m_downscaledOrigImage, m_xform.transform(), - PolygonUtils::convexHull(m_xform.resultingPreCropArea().toStdVector()), m_virtContentRect, m_pageId, - m_params.dewarpingOptions(), m_params.distortionModel(), opt_widget->depthPerception())); - const QPixmap downscaled_orig_pixmap(dewarping_view->downscaledPixmap()); - QObject::connect(opt_widget, SIGNAL(depthPerceptionChanged(double)), dewarping_view.get(), - SLOT(depthPerceptionChanged(double))); - QObject::connect(dewarping_view.get(), SIGNAL(distortionModelChanged(const dewarping::DistortionModel&)), - opt_widget, SLOT(distortionModelChanged(const dewarping::DistortionModel&))); - tab_image_rect_map->insert( - std::pair(TAB_DEWARPING, m_xform.resultingPreCropArea().boundingRect())); - - std::unique_ptr picture_zone_editor; - if (m_pictureMask.isNull()) { - picture_zone_editor = std::make_unique(tr("Picture zones are only available in Mixed mode.")); - } else { - picture_zone_editor = std::make_unique( - m_origImage, downscaled_orig_pixmap, m_pictureMask, m_xform.transform(), m_xform.resultingPreCropArea(), - m_pageId, m_ptrSettings); - QObject::connect(picture_zone_editor.get(), SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, - SIGNAL(invalidateThumbnail(const PageId&))); - tab_image_rect_map->insert( - std::pair(TAB_PICTURE_ZONES, m_xform.resultingPreCropArea().boundingRect())); - } - - // We make sure we never need to update the original <-> output - // mapping at run time, that is without reloading. - // In OptionsWidget::dewarpingChanged() we make sure to reload - // if we are on the "Fill Zones" tab, and if not, it will be reloaded - // anyway when another tab is selected. - boost::function orig_to_output; - boost::function output_to_orig; - if ((m_params.dewarpingOptions().dewarpingMode() != OFF) && m_params.distortionModel().isValid()) { - std::shared_ptr mapper( - new DewarpingPointMapper(m_params.distortionModel(), m_params.depthPerception().value(), - m_xform.transform(), m_virtContentRect, m_outputPostTransform)); - orig_to_output = boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1); - output_to_orig = boost::bind(&DewarpingPointMapper::mapToWarpedSpace, mapper, _1); - } else { - typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; - orig_to_output = boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1); - output_to_orig = boost::bind((MapPointFunc) &QTransform::map, m_xform.transformBack(), _1); - } - - std::unique_ptr fill_zone_editor(new FillZoneEditor( - m_outputImage, downscaled_output_pixmap, orig_to_output, output_to_orig, m_pageId, m_ptrSettings)); - QObject::connect(fill_zone_editor.get(), SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + + ui->invalidateThumbnail(m_pageId); + + if (m_batchProcessing) { + return; + } + + auto tab_image_rect_map = std::make_unique>>(); + + std::unique_ptr image_view(new ImageView(m_outputImage, m_downscaledOutputImage)); + const QPixmap downscaled_output_pixmap(image_view->downscaledPixmap()); + tab_image_rect_map->insert(std::pair(TAB_OUTPUT, m_xform.resultingRect())); + + std::unique_ptr dewarping_view(new DewarpingView( + m_origImage, m_downscaledOrigImage, m_xform.transform(), + PolygonUtils::convexHull(m_xform.resultingPreCropArea().toStdVector()), m_virtContentRect, m_pageId, + m_params.dewarpingOptions(), m_params.distortionModel(), opt_widget->depthPerception())); + const QPixmap downscaled_orig_pixmap(dewarping_view->downscaledPixmap()); + QObject::connect(opt_widget, SIGNAL(depthPerceptionChanged(double)), dewarping_view.get(), + SLOT(depthPerceptionChanged(double))); + QObject::connect(dewarping_view.get(), SIGNAL(distortionModelChanged(const dewarping::DistortionModel&)), opt_widget, + SLOT(distortionModelChanged(const dewarping::DistortionModel&))); + tab_image_rect_map->insert( + std::pair(TAB_DEWARPING, m_xform.resultingPreCropArea().boundingRect())); + + std::unique_ptr picture_zone_editor; + if (m_pictureMask.isNull()) { + picture_zone_editor = std::make_unique(tr("Picture zones are only available in Mixed mode.")); + } else { + picture_zone_editor = std::make_unique( + m_origImage, downscaled_orig_pixmap, m_pictureMask, m_xform.transform(), m_xform.resultingPreCropArea(), + m_pageId, m_settings); + QObject::connect(picture_zone_editor.get(), SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, SIGNAL(invalidateThumbnail(const PageId&))); - tab_image_rect_map->insert(std::pair(TAB_FILL_ZONES, m_xform.resultingRect())); - - std::unique_ptr despeckle_view; - if (m_params.colorParams().colorMode() == COLOR_GRAYSCALE) { - despeckle_view = std::make_unique(tr("Despeckling can't be done in Color / Grayscale mode.")); - } else { - despeckle_view = std::make_unique( - - m_despeckleState, m_despeckleVisualization, m_debug - - ); - QObject::connect(opt_widget, SIGNAL(despeckleLevelChanged(DespeckleLevel, bool*)), despeckle_view.get(), - SLOT(despeckleLevelChanged(DespeckleLevel, bool*))); - tab_image_rect_map->insert(std::pair(TAB_DESPECKLING, m_xform.resultingRect())); - } - - std::unique_ptr tab_widget(new TabbedImageView); - tab_widget->setDocumentMode(true); - tab_widget->setTabPosition(QTabWidget::East); - tab_widget->addTab(image_view.release(), tr("Output"), TAB_OUTPUT); - tab_widget->addTab(picture_zone_editor.release(), tr("Picture Zones"), TAB_PICTURE_ZONES); - tab_widget->addTab(fill_zone_editor.release(), tr("Fill Zones"), TAB_FILL_ZONES); - tab_widget->addTab(dewarping_view.release(), tr("Dewarping"), TAB_DEWARPING); - tab_widget->addTab(despeckle_view.release(), tr("Despeckling"), TAB_DESPECKLING); - tab_widget->setCurrentTab(opt_widget->lastTab()); - tab_widget->setImageRectMap(std::move(tab_image_rect_map)); - - QObject::connect(tab_widget.get(), SIGNAL(tabChanged(ImageViewTab)), opt_widget, SLOT(tabChanged(ImageViewTab))); - - ui->setImageWidget(tab_widget.release(), ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); + tab_image_rect_map->insert( + std::pair(TAB_PICTURE_ZONES, m_xform.resultingPreCropArea().boundingRect())); + } + + // We make sure we never need to update the original <-> output + // mapping at run time, that is without reloading. + // In OptionsWidget::dewarpingChanged() we make sure to reload + // if we are on the "Fill Zones" tab, and if not, it will be reloaded + // anyway when another tab is selected. + boost::function orig_to_output; + boost::function output_to_orig; + if ((m_params.dewarpingOptions().dewarpingMode() != OFF) && m_params.distortionModel().isValid()) { + std::shared_ptr mapper( + new DewarpingPointMapper(m_params.distortionModel(), m_params.depthPerception().value(), m_xform.transform(), + m_virtContentRect, m_outputPostTransform)); + orig_to_output = boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1); + output_to_orig = boost::bind(&DewarpingPointMapper::mapToWarpedSpace, mapper, _1); + } else { + typedef QPointF (QTransform::*MapPointFunc)(const QPointF&) const; + orig_to_output = boost::bind((MapPointFunc) &QTransform::map, m_xform.transform(), _1); + output_to_orig = boost::bind((MapPointFunc) &QTransform::map, m_xform.transformBack(), _1); + } + + std::unique_ptr fill_zone_editor(new FillZoneEditor(m_outputImage, downscaled_output_pixmap, orig_to_output, + output_to_orig, m_pageId, m_settings)); + QObject::connect(fill_zone_editor.get(), SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, + SIGNAL(invalidateThumbnail(const PageId&))); + tab_image_rect_map->insert(std::pair(TAB_FILL_ZONES, m_xform.resultingRect())); + + std::unique_ptr despeckle_view; + if (m_params.colorParams().colorMode() == COLOR_GRAYSCALE) { + despeckle_view = std::make_unique(tr("Despeckling can't be done in Color / Grayscale mode.")); + } else { + despeckle_view = std::make_unique(m_despeckleState, m_despeckleVisualization, m_debug); + QObject::connect(opt_widget, SIGNAL(despeckleLevelChanged(double, bool*)), despeckle_view.get(), + SLOT(despeckleLevelChanged(double, bool*))); + tab_image_rect_map->insert(std::pair(TAB_DESPECKLING, m_xform.resultingRect())); + } + + std::unique_ptr tab_widget(new TabbedImageView); + tab_widget->setDocumentMode(true); + tab_widget->setTabPosition(QTabWidget::East); + tab_widget->addTab(image_view.release(), tr("Output"), TAB_OUTPUT); + tab_widget->addTab(picture_zone_editor.release(), tr("Picture Zones"), TAB_PICTURE_ZONES); + tab_widget->addTab(fill_zone_editor.release(), tr("Fill Zones"), TAB_FILL_ZONES); + tab_widget->addTab(dewarping_view.release(), tr("Dewarping"), TAB_DEWARPING); + tab_widget->addTab(despeckle_view.release(), tr("Despeckling"), TAB_DESPECKLING); + tab_widget->setCurrentTab(opt_widget->lastTab()); + tab_widget->setImageRectMap(std::move(tab_image_rect_map)); + + QObject::connect(tab_widget.get(), SIGNAL(tabChanged(ImageViewTab)), opt_widget, SLOT(tabChanged(ImageViewTab))); + + ui->setImageWidget(tab_widget.release(), ui->TRANSFER_OWNERSHIP, m_dbg.get()); } // Task::UiUpdater::updateUI } // namespace output \ No newline at end of file diff --git a/filters/output/Task.h b/filters/output/Task.h index a27b7bdfe..b4391f9e8 100644 --- a/filters/output/Task.h +++ b/filters/output/Task.h @@ -19,15 +19,15 @@ #ifndef OUTPUT_TASK_H_ #define OUTPUT_TASK_H_ -#include "NonCopyable.h" -#include "ref_countable.h" +#include +#include +#include #include "FilterResult.h" -#include "PageId.h" #include "ImageViewTab.h" +#include "NonCopyable.h" #include "OutputFileNameGenerator.h" -#include -#include -#include +#include "PageId.h" +#include "ref_countable.h" class DebugImages; class TaskStatus; @@ -48,36 +48,36 @@ class Filter; class Settings; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(intrusive_ptr filter, - intrusive_ptr settings, - intrusive_ptr thumbnail_cache, - const PageId& page_id, - const OutputFileNameGenerator& out_file_name_gen, - ImageViewTab last_tab, - bool batch, - bool debug); + public: + Task(intrusive_ptr filter, + intrusive_ptr settings, + intrusive_ptr thumbnail_cache, + const PageId& page_id, + const OutputFileNameGenerator& out_file_name_gen, + ImageViewTab last_tab, + bool batch, + bool debug); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, const FilterData& data, const QPolygonF& content_rect_phys); + FilterResultPtr process(const TaskStatus& status, const FilterData& data, const QPolygonF& content_rect_phys); -private: - class UiUpdater; + private: + class UiUpdater; - void deleteMutuallyExclusiveOutputFiles(); + void deleteMutuallyExclusiveOutputFiles(); - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrThumbnailCache; - std::unique_ptr m_ptrDbg; - PageId m_pageId; - OutputFileNameGenerator m_outFileNameGen; - ImageViewTab m_lastTab; - bool m_batchProcessing; - bool m_debug; + intrusive_ptr m_filter; + intrusive_ptr m_settings; + intrusive_ptr m_thumbnailCache; + std::unique_ptr m_dbg; + PageId m_pageId; + OutputFileNameGenerator m_outFileNameGen; + ImageViewTab m_lastTab; + bool m_batchProcessing; + bool m_debug; }; } // namespace output #endif // ifndef OUTPUT_TASK_H_ diff --git a/filters/output/Thumbnail.cpp b/filters/output/Thumbnail.cpp index 5ce571ece..299ed7814 100644 --- a/filters/output/Thumbnail.cpp +++ b/filters/output/Thumbnail.cpp @@ -25,6 +25,5 @@ Thumbnail::Thumbnail(intrusive_ptr thumbnail_cache, const QSizeF& max_size, const ImageId& image_id, const ImageTransformation& xform) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform) { -} + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform) {} } // namespace output diff --git a/filters/output/Thumbnail.h b/filters/output/Thumbnail.h index fb7c610f2..891e5a2df 100644 --- a/filters/output/Thumbnail.h +++ b/filters/output/Thumbnail.h @@ -29,11 +29,11 @@ class QSizeF; namespace output { class Thumbnail : public ThumbnailBase { -public: - Thumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& xform); + public: + Thumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& xform); }; } // namespace output #endif diff --git a/filters/output/Utils.cpp b/filters/output/Utils.cpp index 0706dba7e..7c5fca5c1 100644 --- a/filters/output/Utils.cpp +++ b/filters/output/Utils.cpp @@ -17,40 +17,40 @@ */ #include "Utils.h" -#include "Dpi.h" +#include #include #include -#include +#include "Dpi.h" namespace output { QString Utils::automaskDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("cache/automask"); + return QDir(out_dir).absoluteFilePath("cache/automask"); } QString Utils::predespeckleDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("cache/predespeckle"); + return QDir(out_dir).absoluteFilePath("cache/predespeckle"); } QString Utils::specklesDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("cache/speckles"); + return QDir(out_dir).absoluteFilePath("cache/speckles"); } QTransform Utils::scaleFromToDpi(const Dpi& from, const Dpi& to) { - QTransform xform; - xform.scale((double) to.horizontal() / from.horizontal(), (double) to.vertical() / from.vertical()); + QTransform xform; + xform.scale((double) to.horizontal() / from.horizontal(), (double) to.vertical() / from.vertical()); - return xform; + return xform; } QString Utils::foregroundDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("foreground"); + return QDir(out_dir).absoluteFilePath("foreground"); } QString Utils::backgroundDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("background"); + return QDir(out_dir).absoluteFilePath("background"); } QString Utils::originalBackgroundDir(const QString& out_dir) { - return QDir(out_dir).absoluteFilePath("original_background"); + return QDir(out_dir).absoluteFilePath("original_background"); } } // namespace output \ No newline at end of file diff --git a/filters/output/Utils.h b/filters/output/Utils.h index b8f12ce2f..ef81cfe3b 100644 --- a/filters/output/Utils.h +++ b/filters/output/Utils.h @@ -25,20 +25,20 @@ class QTransform; namespace output { class Utils { -public: - static QString automaskDir(const QString& out_dir); + public: + static QString automaskDir(const QString& out_dir); - static QString predespeckleDir(const QString& out_dir); + static QString predespeckleDir(const QString& out_dir); - static QString specklesDir(const QString& out_dir); + static QString specklesDir(const QString& out_dir); - static QString foregroundDir(const QString& out_dir); + static QString foregroundDir(const QString& out_dir); - static QString backgroundDir(const QString& out_dir); + static QString backgroundDir(const QString& out_dir); - static QString originalBackgroundDir(const QString& out_dir); + static QString originalBackgroundDir(const QString& out_dir); - static QTransform scaleFromToDpi(const Dpi& from, const Dpi& to); + static QTransform scaleFromToDpi(const Dpi& from, const Dpi& to); }; } // namespace output #endif diff --git a/filters/output/WolfBinarizationOptionsWidget.cpp b/filters/output/WolfBinarizationOptionsWidget.cpp index f24195964..34ced5d45 100644 --- a/filters/output/WolfBinarizationOptionsWidget.cpp +++ b/filters/output/WolfBinarizationOptionsWidget.cpp @@ -7,88 +7,91 @@ namespace output { WolfBinarizationOptionsWidget::WolfBinarizationOptionsWidget(intrusive_ptr settings) - : m_ptrSettings(std::move(settings)) { - setupUi(this); + : m_settings(std::move(settings)) { + setupUi(this); - delayedStateChanger.setSingleShot(true); + m_delayedStateChanger.setSingleShot(true); - setupUiConnections(); + setupUiConnections(); } -void WolfBinarizationOptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); +void WolfBinarizationOptionsWidget::updateUi(const PageId& page_id) { + removeUiConnections(); - const Params params(m_ptrSettings->getParams(page_id)); - m_pageId = page_id; - m_colorParams = params.colorParams(); - m_outputProcessingParams = m_ptrSettings->getOutputProcessingParams(page_id); + const Params params(m_settings->getParams(page_id)); + m_pageId = page_id; + m_colorParams = params.colorParams(); + m_outputProcessingParams = m_settings->getOutputProcessingParams(page_id); - updateView(); + updateView(); - setupUiConnections(); + setupUiConnections(); } void WolfBinarizationOptionsWidget::windowSizeChanged(int value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setWindowSize(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setWindowSize(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void WolfBinarizationOptionsWidget::lowerBoundChanged(int value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setWolfLowerBound(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setWolfLowerBound(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void WolfBinarizationOptionsWidget::upperBoundChanged(int value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setWolfUpperBound(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setWolfUpperBound(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void WolfBinarizationOptionsWidget::wolfCoefChanged(double value) { - BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); - opt.setWolfCoef(value); - m_colorParams.setBlackWhiteOptions(opt); - m_ptrSettings->setColorParams(m_pageId, m_colorParams); + BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); + opt.setWolfCoef(value); + m_colorParams.setBlackWhiteOptions(opt); + m_settings->setColorParams(m_pageId, m_colorParams); - delayedStateChanger.start(750); + m_delayedStateChanger.start(750); } void WolfBinarizationOptionsWidget::updateView() { - BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); - windowSize->setValue(blackWhiteOptions.getWindowSize()); - lowerBound->setValue(blackWhiteOptions.getWolfLowerBound()); - upperBound->setValue(blackWhiteOptions.getWolfUpperBound()); - wolfCoef->setValue(blackWhiteOptions.getWolfCoef()); + BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions(); + windowSize->setValue(blackWhiteOptions.getWindowSize()); + lowerBound->setValue(blackWhiteOptions.getWolfLowerBound()); + upperBound->setValue(blackWhiteOptions.getWolfUpperBound()); + wolfCoef->setValue(blackWhiteOptions.getWolfCoef()); } void WolfBinarizationOptionsWidget::sendStateChanged() { - emit stateChanged(); + emit stateChanged(); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void WolfBinarizationOptionsWidget::setupUiConnections() { - connect(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); - connect(lowerBound, SIGNAL(valueChanged(int)), this, SLOT(lowerBoundChanged(int))); - connect(upperBound, SIGNAL(valueChanged(int)), this, SLOT(upperBoundChanged(int))); - connect(wolfCoef, SIGNAL(valueChanged(double)), this, SLOT(wolfCoefChanged(double))); - connect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + CONNECT(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); + CONNECT(lowerBound, SIGNAL(valueChanged(int)), this, SLOT(lowerBoundChanged(int))); + CONNECT(upperBound, SIGNAL(valueChanged(int)), this, SLOT(upperBoundChanged(int))); + CONNECT(wolfCoef, SIGNAL(valueChanged(double)), this, SLOT(wolfCoefChanged(double))); + CONNECT(&m_delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); } +#undef CONNECT + void WolfBinarizationOptionsWidget::removeUiConnections() { - disconnect(windowSize, SIGNAL(valueChanged(int)), this, SLOT(windowSizeChanged(int))); - disconnect(lowerBound, SIGNAL(valueChanged(int)), this, SLOT(lowerBoundChanged(int))); - disconnect(upperBound, SIGNAL(valueChanged(int)), this, SLOT(upperBoundChanged(int))); - disconnect(wolfCoef, SIGNAL(valueChanged(double)), this, SLOT(wolfCoefChanged(double))); - disconnect(&delayedStateChanger, SIGNAL(timeout()), this, SLOT(sendStateChanged())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } } // namespace output \ No newline at end of file diff --git a/filters/output/WolfBinarizationOptionsWidget.h b/filters/output/WolfBinarizationOptionsWidget.h index a6804ccd7..eb35b63b4 100644 --- a/filters/output/WolfBinarizationOptionsWidget.h +++ b/filters/output/WolfBinarizationOptionsWidget.h @@ -2,49 +2,50 @@ #ifndef SCANTAILOR_WOLFBINARIZATIONOPTIONSWIDGET_H #define SCANTAILOR_WOLFBINARIZATIONOPTIONSWIDGET_H -#include "ui_WolfBinarizationOptionsWidget.h" +#include +#include #include "BinarizationOptionsWidget.h" #include "ColorParams.h" -#include "intrusive_ptr.h" #include "Settings.h" -#include +#include "intrusive_ptr.h" +#include "ui_WolfBinarizationOptionsWidget.h" namespace output { class WolfBinarizationOptionsWidget : public BinarizationOptionsWidget, private Ui::WolfBinarizationOptionsWidget { - Q_OBJECT + Q_OBJECT + public: + explicit WolfBinarizationOptionsWidget(intrusive_ptr settings); -private: - intrusive_ptr m_ptrSettings; - PageId m_pageId; - ColorParams m_colorParams; - QTimer delayedStateChanger; - OutputProcessingParams m_outputProcessingParams; + ~WolfBinarizationOptionsWidget() override = default; -public: - explicit WolfBinarizationOptionsWidget(intrusive_ptr settings); + void updateUi(const PageId& m_pageId) override; - ~WolfBinarizationOptionsWidget() override = default; + private slots: - void preUpdateUI(const PageId& m_pageId) override; + void windowSizeChanged(int value); -private slots: + void wolfCoefChanged(double value); - void windowSizeChanged(int value); + void lowerBoundChanged(int value); - void wolfCoefChanged(double value); + void upperBoundChanged(int value); - void lowerBoundChanged(int value); + void sendStateChanged(); - void upperBoundChanged(int value); + private: + void updateView(); - void sendStateChanged(); + void setupUiConnections(); -private: - void updateView(); + void removeUiConnections(); - void setupUiConnections(); + intrusive_ptr m_settings; + PageId m_pageId; + ColorParams m_colorParams; + QTimer m_delayedStateChanger; + OutputProcessingParams m_outputProcessingParams; - void removeUiConnections(); + std::list m_connectionList; }; } // namespace output diff --git a/filters/output/ZoneCategoryProperty.cpp b/filters/output/ZoneCategoryProperty.cpp index e48d6babd..50a751c5b 100644 --- a/filters/output/ZoneCategoryProperty.cpp +++ b/filters/output/ZoneCategoryProperty.cpp @@ -18,69 +18,68 @@ */ #include "ZoneCategoryProperty.h" -#include "PropertyFactory.h" #include +#include "PropertyFactory.h" namespace output { const char ZoneCategoryProperty::m_propertyName[] = "ZoneCategoryProperty"; ZoneCategoryProperty::ZoneCategoryProperty(const QDomElement& el) - : m_zone_category(zoneCategoryFromString(el.attribute("zone_category"))) { -} + : m_zone_category(zoneCategoryFromString(el.attribute("zone_category"))) {} void ZoneCategoryProperty::registerIn(PropertyFactory& factory) { - factory.registerProperty(m_propertyName, &ZoneCategoryProperty::construct); + factory.registerProperty(m_propertyName, &ZoneCategoryProperty::construct); } intrusive_ptr ZoneCategoryProperty::clone() const { - return make_intrusive(*this); + return make_intrusive(*this); } QDomElement ZoneCategoryProperty::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("type", m_propertyName); - el.setAttribute("zone_category", zoneCategoryToString(m_zone_category)); + QDomElement el(doc.createElement(name)); + el.setAttribute("type", m_propertyName); + el.setAttribute("zone_category", zoneCategoryToString(m_zone_category)); - return el; + return el; } intrusive_ptr ZoneCategoryProperty::construct(const QDomElement& el) { - return make_intrusive(el); + return make_intrusive(el); } ZoneCategoryProperty::ZoneCategory ZoneCategoryProperty::zoneCategoryFromString(const QString& str) { - if (str == "manual") { - return MANUAL; - } else if (str == "rectangular_outline") { - return RECTANGULAR_OUTLINE; - } else { - return MANUAL; - } + if (str == "manual") { + return MANUAL; + } else if (str == "rectangular_outline") { + return RECTANGULAR_OUTLINE; + } else { + return MANUAL; + } } QString ZoneCategoryProperty::zoneCategoryToString(ZoneCategory zone_category) { - const char* str = nullptr; - - switch (zone_category) { - case MANUAL: - str = "manual"; - break; - case RECTANGULAR_OUTLINE: - str = "rectangular_outline"; - break; - default: - str = ""; - break; - } - - return str; + const char* str = nullptr; + + switch (zone_category) { + case MANUAL: + str = "manual"; + break; + case RECTANGULAR_OUTLINE: + str = "rectangular_outline"; + break; + default: + str = ""; + break; + } + + return str; } ZoneCategoryProperty::ZoneCategory ZoneCategoryProperty::zone_category() const { - return m_zone_category; + return m_zone_category; } void ZoneCategoryProperty::setZoneCategory(ZoneCategoryProperty::ZoneCategory zone_category) { - m_zone_category = zone_category; + m_zone_category = zone_category; } } // namespace output \ No newline at end of file diff --git a/filters/output/ZoneCategoryProperty.h b/filters/output/ZoneCategoryProperty.h index a6936bea8..9848818b0 100644 --- a/filters/output/ZoneCategoryProperty.h +++ b/filters/output/ZoneCategoryProperty.h @@ -30,34 +30,33 @@ class QString; namespace output { class ZoneCategoryProperty : public Property { -public: - enum ZoneCategory { MANUAL, RECTANGULAR_OUTLINE }; + public: + enum ZoneCategory { MANUAL, RECTANGULAR_OUTLINE }; - explicit ZoneCategoryProperty(ZoneCategory zone_category = MANUAL) : m_zone_category(zone_category) { - } + explicit ZoneCategoryProperty(ZoneCategory zone_category = MANUAL) : m_zone_category(zone_category) {} - explicit ZoneCategoryProperty(const QDomElement& el); + explicit ZoneCategoryProperty(const QDomElement& el); - static void registerIn(PropertyFactory& factory); + static void registerIn(PropertyFactory& factory); - intrusive_ptr clone() const override; + intrusive_ptr clone() const override; - QDomElement toXml(QDomDocument& doc, const QString& name) const override; + QDomElement toXml(QDomDocument& doc, const QString& name) const override; - ZoneCategory zone_category() const; + ZoneCategory zone_category() const; - void setZoneCategory(ZoneCategory zone_category); + void setZoneCategory(ZoneCategory zone_category); -private: - static intrusive_ptr construct(const QDomElement& el); + private: + static intrusive_ptr construct(const QDomElement& el); - static ZoneCategory zoneCategoryFromString(const QString& str); + static ZoneCategory zoneCategoryFromString(const QString& str); - static QString zoneCategoryToString(ZoneCategory zone_category); + static QString zoneCategoryToString(ZoneCategory zone_category); - static const char m_propertyName[]; - ZoneCategory m_zone_category; + static const char m_propertyName[]; + ZoneCategory m_zone_category; }; } // namespace output diff --git a/filters/output/ui/OtsuBinarizationOptionsWidget.ui b/filters/output/ui/OtsuBinarizationOptionsWidget.ui index bf1c6c070..7e91a0841 100644 --- a/filters/output/ui/OtsuBinarizationOptionsWidget.ui +++ b/filters/output/ui/OtsuBinarizationOptionsWidget.ui @@ -58,11 +58,6 @@ - - - 8 - - QFrame::NoFrame diff --git a/filters/output/ui/OutputOptionsWidget.ui b/filters/output/ui/OutputOptionsWidget.ui index f8f6c209d..1aa883d1b 100644 --- a/filters/output/ui/OutputOptionsWidget.ui +++ b/filters/output/ui/OutputOptionsWidget.ui @@ -2,1031 +2,616 @@ OutputOptionsWidget - - Qt::NonModal - 0 0 - 238 - 628 + 228 + 1336 - - - 0 - 0 - - - - - 0 - 0 - - - - false - - - false - Form - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - + - - - - 0 - 0 - + + + - - Qt::LeftToRight - - - false + + - - QFrame::NoFrame + + Output Resolution (DPI) - - Qt::ScrollBarAlwaysOff + + Qt::AlignCenter - - QAbstractScrollArea::AdjustIgnored + + false - - true + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Change ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + Mode - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter - - - - 0 - 0 - 228 - 1215 - - - - - - - - - - - - - Output Resolution (DPI) - - - Qt::AlignCenter - - - false - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - 8 - 50 - false - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Change ... - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - Mode + + false + + + false + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + Options + + + Qt::AlignCenter + + + + 0 - - Qt::AlignCenter + + 0 - - false + + 0 - - false + + 0 - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - Options - - - - 0 + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + 2 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Fill offcut - - 0 + + + + + + Fill margins - - 0 + + + + + + Normalize illumination before binarization. - - 0 + + Equalize illumination (B&&W) - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - 2 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - Cut margins - - - - - - - Normalize illumination before binarization. - - - Equalize illumination (B&&W) - - - - - - - Normalize illumination in color mode / in picture zones in mixed mode. - - - Equalize illumination (Color) - - - - - - - Savitzky-Golay smoothing - - - - - - - Morphological smoothing - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - Filling - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - false - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Color: - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - Threshold - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - false - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Method: - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - 1 - - - - - - - - - - - - - - - Color operations - - - - 0 + + + + + + Normalize illumination in color mode / in picture zones in mixed mode. - - 0 + + Equalize illumination (Color) - - 0 + + + + + + Savitzky-Golay smoothing - - 0 + + + + + + Morphological smoothing - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - 2 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - Split the image into color segments and colorize b&w mask. - - - - - - Color segmentation - - - - - - - - 0 + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + Filling + + + Qt::AlignCenter + + + false + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Color: + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + Threshold + + + Qt::AlignCenter + + + false + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Method: + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + 1 + + + + + + + + + + + + + + + Color operations + + + Qt::AlignCenter + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + 2 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Split the image into color segments and colorize b&w mask. + + + + + + Color segmentation + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + Qt::Horizontal - - 0 + + QSizePolicy::Minimum - - 0 + + + 15 + 1 + - - 0 + + + + + + R - - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 15 - 1 - - - - - - - - R - - - 1 - - - 0 - - - - - - - Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -99 - - - 99 - - - 0 - - - - - - - G - - - 1 - - - 3 - - - - - - - Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -99 - - - 99 - - - 0 - - - - - - - B - - - 1 - - - 3 - - - - - - - Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -99 - - - 99 - - - 0 - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 15 - 1 - - - - - - - - Reduce noise: - - - - - - - - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 0 - - - 999 - - - 0 - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - Reduce the number of colors of the output image by grouping similar colors. - - - - - - Posterize - - - - - - - + + 1 + + 0 - + + + + + + Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -99 + + + 99 + + 0 - + + + + + + G + + + 1 + + + 3 + + + + + + + Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -99 + + + 99 + + 0 - + + + + + + B + + + 1 + + + 3 + + + + + + + Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -99 + + + 99 + + 0 - - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 15 - 1 - - - - - - - - Level: - - - - - - - Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 2 - - - 255 - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 15 - 1 - - - - - - - - - - - - - - Normalize - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 15 - 1 - - - - - - - - Make dark and light gray gradients black and white respectively. - - - - - - Force b&&w - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - Picture Shape - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - + + - + Qt::Horizontal @@ -1038,28 +623,57 @@ + + + + - + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 15 + 1 + + + + + + - Sensitivity (%): + Reduce noise: - + + + + true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + 0 - 100 + 999 + + + 0 - + Qt::Horizontal @@ -1072,138 +686,113 @@ - - - - - - - - Qt::Horizontal - - - - 1 - 0 - - - - - - - - Higher search sensitivity - - - - - - - Qt::Horizontal - - - - 1 - 0 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Apply To ... - - - - - - - Qt::Horizontal + + + + + + + + Reduce the number of colors of the output image by grouping similar colors. + + + + + + Posterize + + + + + + + + 0 - - - 1 - 1 - + + 0 - - - -
- - -
- - - - - - - Splitting - - - - - - - - Qt::Horizontal + + 0 - - - 1 - 1 - + + 0 - - - - - - - Split output + + + 0 - + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 15 + 1 + + + + + + + + Level: + + + + + + + Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 2 + + + 255 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + - - - B&&W foreground - - - true + + + 0 - - - - - + Qt::Horizontal + + QSizePolicy::Minimum + 15 @@ -1213,100 +802,162 @@ - + - Save the original background of the foreground layer. + + + + - Original background + Normalize + + + + Qt::Horizontal + + + + 1 + 1 + + + + - - - Color foreground + + + 0 - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Apply To ... - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - -
- - - - Despeckling - - - Qt::AlignCenter - - - false - - - - - - 6 + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 15 + 1 + + + + + + + + Make dark and light gray gradients black and white respectively. + + + + + + Force b&&w + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + +
+ + + + Qt::Horizontal + + + + 1 + 1 + + + + + + +
+ + + + Picture Shape + + + Qt::AlignCenter + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 - + Qt::Horizontal @@ -1319,110 +970,27 @@ - - - No despeckling - - - - - - - 32 - 32 - - - - true - - - true - - - true - - - - - - - Cautious despeckling - + - ... - - - - :/icons/despeckle-cautious.png.png:/icons/despeckle-cautious.png.png - - - - 32 - 32 - - - - true - - - true + Sensitivity (%): - - - Normal despeckling - - - ... - - - - :/icons/despeckle-normal.png.png:/icons/despeckle-normal.png.png - - - - 32 - 32 - - - - true - - + + true - - - - - - Aggressive despeckling - - - ... - - - - :/icons/despeckle-aggressive.png.png:/icons/despeckle-aggressive.png.png - - - - 32 - 32 - - - - true + + 0 - - true + + 100 - + Qt::Horizontal @@ -1435,224 +1003,640 @@ + + + + + + + + Qt::Horizontal + + + + 1 + 0 + + + + + + + + Higher search sensitivity + + + + + + + Qt::Horizontal + + + + 1 + 0 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + +
+ + + + + + + Splitting + + + Qt::AlignCenter + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + Split output + + + + + + + B&&W foreground + + + true + + - + - + Qt::Horizontal - 1 + 15 1 - + + + Save the original background of the foreground layer. + - Apply To ... + Original background - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - Depth perception - - - Qt::AlignCenter - - - false - - - - - 10 - - - 30 - - - 5 - - - 10 + + + Color foreground + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + +
+ + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + +
+ + + + Despeckling + + + Qt::AlignCenter + + + false + + + + 6 + + + 15 + + + 9 + + + 15 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal - + + + 40 + 20 + + + - - - - - Qt::Horizontal - - - - 1 - 1 - - - - + - + - Apply To ... + Despeckle - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - -
- - - - Dewarping - - - Qt::AlignCenter - - - false - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 10 + + + 30 + + + 2 + + + 5 + + + 10 + + + Qt::Horizontal + + + + +
+ + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + +
+ + + + Processing + + + Qt::AlignCenter + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Change ... - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + + This option should be enabled when the page has dark content on light background and disabled if vice versa in order to correct processing algorithms. + + + Black on white mode + + - - - - - - Qt::Vertical - - - - 0 - 40 - - - - - - +
+ + + + Qt::Horizontal + + + + 40 + 20 + + + + + +
+ + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + +
+ + + + Depth perception + + + Qt::AlignCenter + + + false + + + + 15 + + + 15 + + + + + 10 + + + 30 + + + 5 + + + 10 + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + Dewarping + + + Qt::AlignCenter + + + false + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Change ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + Qt::Vertical + + + + 0 + 40 + + + + @@ -1663,8 +1647,6 @@ 1 - - - + diff --git a/filters/page_layout/Alignment.cpp b/filters/page_layout/Alignment.cpp index 43a03a0ce..f430e38a6 100644 --- a/filters/page_layout/Alignment.cpp +++ b/filters/page_layout/Alignment.cpp @@ -20,135 +20,133 @@ #include namespace page_layout { -Alignment::Alignment() : m_vertical(VCENTER), m_horizontal(HCENTER), m_isNull(false) { -} +Alignment::Alignment() : m_vertical(VCENTER), m_horizontal(HCENTER), m_isNull(false) {} Alignment::Alignment(Vertical vertical, Horizontal horizontal) - : m_vertical(vertical), m_horizontal(horizontal), m_isNull(false) { -} + : m_vertical(vertical), m_horizontal(horizontal), m_isNull(false) {} Alignment::Alignment(const QDomElement& el) { - const QString vert(el.attribute("vert")); - const QString hor(el.attribute("hor")); - m_isNull = el.attribute("null").toInt() != 0; - - if (vert == "top") { - m_vertical = TOP; - } else if (vert == "bottom") { - m_vertical = BOTTOM; - } else if (vert == "auto") { - m_vertical = VAUTO; - } else if (vert == "original") { - m_vertical = VORIGINAL; - } else { - m_vertical = VCENTER; - } - - if (hor == "left") { - m_horizontal = LEFT; - } else if (hor == "right") { - m_horizontal = RIGHT; - } else if (hor == "auto") { - m_horizontal = HAUTO; - } else if (vert == "original") { - m_horizontal = HORIGINAL; - } else { - m_horizontal = HCENTER; - } + const QString vert(el.attribute("vert")); + const QString hor(el.attribute("hor")); + m_isNull = el.attribute("null").toInt() != 0; + + if (vert == "top") { + m_vertical = TOP; + } else if (vert == "bottom") { + m_vertical = BOTTOM; + } else if (vert == "auto") { + m_vertical = VAUTO; + } else if (vert == "original") { + m_vertical = VORIGINAL; + } else { + m_vertical = VCENTER; + } + + if (hor == "left") { + m_horizontal = LEFT; + } else if (hor == "right") { + m_horizontal = RIGHT; + } else if (hor == "auto") { + m_horizontal = HAUTO; + } else if (vert == "original") { + m_horizontal = HORIGINAL; + } else { + m_horizontal = HCENTER; + } } QDomElement Alignment::toXml(QDomDocument& doc, const QString& name) const { - const char* vert = nullptr; - switch (m_vertical) { - case TOP: - vert = "top"; - break; - case VCENTER: - vert = "vcenter"; - break; - case BOTTOM: - vert = "bottom"; - break; - case VAUTO: - vert = "auto"; - break; - case VORIGINAL: - vert = "original"; - break; - } - - const char* hor = nullptr; - switch (m_horizontal) { - case LEFT: - hor = "left"; - break; - case HCENTER: - hor = "hcenter"; - break; - case RIGHT: - hor = "right"; - break; - case HAUTO: - hor = "auto"; - break; - case HORIGINAL: - hor = "original"; - break; - } - - QDomElement el(doc.createElement(name)); - el.setAttribute("vert", QString::fromLatin1(vert)); - el.setAttribute("hor", QString::fromLatin1(hor)); - el.setAttribute("null", m_isNull ? 1 : 0); - - return el; + const char* vert = nullptr; + switch (m_vertical) { + case TOP: + vert = "top"; + break; + case VCENTER: + vert = "vcenter"; + break; + case BOTTOM: + vert = "bottom"; + break; + case VAUTO: + vert = "auto"; + break; + case VORIGINAL: + vert = "original"; + break; + } + + const char* hor = nullptr; + switch (m_horizontal) { + case LEFT: + hor = "left"; + break; + case HCENTER: + hor = "hcenter"; + break; + case RIGHT: + hor = "right"; + break; + case HAUTO: + hor = "auto"; + break; + case HORIGINAL: + hor = "original"; + break; + } + + QDomElement el(doc.createElement(name)); + el.setAttribute("vert", QString::fromLatin1(vert)); + el.setAttribute("hor", QString::fromLatin1(hor)); + el.setAttribute("null", m_isNull ? 1 : 0); + + return el; } bool Alignment::operator==(const Alignment& other) const { - return (m_vertical == other.m_vertical) && (m_horizontal == other.m_horizontal) && (m_isNull == other.m_isNull); + return (m_vertical == other.m_vertical) && (m_horizontal == other.m_horizontal) && (m_isNull == other.m_isNull); } bool Alignment::operator!=(const Alignment& other) const { - return !(*this == other); + return !(*this == other); } Alignment::Vertical Alignment::vertical() const { - return m_vertical; + return m_vertical; } void Alignment::setVertical(Alignment::Vertical vertical) { - m_vertical = vertical; + m_vertical = vertical; } Alignment::Horizontal Alignment::horizontal() const { - return m_horizontal; + return m_horizontal; } void Alignment::setHorizontal(Alignment::Horizontal horizontal) { - m_horizontal = horizontal; + m_horizontal = horizontal; } bool Alignment::isNull() const { - return m_isNull; + return m_isNull; } void Alignment::setNull(bool is_null) { - m_isNull = is_null; + m_isNull = is_null; } bool Alignment::isAutoVertical() const { - return (m_vertical == VAUTO) || (m_vertical == VORIGINAL); + return (m_vertical == VAUTO) || (m_vertical == VORIGINAL); } bool Alignment::isAutoHorizontal() const { - return (m_horizontal == HAUTO) || (m_horizontal == HORIGINAL); + return (m_horizontal == HAUTO) || (m_horizontal == HORIGINAL); } bool Alignment::isOriginal() const { - return (m_vertical == VORIGINAL) || (m_horizontal == HORIGINAL); + return (m_vertical == VORIGINAL) || (m_horizontal == HORIGINAL); } bool Alignment::isAuto() const { - return (m_vertical == VAUTO) || (m_horizontal == HAUTO); + return (m_vertical == VAUTO) || (m_horizontal == HAUTO); } } // namespace page_layout diff --git a/filters/page_layout/Alignment.h b/filters/page_layout/Alignment.h index 7ba2a17fc..37f1fbb55 100644 --- a/filters/page_layout/Alignment.h +++ b/filters/page_layout/Alignment.h @@ -29,50 +29,50 @@ class CommandLine; namespace page_layout { class Alignment { -public: - enum Vertical { TOP, VCENTER, BOTTOM, VAUTO, VORIGINAL }; + public: + enum Vertical { TOP, VCENTER, BOTTOM, VAUTO, VORIGINAL }; - enum Horizontal { LEFT, HCENTER, RIGHT, HAUTO, HORIGINAL }; + enum Horizontal { LEFT, HCENTER, RIGHT, HAUTO, HORIGINAL }; - /** - * \brief Constructs a null alignment. - */ - Alignment(); + /** + * \brief Constructs a null alignment. + */ + Alignment(); - Alignment(Vertical vertical, Horizontal horizontal); + Alignment(Vertical vertical, Horizontal horizontal); - explicit Alignment(const QDomElement& el); + explicit Alignment(const QDomElement& el); - Vertical vertical() const; + Vertical vertical() const; - void setVertical(Vertical vertical); + void setVertical(Vertical vertical); - Horizontal horizontal() const; + Horizontal horizontal() const; - void setHorizontal(Horizontal horizontal); + void setHorizontal(Horizontal horizontal); - bool isNull() const; + bool isNull() const; - void setNull(bool is_null); + void setNull(bool is_null); - bool isAutoVertical() const; + bool isAutoVertical() const; - bool isAutoHorizontal() const; + bool isAutoHorizontal() const; - bool isOriginal() const; + bool isOriginal() const; - bool isAuto() const; + bool isAuto() const; - bool operator==(const Alignment& other) const; + bool operator==(const Alignment& other) const; - bool operator!=(const Alignment& other) const; + bool operator!=(const Alignment& other) const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - Vertical m_vertical; - Horizontal m_horizontal; - bool m_isNull; + private: + Vertical m_vertical; + Horizontal m_horizontal; + bool m_isNull; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_ALIGNMENT_H_ diff --git a/filters/page_layout/ApplyDialog.cpp b/filters/page_layout/ApplyDialog.cpp index 0d02006c8..5b28aa020 100644 --- a/filters/page_layout/ApplyDialog.cpp +++ b/filters/page_layout/ApplyDialog.cpp @@ -17,72 +17,72 @@ */ #include "ApplyDialog.h" -#include "PageSelectionAccessor.h" #include +#include "PageSelectionAccessor.h" namespace page_layout { ApplyDialog::ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_selectedRanges(page_selection_accessor.selectedRanges()), - m_curPage(cur_page), - m_pScopeGroup(new QButtonGroup(this)) { - setupUi(this); - m_pScopeGroup->addButton(thisPageRB); - m_pScopeGroup->addButton(allPagesRB); - m_pScopeGroup->addButton(thisPageAndFollowersRB); - m_pScopeGroup->addButton(selectedPagesRB); - m_pScopeGroup->addButton(everyOtherRB); - m_pScopeGroup->addButton(thisEveryOtherRB); - m_pScopeGroup->addButton(everyOtherSelectedRB); + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_selectedRanges(page_selection_accessor.selectedRanges()), + m_curPage(cur_page), + m_scopeGroup(new QButtonGroup(this)) { + setupUi(this); + m_scopeGroup->addButton(thisPageRB); + m_scopeGroup->addButton(allPagesRB); + m_scopeGroup->addButton(thisPageAndFollowersRB); + m_scopeGroup->addButton(selectedPagesRB); + m_scopeGroup->addButton(everyOtherRB); + m_scopeGroup->addButton(thisEveryOtherRB); + m_scopeGroup->addButton(everyOtherSelectedRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - everyOtherSelectedRB->setEnabled(false); - everyOtherSelectedHint->setEnabled(false); - } + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + everyOtherSelectedRB->setEnabled(false); + everyOtherSelectedHint->setEnabled(false); + } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() = default; void ApplyDialog::onSubmit() { - std::set pages; + std::set pages; - // thisPageRB is intentionally not handled. - if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - emit accepted(m_selectedPages); - accept(); + // thisPageRB is intentionally not handled. + if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + emit accepted(m_selectedPages); + accept(); - return; - } else if (everyOtherRB->isChecked()) { - m_pages.selectEveryOther(m_curPage).swap(pages); - } else if (thisEveryOtherRB->isChecked()) { - std::set tmp; - m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); - auto it = tmp.begin(); - for (int i = 0; it != tmp.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - } else if (everyOtherSelectedRB->isChecked()) { - assert(m_selectedRanges.size() == 1); - const PageRange& range = m_selectedRanges.front(); - range.selectEveryOther(m_curPage).swap(pages); + return; + } else if (everyOtherRB->isChecked()) { + m_pages.selectEveryOther(m_curPage).swap(pages); + } else if (thisEveryOtherRB->isChecked()) { + std::set tmp; + m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); + auto it = tmp.begin(); + for (int i = 0; it != tmp.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } } + } else if (everyOtherSelectedRB->isChecked()) { + assert(m_selectedRanges.size() == 1); + const PageRange& range = m_selectedRanges.front(); + range.selectEveryOther(m_curPage).swap(pages); + } - emit accepted(pages); + emit accepted(pages); - // We assume the default connection from accepted() to accept() - // was removed. - accept(); + // We assume the default connection from accepted() to accept() + // was removed. + accept(); } // ApplyDialog::onSubmit } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/ApplyDialog.h b/filters/page_layout/ApplyDialog.h index ba2515470..6aa4d6d9b 100644 --- a/filters/page_layout/ApplyDialog.h +++ b/filters/page_layout/ApplyDialog.h @@ -19,39 +19,39 @@ #ifndef PAGE_LAYOUT_APPLYDIALOG_H_ #define PAGE_LAYOUT_APPLYDIALOG_H_ -#include "ui_PageLayoutApplyDialog.h" +#include +#include +#include #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include +#include "ui_PageLayoutApplyDialog.h" class PageSelectionAccessor; -class QButtonGroup; namespace page_layout { class ApplyDialog : public QDialog, private Ui::PageLayoutApplyDialog { - Q_OBJECT -public: - ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); - ~ApplyDialog() override; + ~ApplyDialog() override; -signals: + signals: - void accepted(const std::set& pages); + void accepted(const std::set& pages); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - std::set m_selectedPages; - std::vector m_selectedRanges; - PageId m_curPage; - QButtonGroup* m_pScopeGroup; + private: + PageSequence m_pages; + std::set m_selectedPages; + std::vector m_selectedRanges; + PageId m_curPage; + QButtonGroup* m_scopeGroup; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_APPLYDIALOG_H_ diff --git a/filters/page_layout/CMakeLists.txt b/filters/page_layout/CMakeLists.txt index 36d0cb329..ce7bbf83c 100644 --- a/filters/page_layout/CMakeLists.txt +++ b/filters/page_layout/CMakeLists.txt @@ -1,33 +1,33 @@ -PROJECT("Page Layout Filter") +project("Page Layout Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - Utils.cpp Utils.h - ImageView.cpp ImageView.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h - Params.cpp Params.h - Settings.cpp Settings.h - Thumbnail.cpp Thumbnail.h - ApplyDialog.cpp ApplyDialog.h - Alignment.cpp Alignment.h - OrderByWidthProvider.cpp OrderByWidthProvider.h - OrderByHeightProvider.cpp OrderByHeightProvider.h -) -SOURCE_GROUP("Sources" FILES ${sources}) +set( + sources + Utils.cpp Utils.h + ImageView.cpp ImageView.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h + Params.cpp Params.h + Settings.cpp Settings.h + Thumbnail.cpp Thumbnail.h + ApplyDialog.cpp ApplyDialog.h + Alignment.cpp Alignment.h + OrderByWidthProvider.cpp OrderByWidthProvider.h + OrderByHeightProvider.cpp OrderByHeightProvider.h + Guide.cpp Guide.h) +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(page_layout STATIC ${sources} ${ui_sources}) +add_library(page_layout STATIC ${sources} ${ui_sources}) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) \ No newline at end of file +translation_sources(scantailor ${sources} ${ui_files}) \ No newline at end of file diff --git a/filters/page_layout/CacheDrivenTask.cpp b/filters/page_layout/CacheDrivenTask.cpp index 1129936cf..5a29b04c2 100644 --- a/filters/page_layout/CacheDrivenTask.cpp +++ b/filters/page_layout/CacheDrivenTask.cpp @@ -18,22 +18,21 @@ #include "CacheDrivenTask.h" -#include #include -#include "Settings.h" -#include "Params.h" -#include "Thumbnail.h" +#include #include "IncompleteThumbnail.h" #include "PageInfo.h" +#include "Params.h" +#include "Settings.h" +#include "Thumbnail.h" #include "Utils.h" -#include "filters/output/CacheDrivenTask.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" +#include "filters/output/CacheDrivenTask.h" namespace page_layout { CacheDrivenTask::CacheDrivenTask(intrusive_ptr next_task, intrusive_ptr settings) - : m_ptrNextTask(std::move(next_task)), m_ptrSettings(std::move(settings)) { -} + : m_nextTask(std::move(next_task)), m_settings(std::move(settings)) {} CacheDrivenTask::~CacheDrivenTask() = default; @@ -42,52 +41,52 @@ void CacheDrivenTask::process(const PageInfo& page_info, const ImageTransformation& xform, const QRectF& page_rect, const QRectF& content_rect) { - const std::unique_ptr params(m_ptrSettings->getPageParams(page_info.id())); - if (!params || (params->contentSizeMM().isEmpty() && !content_rect.isEmpty())) { - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); - } - - return; + const std::unique_ptr params(m_settings->getPageParams(page_info.id())); + if (!params || (params->contentSizeMM().isEmpty() && !content_rect.isEmpty())) { + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); } - Params new_params(m_ptrSettings->updateContentSizeAndGetParams(page_info.id(), page_rect, content_rect, - params->contentSizeMM())); + return; + } - const QRectF adapted_content_rect(Utils::adaptContentRect(xform, content_rect)); - const QPolygonF content_rect_phys(xform.transformBack().map(adapted_content_rect)); - const QPolygonF page_rect_phys(Utils::calcPageRectPhys(xform, content_rect_phys, new_params, - m_ptrSettings->getAggregateHardSizeMM(), - m_ptrSettings->getAggregateContentRect())); - - ImageTransformation new_xform(xform); - new_xform.setPostCropArea(shiftToRoundedOrigin(new_xform.transform().map(page_rect_phys))); + Params new_params( + m_settings->updateContentSizeAndGetParams(page_info.id(), page_rect, content_rect, params->contentSizeMM())); - if (m_ptrNextTask) { - m_ptrNextTask->process(page_info, collector, new_xform, content_rect_phys); + const QRectF adapted_content_rect(Utils::adaptContentRect(xform, content_rect)); + const QPolygonF content_rect_phys(xform.transformBack().map(adapted_content_rect)); + const QPolygonF page_rect_phys(Utils::calcPageRectPhys(xform, content_rect_phys, new_params, + m_settings->getAggregateHardSizeMM(), + m_settings->getAggregateContentRect())); - return; - } + ImageTransformation new_xform(xform); + new_xform.setPostCropArea(shiftToRoundedOrigin(new_xform.transform().map(page_rect_phys))); - QSettings settings; - const double deviationCoef = settings.value("settings/marginsDeviationCoef", 0.35).toDouble(); - const double deviationThreshold = settings.value("settings/marginsDeviationThreshold", 1.0).toDouble(); + if (m_nextTask) { + m_nextTask->process(page_info, collector, new_xform, content_rect_phys); - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new Thumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_params, xform, - content_rect_phys, xform.transform().map(page_rect_phys).boundingRect(), - m_ptrSettings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); - } + return; + } + + QSettings settings; + const double deviationCoef = settings.value("settings/marginsDeviationCoef", 0.35).toDouble(); + const double deviationThreshold = settings.value("settings/marginsDeviationThreshold", 1.0).toDouble(); + + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr( + new Thumbnail(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_params, + xform, content_rect_phys, xform.transform().map(page_rect_phys).boundingRect(), + m_settings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); + } } // CacheDrivenTask::process QPolygonF CacheDrivenTask::shiftToRoundedOrigin(const QPolygonF& poly) { - const double x = poly.boundingRect().left(); - const double y = poly.boundingRect().top(); - const double shift_value_x = -(x - std::round(x)); - const double shift_value_y = -(y - std::round(y)); + const double x = poly.boundingRect().left(); + const double y = poly.boundingRect().top(); + const double shift_value_x = -(x - std::round(x)); + const double shift_value_y = -(y - std::round(y)); - return poly.translated(shift_value_x, shift_value_y); + return poly.translated(shift_value_x, shift_value_y); } } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/CacheDrivenTask.h b/filters/page_layout/CacheDrivenTask.h index bf0b97836..8a7fdc0d7 100644 --- a/filters/page_layout/CacheDrivenTask.h +++ b/filters/page_layout/CacheDrivenTask.h @@ -19,10 +19,10 @@ #ifndef PAGE_LAYOUT_CACHEDRIVENTASK_H_ #define PAGE_LAYOUT_CACHEDRIVENTASK_H_ +#include #include "NonCopyable.h" -#include "ref_countable.h" #include "intrusive_ptr.h" -#include +#include "ref_countable.h" class QRectF; class PageInfo; @@ -37,24 +37,24 @@ namespace page_layout { class Settings; class CacheDrivenTask : public ref_countable { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr next_task, intrusive_ptr settings); + public: + CacheDrivenTask(intrusive_ptr next_task, intrusive_ptr settings); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, - AbstractFilterDataCollector* collector, - const ImageTransformation& xform, - const QRectF& page_rect, - const QRectF& content_rect); + void process(const PageInfo& page_info, + AbstractFilterDataCollector* collector, + const ImageTransformation& xform, + const QRectF& page_rect, + const QRectF& content_rect); -private: - static QPolygonF shiftToRoundedOrigin(const QPolygonF& poly); + private: + static QPolygonF shiftToRoundedOrigin(const QPolygonF& poly); - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_CACHEDRIVENTASK_H_ diff --git a/filters/page_layout/Filter.cpp b/filters/page_layout/Filter.cpp index 4e8c9c400..d6cfafb90 100644 --- a/filters/page_layout/Filter.cpp +++ b/filters/page_layout/Filter.cpp @@ -17,206 +17,223 @@ */ #include "Filter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CacheDrivenTask.h" +#include "CommandLine.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" -#include "Task.h" -#include "Settings.h" +#include "OrderByHeightProvider.h" +#include "OrderByWidthProvider.h" #include "Params.h" #include "ProjectPages.h" #include "ProjectReader.h" #include "ProjectWriter.h" -#include "CacheDrivenTask.h" -#include "OrderByWidthProvider.h" -#include "OrderByHeightProvider.h" +#include "Settings.h" +#include "Task.h" #include "Utils.h" -#include -#include -#include -#include -#include -#include -#include -#include "CommandLine.h" -#include -#include -#include -#include namespace page_layout { Filter::Filter(intrusive_ptr pages, const PageSelectionAccessor& page_selection_accessor) - : m_ptrPages(std::move(pages)), m_ptrSettings(new Settings), m_selectedPageOrder(0) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); - } - - typedef PageOrderOption::ProviderPtr ProviderPtr; + : m_pages(std::move(pages)), m_settings(new Settings), m_selectedPageOrder(0) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, page_selection_accessor)); + } - const ProviderPtr default_order; - const auto order_by_width = make_intrusive(m_ptrSettings); - const auto order_by_height = make_intrusive(m_ptrSettings); - const auto order_by_deviation = make_intrusive(m_ptrSettings->deviationProvider()); - m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); - m_pageOrderOptions.emplace_back(tr("Order by increasing width"), order_by_width); - m_pageOrderOptions.emplace_back(tr("Order by increasing height"), order_by_height); - m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); + const PageOrderOption::ProviderPtr default_order; + const auto order_by_width = make_intrusive(m_settings); + const auto order_by_height = make_intrusive(m_settings); + const auto order_by_deviation = make_intrusive(m_settings->deviationProvider()); + m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); + m_pageOrderOptions.emplace_back(tr("Order by increasing width"), order_by_width); + m_pageOrderOptions.emplace_back(tr("Order by increasing height"), order_by_height); + m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); } Filter::~Filter() = default; QString Filter::getName() const { - return tr("Margins"); + return tr("Margins"); } PageView Filter::getView() const { - return PAGE_VIEW; + return PAGE_VIEW; } void Filter::selected() { - m_ptrSettings->removePagesMissingFrom(m_ptrPages->toPageSequence(getView())); + m_settings->removePagesMissingFrom(m_pages->toPageSequence(getView())); } int Filter::selectedPageOrder() const { - return m_selectedPageOrder; + return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { - assert((unsigned) option < m_pageOrderOptions.size()); - m_selectedPageOrder = option; + assert((unsigned) option < m_pageOrderOptions.size()); + m_selectedPageOrder = option; } std::vector Filter::pageOrderOptions() const { - return m_pageOrderOptions; + return m_pageOrderOptions; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) { - const Margins margins_mm(m_ptrSettings->getHardMarginsMM(page_info.id())); - const Alignment alignment(m_ptrSettings->getPageAlignment(page_info.id())); - m_ptrOptionsWidget->preUpdateUI(page_info, margins_mm, alignment); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); + const Margins margins_mm(m_settings->getHardMarginsMM(page_info.id())); + const Alignment alignment(m_settings->getPageAlignment(page_info.id())); + m_optionsWidget->preUpdateUI(page_info, margins_mm, alignment); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("page-layout")); + QDomElement filter_el(doc.createElement("page-layout")); - XmlMarshaller marshaller(doc); - filter_el.appendChild(marshaller.rectF(m_ptrSettings->getAggregateContentRect(), "aggregateContentRect")); + XmlMarshaller marshaller(doc); + filter_el.appendChild(marshaller.rectF(m_settings->getAggregateContentRect(), "aggregateContentRect")); + filter_el.setAttribute("showMiddleRect", m_settings->isShowingMiddleRectEnabled() ? "1" : "0"); - writer.enumPages([&](const PageId& page_id, int numeric_id) { - this->writePageSettings(doc, filter_el, page_id, numeric_id); - }); + if (!m_settings->guides().empty()) { + QDomElement guides_el(doc.createElement("guides")); + for (const Guide& guide : m_settings->guides()) { + guides_el.appendChild(guide.toXml(doc, "guide")); + } + filter_el.appendChild(guides_el); + } + + writer.enumPages( + [&](const PageId& page_id, int numeric_id) { this->writePageSettings(doc, filter_el, page_id, numeric_id); }); - return filter_el; + return filter_el; } void Filter::writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const std::unique_ptr params(m_ptrSettings->getPageParams(page_id)); - if (!params) { - return; - } + const std::unique_ptr params(m_settings->getPageParams(page_id)); + if (!params) { + return; + } - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); - page_el.appendChild(params->toXml(doc, "params")); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); + page_el.appendChild(params->toXml(doc, "params")); - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); + m_settings->clear(); - const QDomElement filter_el(filters_el.namedItem("page-layout").toElement()); + const QDomElement filter_el(filters_el.namedItem("page-layout").toElement()); - const QDomElement rect_el = filter_el.namedItem("aggregateContentRect").toElement(); - if (!rect_el.isNull()) { - m_ptrSettings->setAggregateContentRect(XmlUnmarshaller::rectF(rect_el)); - } + const QDomElement rect_el = filter_el.namedItem("aggregateContentRect").toElement(); + if (!rect_el.isNull()) { + m_settings->setAggregateContentRect(XmlUnmarshaller::rectF(rect_el)); + } + m_settings->enableShowingMiddleRect(filter_el.attribute("showMiddleRect") == "1"); - const QString page_tag_name("page"); - QDomNode node(filter_el.firstChild()); + const QDomElement guides_el = filter_el.namedItem("guides").toElement(); + if (!guides_el.isNull()) { + QDomNode node(guides_el.firstChild()); for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const QDomElement params_el(el.namedItem("params").toElement()); - if (params_el.isNull()) { - continue; - } - - const Params params(params_el); - m_ptrSettings->setPageParams(page_id, params); + if (!node.isElement() || (node.nodeName() != "guide")) { + continue; + } + m_settings->guides().emplace_back(node.toElement()); + } + } + + const QString page_tag_name("page"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; + } + + const QDomElement params_el(el.namedItem("params").toElement()); + if (params_el.isNull()) { + continue; + } + + const Params params(params_el); + m_settings->setPageParams(page_id, params); + } } // Filter::loadSettings void Filter::setContentBox(const PageId& page_id, const ImageTransformation& xform, const QRectF& content_rect) { - const QSizeF content_size_mm(Utils::calcRectSizeMM(xform, content_rect)); - m_ptrSettings->setContentSizeMM(page_id, content_size_mm); + const QSizeF content_size_mm(Utils::calcRectSizeMM(xform, content_rect)); + m_settings->setContentSizeMM(page_id, content_size_mm); } void Filter::invalidateContentBox(const PageId& page_id) { - m_ptrSettings->invalidateContentSize(page_id); + m_settings->invalidateContentSize(page_id); } bool Filter::checkReadyForOutput(const ProjectPages& pages, const PageId* ignore) { - const PageSequence snapshot(pages.toPageSequence(PAGE_VIEW)); + const PageSequence snapshot(pages.toPageSequence(PAGE_VIEW)); - return m_ptrSettings->checkEverythingDefined(snapshot, ignore); + return m_settings->checkEverythingDefined(snapshot, ignore); } intrusive_ptr Filter::createTask(const PageId& page_id, intrusive_ptr next_task, const bool batch, const bool debug) { - return make_intrusive(intrusive_ptr(this), std::move(next_task), m_ptrSettings, page_id, batch, - debug); + return make_intrusive(intrusive_ptr(this), std::move(next_task), m_settings, page_id, batch, debug); } intrusive_ptr Filter::createCacheDrivenTask(intrusive_ptr next_task) { - return make_intrusive(std::move(next_task), m_ptrSettings); + return make_intrusive(std::move(next_task), m_settings); } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->isParamsNull(page_info.id())) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::PageLayoutParams& pageLayoutParams = defaultParams.getPageLayoutParams(); + if (!m_settings->isParamsNull(page_info.id())) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::PageLayoutParams& pageLayoutParams = defaultParams.getPageLayoutParams(); - const UnitsConverter unitsConverter(page_info.metadata().dpi()); + const UnitsConverter unitsConverter(page_info.metadata().dpi()); - const Margins& margins = pageLayoutParams.getHardMargins(); - double leftMargin = margins.left(); - double topMargin = margins.top(); - double rightMargin = margins.right(); - double bottomMargin = margins.bottom(); - unitsConverter.convert(leftMargin, topMargin, defaultParams.getUnits(), MILLIMETRES); - unitsConverter.convert(rightMargin, bottomMargin, defaultParams.getUnits(), MILLIMETRES); + const Margins& margins = pageLayoutParams.getHardMargins(); + double leftMargin = margins.left(); + double topMargin = margins.top(); + double rightMargin = margins.right(); + double bottomMargin = margins.bottom(); + unitsConverter.convert(leftMargin, topMargin, defaultParams.getUnits(), MILLIMETRES); + unitsConverter.convert(rightMargin, bottomMargin, defaultParams.getUnits(), MILLIMETRES); - m_ptrSettings->setPageParams(page_info.id(), - Params(Margins(leftMargin, topMargin, rightMargin, bottomMargin), QRectF(), QRectF(), - QSizeF(), pageLayoutParams.getAlignment(), pageLayoutParams.isAutoMargins())); + m_settings->setPageParams( + page_info.id(), Params(Margins(leftMargin, topMargin, rightMargin, bottomMargin), QRectF(), QRectF(), QSizeF(), + pageLayoutParams.getAlignment(), pageLayoutParams.isAutoMargins())); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); } } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/Filter.h b/filters/page_layout/Filter.h index c5810d086..80299a914 100644 --- a/filters/page_layout/Filter.h +++ b/filters/page_layout/Filter.h @@ -19,15 +19,15 @@ #ifndef PAGE_LAYOUT_FILTER_H_ #define PAGE_LAYOUT_FILTER_H_ -#include "NonCopyable.h" +#include +#include #include "AbstractFilter.h" -#include "PageView.h" -#include "intrusive_ptr.h" #include "FilterResult.h" -#include "SafeDeletingQObjectPtr.h" +#include "NonCopyable.h" #include "PageOrderOption.h" -#include -#include +#include "PageView.h" +#include "SafeDeletingQObjectPtr.h" +#include "intrusive_ptr.h" class ProjectPages; class PageSelectionAccessor; @@ -47,59 +47,56 @@ class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) - Q_DECLARE_TR_FUNCTIONS(page_layout::Filter) -public: - Filter(intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor); + Q_DECLARE_TR_FUNCTIONS(page_layout::Filter) + public: + Filter(intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor); - ~Filter() override; + ~Filter() override; - QString getName() const override; + QString getName() const override; - PageView getView() const override; + PageView getView() const override; - void selected() override; + void selected() override; - int selectedPageOrder() const override; + int selectedPageOrder() const override; - void selectPageOrder(int option) override; + void selectPageOrder(int option) override; - std::vector pageOrderOptions() const override; + std::vector pageOrderOptions() const override; - void performRelinking(const AbstractRelinker& relinker) override; + void performRelinking(const AbstractRelinker& relinker) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; + QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void loadDefaultSettings(const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - void setContentBox(const PageId& page_id, const ImageTransformation& xform, const QRectF& content_rect); + void setContentBox(const PageId& page_id, const ImageTransformation& xform, const QRectF& content_rect); - void invalidateContentBox(const PageId& page_id); + void invalidateContentBox(const PageId& page_id); - bool checkReadyForOutput(const ProjectPages& pages, const PageId* ignore = nullptr); + bool checkReadyForOutput(const ProjectPages& pages, const PageId* ignore = nullptr); - intrusive_ptr createTask(const PageId& page_id, - intrusive_ptr next_task, - bool batch, - bool debug); + intrusive_ptr createTask(const PageId& page_id, intrusive_ptr next_task, bool batch, bool debug); - intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); + intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); - OptionsWidget* optionsWidget(); + OptionsWidget* optionsWidget(); -private: - void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + private: + void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; - std::vector m_pageOrderOptions; - int m_selectedPageOrder; + intrusive_ptr m_pages; + intrusive_ptr m_settings; + SafeDeletingQObjectPtr m_optionsWidget; + std::vector m_pageOrderOptions; + int m_selectedPageOrder; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_FILTER_H_ diff --git a/filters/page_layout/Guide.cpp b/filters/page_layout/Guide.cpp new file mode 100644 index 000000000..9d6a4f3b1 --- /dev/null +++ b/filters/page_layout/Guide.cpp @@ -0,0 +1,67 @@ + +#include "Guide.h" +#include "../../Utils.h" + +namespace page_layout { +Guide::Guide(const Qt::Orientation orientation, const double position) + : m_orientation(orientation), m_position(position) {} + +Guide::Guide(const QLineF& line) + : m_orientation(lineOrientation(line)), m_position((m_orientation == Qt::Horizontal) ? line.y1() : line.x1()) {} + +Qt::Orientation Guide::getOrientation() const { + return m_orientation; +} + +double Guide::getPosition() const { + return m_position; +} + +void Guide::setPosition(double position) { + Guide::m_position = position; +} + +Qt::Orientation Guide::lineOrientation(const QLineF& line) { + const double angle_cos = std::abs((line.p2().x() - line.p1().x()) / line.length()); + return (angle_cos > (1.0 / std::sqrt(2))) ? Qt::Horizontal : Qt::Vertical; +} + +Guide::operator QLineF() const { + if (m_orientation == Qt::Horizontal) { + return QLineF(0, m_position, 1, m_position); + } else { + return QLineF(m_position, 0, m_position, 1); + } +} + +Guide::Guide(const QDomElement& el) + : m_orientation(orientationFromString(el.attribute("orientation"))), + m_position(el.attribute("position").toDouble()) {} + +QDomElement Guide::toXml(QDomDocument& doc, const QString& name) const { + QDomElement el = doc.createElement(name); + + el.setAttribute("orientation", orientationToString(m_orientation)); + el.setAttribute("position", Utils::doubleToString(m_position)); + + return el; +} + +QString Guide::orientationToString(const Qt::Orientation orientation) { + if (orientation == Qt::Horizontal) { + return "horizontal"; + } else { + return "vertical"; + } +} + +Qt::Orientation Guide::orientationFromString(const QString& str) { + if (str == "vertical") { + return Qt::Vertical; + } else { + return Qt::Horizontal; + } +} + +Guide::Guide() : m_orientation(Qt::Horizontal), m_position(0) {} +} // namespace page_layout diff --git a/filters/page_layout/Guide.h b/filters/page_layout/Guide.h new file mode 100644 index 000000000..3d1e0ecd8 --- /dev/null +++ b/filters/page_layout/Guide.h @@ -0,0 +1,41 @@ + +#ifndef SCANTAILOR_GUIDE_H +#define SCANTAILOR_GUIDE_H + +#include +#include + +namespace page_layout { +class Guide { + public: + Guide(); + + Guide(Qt::Orientation orientation, double position); + + Guide(const QLineF& line); + + explicit Guide(const QDomElement& el); + + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + operator QLineF() const; + + Qt::Orientation getOrientation() const; + + double getPosition() const; + + void setPosition(double position); + + private: + static Qt::Orientation lineOrientation(const QLineF& line); + + static QString orientationToString(Qt::Orientation orientation); + + static Qt::Orientation orientationFromString(const QString& str); + + Qt::Orientation m_orientation; + double m_position; +}; +} // namespace page_layout + +#endif // ifndef SCANTAILOR_GUIDE_H \ No newline at end of file diff --git a/filters/page_layout/ImageView.cpp b/filters/page_layout/ImageView.cpp index 61cd3ded5..9bbcf1421 100644 --- a/filters/page_layout/ImageView.cpp +++ b/filters/page_layout/ImageView.cpp @@ -17,17 +17,24 @@ */ #include "ImageView.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ImagePresentation.h" #include "OptionsWidget.h" +#include "Params.h" #include "Settings.h" -#include "ImagePresentation.h" #include "Utils.h" -#include "Params.h" #include "imageproc/PolygonUtils.h" -#include -#include -#include -#include -#include using namespace imageproc; @@ -36,424 +43,473 @@ ImageView::ImageView(const intrusive_ptr& settings, const PageId& page_id, const QImage& image, const QImage& downscaled_image, + const imageproc::GrayImage& gray_image, const ImageTransformation& xform, const QRectF& adapted_content_rect, const OptionsWidget& opt_widget) - : ImageViewBase(image, - downscaled_image, - ImagePresentation(xform.transform(), xform.resultingPreCropArea()), - Margins(5, 5, 5, 5)), - m_xform(xform), - m_dragHandler(*this), - m_zoomHandler(*this), - m_ptrSettings(settings), - m_pageId(page_id), - m_pixelsToMmXform(UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)), - m_mmToPixelsXform(m_pixelsToMmXform.inverted()), - m_innerRect(adapted_content_rect), - m_aggregateHardSizeMM(settings->getAggregateHardSizeMM()), - m_committedAggregateHardSizeMM(m_aggregateHardSizeMM), - m_alignment(opt_widget.alignment()), - m_leftRightLinked(opt_widget.leftRightLinked()), - m_topBottomLinked(opt_widget.topBottomLinked()) { - setMouseTracking(true); - - interactionState().setDefaultStatusTip(tr("Resize margins by dragging any of the solid lines.")); - - // Setup interaction stuff. - static const int masks_by_edge[] = {TOP, RIGHT, BOTTOM, LEFT}; - static const int masks_by_corner[] = {TOP | LEFT, TOP | RIGHT, BOTTOM | RIGHT, BOTTOM | LEFT}; - for (int i = 0; i < 4; ++i) { - // Proximity priority - inner rect higher than middle, corners higher than edges. - m_innerCorners[i].setProximityPriorityCallback(boost::lambda::constant(4)); - m_innerEdges[i].setProximityPriorityCallback(boost::lambda::constant(3)); - m_middleCorners[i].setProximityPriorityCallback(boost::lambda::constant(2)); - m_middleEdges[i].setProximityPriorityCallback(boost::lambda::constant(1)); - - // Proximity. - m_innerCorners[i].setProximityCallback( - boost::bind(&ImageView::cornerProximity, this, masks_by_corner[i], &m_innerRect, _1)); - m_middleCorners[i].setProximityCallback( - boost::bind(&ImageView::cornerProximity, this, masks_by_corner[i], &m_middleRect, _1)); - m_innerEdges[i].setProximityCallback( - boost::bind(&ImageView::edgeProximity, this, masks_by_edge[i], &m_innerRect, _1)); - m_middleEdges[i].setProximityCallback( - boost::bind(&ImageView::edgeProximity, this, masks_by_edge[i], &m_middleRect, _1)); - // Drag initiation. - m_innerCorners[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); - m_middleCorners[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); - m_innerEdges[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); - m_middleEdges[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); - - // Drag continuation. - m_innerCorners[i].setDragContinuationCallback( - boost::bind(&ImageView::innerRectDragContinuation, this, masks_by_corner[i], _1)); - m_middleCorners[i].setDragContinuationCallback( - boost::bind(&ImageView::middleRectDragContinuation, this, masks_by_corner[i], _1)); - m_innerEdges[i].setDragContinuationCallback( - boost::bind(&ImageView::innerRectDragContinuation, this, masks_by_edge[i], _1)); - m_middleEdges[i].setDragContinuationCallback( - boost::bind(&ImageView::middleRectDragContinuation, this, masks_by_edge[i], _1)); - // Drag finishing. - m_innerCorners[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - m_middleCorners[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - m_innerEdges[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - m_middleEdges[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - - m_innerCornerHandlers[i].setObject(&m_innerCorners[i]); - m_middleCornerHandlers[i].setObject(&m_middleCorners[i]); - m_innerEdgeHandlers[i].setObject(&m_innerEdges[i]); - m_middleEdgeHandlers[i].setObject(&m_middleEdges[i]); - - Qt::CursorShape corner_cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; - m_innerCornerHandlers[i].setProximityCursor(corner_cursor); - m_innerCornerHandlers[i].setInteractionCursor(corner_cursor); - m_middleCornerHandlers[i].setProximityCursor(corner_cursor); - m_middleCornerHandlers[i].setInteractionCursor(corner_cursor); - - Qt::CursorShape edge_cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; - m_innerEdgeHandlers[i].setProximityCursor(edge_cursor); - m_innerEdgeHandlers[i].setInteractionCursor(edge_cursor); - m_middleEdgeHandlers[i].setProximityCursor(edge_cursor); - m_middleEdgeHandlers[i].setInteractionCursor(edge_cursor); - - makeLastFollower(m_innerCornerHandlers[i]); - makeLastFollower(m_innerEdgeHandlers[i]); - makeLastFollower(m_middleCornerHandlers[i]); - makeLastFollower(m_middleEdgeHandlers[i]); + : ImageViewBase(image, + downscaled_image, + ImagePresentation(xform.transform(), xform.resultingPreCropArea()), + Margins(5, 5, 5, 5)), + m_xform(xform), + m_dragHandler(*this), + m_zoomHandler(*this), + m_settings(settings), + m_pageId(page_id), + m_pixelsToMmXform(UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)), + m_mmToPixelsXform(m_pixelsToMmXform.inverted()), + m_innerRect(adapted_content_rect), + m_aggregateHardSizeMM(settings->getAggregateHardSizeMM()), + m_committedAggregateHardSizeMM(m_aggregateHardSizeMM), + m_alignment(opt_widget.alignment()), + m_leftRightLinked(opt_widget.leftRightLinked()), + m_topBottomLinked(opt_widget.topBottomLinked()), + m_contextMenu(new QMenu(this)), + m_guidesFreeIndex(0), + m_guideUnderMouse(-1), + m_innerRectVerticalDragModifier(Qt::ControlModifier), + m_innerRectHorizontalDragModifier(Qt::ShiftModifier), + m_nullContentRect((m_innerRect.width() < 1) && (m_innerRect.height() < 1)) { + setMouseTracking(true); + + interactionState().setDefaultStatusTip(tr("Resize margins by dragging any of the solid lines.")); + + // Setup interaction stuff. + static const int masks_by_edge[] = {TOP, RIGHT, BOTTOM, LEFT}; + static const int masks_by_corner[] = {TOP | LEFT, TOP | RIGHT, BOTTOM | RIGHT, BOTTOM | LEFT}; + for (int i = 0; i < 4; ++i) { + // Proximity priority - inner rect higher than middle, corners higher than edges. + m_innerCorners[i].setProximityPriorityCallback(boost::lambda::constant(4)); + m_innerEdges[i].setProximityPriorityCallback(boost::lambda::constant(3)); + m_middleCorners[i].setProximityPriorityCallback(boost::lambda::constant(2)); + m_middleEdges[i].setProximityPriorityCallback(boost::lambda::constant(1)); + + // Proximity. + m_innerCorners[i].setProximityCallback( + boost::bind(&ImageView::cornerProximity, this, masks_by_corner[i], &m_innerRect, _1)); + m_middleCorners[i].setProximityCallback( + boost::bind(&ImageView::cornerProximity, this, masks_by_corner[i], &m_middleRect, _1)); + m_innerEdges[i].setProximityCallback( + boost::bind(&ImageView::edgeProximity, this, masks_by_edge[i], &m_innerRect, _1)); + m_middleEdges[i].setProximityCallback( + boost::bind(&ImageView::edgeProximity, this, masks_by_edge[i], &m_middleRect, _1)); + // Drag initiation. + m_innerCorners[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); + m_middleCorners[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); + m_innerEdges[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); + m_middleEdges[i].setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); + + // Drag continuation. + m_innerCorners[i].setDragContinuationCallback( + boost::bind(&ImageView::innerRectDragContinuation, this, masks_by_corner[i], _1)); + m_middleCorners[i].setDragContinuationCallback( + boost::bind(&ImageView::middleRectDragContinuation, this, masks_by_corner[i], _1)); + m_innerEdges[i].setDragContinuationCallback( + boost::bind(&ImageView::innerRectDragContinuation, this, masks_by_edge[i], _1)); + m_middleEdges[i].setDragContinuationCallback( + boost::bind(&ImageView::middleRectDragContinuation, this, masks_by_edge[i], _1)); + // Drag finishing. + m_innerCorners[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + m_middleCorners[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + m_innerEdges[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + m_middleEdges[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + + m_innerCornerHandlers[i].setObject(&m_innerCorners[i]); + m_middleCornerHandlers[i].setObject(&m_middleCorners[i]); + m_innerEdgeHandlers[i].setObject(&m_innerEdges[i]); + m_middleEdgeHandlers[i].setObject(&m_middleEdges[i]); + + Qt::CursorShape corner_cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; + m_innerCornerHandlers[i].setProximityCursor(corner_cursor); + m_innerCornerHandlers[i].setInteractionCursor(corner_cursor); + m_middleCornerHandlers[i].setProximityCursor(corner_cursor); + m_middleCornerHandlers[i].setInteractionCursor(corner_cursor); + + Qt::CursorShape edge_cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; + m_innerEdgeHandlers[i].setProximityCursor(edge_cursor); + m_innerEdgeHandlers[i].setInteractionCursor(edge_cursor); + m_middleEdgeHandlers[i].setProximityCursor(edge_cursor); + m_middleEdgeHandlers[i].setInteractionCursor(edge_cursor); + + if (isShowingMiddleRectEnabled()) { + makeLastFollower(m_middleCornerHandlers[i]); + makeLastFollower(m_middleEdgeHandlers[i]); } - - rootInteractionHandler().makeLastFollower(*this); - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); - - recalcBoxesAndFit(opt_widget.marginsMM()); + if (!m_nullContentRect) { + makeLastFollower(m_innerCornerHandlers[i]); + makeLastFollower(m_innerEdgeHandlers[i]); + } + } + + { + m_innerRectArea.setProximityCallback(boost::bind(&ImageView::rectProximity, this, boost::ref(m_innerRect), _1)); + m_innerRectArea.setDragInitiatedCallback(boost::bind(&ImageView::dragInitiated, this, _1)); + m_innerRectArea.setDragContinuationCallback(boost::bind(&ImageView::innerRectMoveRequest, this, _1, _2)); + m_innerRectArea.setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + m_innerRectAreaHandler.setObject(&m_innerRectArea); + m_innerRectAreaHandler.setProximityStatusTip(tr("Hold left mouse button to drag the page content.")); + m_innerRectAreaHandler.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); + m_innerRectAreaHandler.setKeyboardModifiers({m_innerRectVerticalDragModifier, m_innerRectHorizontalDragModifier, + m_innerRectVerticalDragModifier | m_innerRectHorizontalDragModifier}); + Qt::CursorShape cursor = Qt::DragMoveCursor; + m_innerRectAreaHandler.setProximityCursor(cursor); + m_innerRectAreaHandler.setInteractionCursor(cursor); + makeLastFollower(m_innerRectAreaHandler); + } + + rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); + + setupContextMenuInteraction(); + setupGuides(); + + recalcBoxesAndFit(opt_widget.marginsMM()); + + buildContentImage(gray_image, xform); } ImageView::~ImageView() = default; void ImageView::marginsSetExternally(const Margins& margins_mm) { - const AggregateSizeChanged changed = commitHardMargins(margins_mm); + const AggregateSizeChanged changed = commitHardMargins(margins_mm); - recalcBoxesAndFit(margins_mm); + recalcBoxesAndFit(margins_mm); - invalidateThumbnails(changed); + invalidateThumbnails(changed); } void ImageView::leftRightLinkToggled(const bool linked) { - m_leftRightLinked = linked; - if (linked) { - Margins margins_mm(calcHardMarginsMM()); - if (margins_mm.left() != margins_mm.right()) { - const double new_margin = std::min(margins_mm.left(), margins_mm.right()); - margins_mm.setLeft(new_margin); - margins_mm.setRight(new_margin); + m_leftRightLinked = linked; + if (linked) { + Margins margins_mm(calcHardMarginsMM()); + if (margins_mm.left() != margins_mm.right()) { + const double new_margin = std::min(margins_mm.left(), margins_mm.right()); + margins_mm.setLeft(new_margin); + margins_mm.setRight(new_margin); - const AggregateSizeChanged changed = commitHardMargins(margins_mm); + const AggregateSizeChanged changed = commitHardMargins(margins_mm); - recalcBoxesAndFit(margins_mm); - emit marginsSetLocally(margins_mm); + recalcBoxesAndFit(margins_mm); + emit marginsSetLocally(margins_mm); - invalidateThumbnails(changed); - } + invalidateThumbnails(changed); } + } } void ImageView::topBottomLinkToggled(const bool linked) { - m_topBottomLinked = linked; - if (linked) { - Margins margins_mm(calcHardMarginsMM()); - if (margins_mm.top() != margins_mm.bottom()) { - const double new_margin = std::min(margins_mm.top(), margins_mm.bottom()); - margins_mm.setTop(new_margin); - margins_mm.setBottom(new_margin); + m_topBottomLinked = linked; + if (linked) { + Margins margins_mm(calcHardMarginsMM()); + if (margins_mm.top() != margins_mm.bottom()) { + const double new_margin = std::min(margins_mm.top(), margins_mm.bottom()); + margins_mm.setTop(new_margin); + margins_mm.setBottom(new_margin); - const AggregateSizeChanged changed = commitHardMargins(margins_mm); + const AggregateSizeChanged changed = commitHardMargins(margins_mm); - recalcBoxesAndFit(margins_mm); - emit marginsSetLocally(margins_mm); + recalcBoxesAndFit(margins_mm); + emit marginsSetLocally(margins_mm); - invalidateThumbnails(changed); - } + invalidateThumbnails(changed); } + } } void ImageView::alignmentChanged(const Alignment& alignment) { - m_alignment = alignment; + m_alignment = alignment; - const Settings::AggregateSizeChanged size_changed = m_ptrSettings->setPageAlignment(m_pageId, alignment); + const Settings::AggregateSizeChanged size_changed = m_settings->setPageAlignment(m_pageId, alignment); - recalcBoxesAndFit(calcHardMarginsMM()); + recalcBoxesAndFit(calcHardMarginsMM()); - if (size_changed == Settings::AGGREGATE_SIZE_CHANGED) { - emit invalidateAllThumbnails(); - } else { - emit invalidateThumbnail(m_pageId); - } + enableGuidesInteraction(!m_alignment.isNull()); + forceInscribeGuides(); + + enableMiddleRectInteraction(isShowingMiddleRectEnabled()); + + if (size_changed == Settings::AGGREGATE_SIZE_CHANGED) { + emit invalidateAllThumbnails(); + } else { + emit invalidateThumbnail(m_pageId); + } } void ImageView::aggregateHardSizeChanged() { - m_aggregateHardSizeMM = m_ptrSettings->getAggregateHardSizeMM(); - m_committedAggregateHardSizeMM = m_aggregateHardSizeMM; - recalcOuterRect(); - updatePresentationTransform(FIT); + m_aggregateHardSizeMM = m_settings->getAggregateHardSizeMM(); + m_committedAggregateHardSizeMM = m_aggregateHardSizeMM; + recalcOuterRect(); + updatePresentationTransform(FIT); } void ImageView::onPaint(QPainter& painter, const InteractionState& interaction) { - QColor bg_color; - QColor fg_color; - if (m_alignment.isNull()) { - // "Align with other pages" is turned off. - // Different color is useful on a thumbnail list to - // distinguish "safe" pages from potentially problematic ones. - bg_color = QColor(0x58, 0x7f, 0xf4, 70); - fg_color = QColor(0x00, 0x52, 0xff); - } else { - bg_color = QColor(0xbb, 0x00, 0xff, 40); - fg_color = QColor(0xbe, 0x5b, 0xec); - } - - QPainterPath outer_outline; - outer_outline.addPolygon(PolygonUtils::round(m_alignment.isNull() ? m_middleRect : m_outerRect)); - - QPainterPath content_outline; - content_outline.addPolygon(PolygonUtils::round(m_innerRect)); - - painter.setRenderHint(QPainter::Antialiasing, false); - - // we round inner rect to check whether content rect is empty and this is just an adapted rect. - const bool isNullContentRect = m_innerRect.toRect().isEmpty(); - - painter.setPen(Qt::NoPen); - painter.setBrush(bg_color); - - if (!isNullContentRect) { - painter.drawPath(outer_outline.subtracted(content_outline)); - } else { - painter.drawPath(outer_outline); - } - - QPen pen(fg_color); - pen.setCosmetic(true); - pen.setWidthF(2.0); + QColor bg_color; + QColor fg_color; + if (m_alignment.isNull()) { + // "Align with other pages" is turned off. + // Different color is useful on a thumbnail list to + // distinguish "safe" pages from potentially problematic ones. + bg_color = QColor(0x58, 0x7f, 0xf4, 70); + fg_color = QColor(0x00, 0x52, 0xff); + } else { + bg_color = QColor(0xbb, 0x00, 0xff, 40); + fg_color = QColor(0xbe, 0x5b, 0xec); + } + + QPainterPath outer_outline; + outer_outline.addPolygon(PolygonUtils::round(m_alignment.isNull() ? m_middleRect : m_outerRect)); + + QPainterPath content_outline; + content_outline.addPolygon(PolygonUtils::round(m_innerRect)); + + painter.setRenderHint(QPainter::Antialiasing, false); + + painter.setPen(Qt::NoPen); + painter.setBrush(bg_color); + + if (!m_nullContentRect) { + painter.drawPath(outer_outline.subtracted(content_outline)); + } else { + painter.drawPath(outer_outline); + } + + QPen pen(fg_color); + pen.setCosmetic(true); + pen.setWidthF(2.0); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + if (isShowingMiddleRectEnabled()) { + painter.drawRect(m_middleRect); + } + if (!m_nullContentRect) { + painter.drawRect(m_innerRect); + } + + if (!m_alignment.isNull()) { + pen.setStyle(Qt::DashLine); painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - - if (!isNullContentRect || m_alignment.isNull()) { - painter.drawRect(m_middleRect); - } - if (!isNullContentRect) { - painter.drawRect(m_innerRect); - } - - if (!m_alignment.isNull()) { - pen.setStyle(Qt::DashLine); - painter.setPen(pen); - painter.drawRect(m_outerRect); + painter.drawRect(m_outerRect); + + // Draw guides. + if (!m_guides.empty()) { + painter.setWorldTransform(QTransform()); + + QPen pen(QColor(0x00, 0x9d, 0x9f)); + pen.setStyle(Qt::DashLine); + pen.setCosmetic(true); + pen.setWidthF(2.0); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + for (const auto& idxAndGuide : m_guides) { + const QLineF guide(widgetGuideLine(idxAndGuide.first)); + painter.drawLine(guide); + } } + } } // ImageView::onPaint Proximity ImageView::cornerProximity(const int edge_mask, const QRectF* box, const QPointF& mouse_pos) const { - const QRectF r(virtualToWidget().mapRect(*box)); - QPointF pt; - - if (edge_mask & TOP) { - pt.setY(r.top()); - } else if (edge_mask & BOTTOM) { - pt.setY(r.bottom()); - } - - if (edge_mask & LEFT) { - pt.setX(r.left()); - } else if (edge_mask & RIGHT) { - pt.setX(r.right()); - } - - return Proximity(pt, mouse_pos); + const QRectF r(virtualToWidget().mapRect(*box)); + QPointF pt; + + if (edge_mask & TOP) { + pt.setY(r.top()); + } else if (edge_mask & BOTTOM) { + pt.setY(r.bottom()); + } + + if (edge_mask & LEFT) { + pt.setX(r.left()); + } else if (edge_mask & RIGHT) { + pt.setX(r.right()); + } + + return Proximity(pt, mouse_pos); } Proximity ImageView::edgeProximity(const int edge_mask, const QRectF* box, const QPointF& mouse_pos) const { - const QRectF r(virtualToWidget().mapRect(*box)); - QLineF line; - - switch (edge_mask) { - case TOP: - line.setP1(r.topLeft()); - line.setP2(r.topRight()); - break; - case BOTTOM: - line.setP1(r.bottomLeft()); - line.setP2(r.bottomRight()); - break; - case LEFT: - line.setP1(r.topLeft()); - line.setP2(r.bottomLeft()); - break; - case RIGHT: - line.setP1(r.topRight()); - line.setP2(r.bottomRight()); - break; - default: - assert(!"Unreachable"); - } - - return Proximity::pointAndLineSegment(mouse_pos, line); + const QRectF r(virtualToWidget().mapRect(*box)); + QLineF line; + + switch (edge_mask) { + case TOP: + line.setP1(r.topLeft()); + line.setP2(r.topRight()); + break; + case BOTTOM: + line.setP1(r.bottomLeft()); + line.setP2(r.bottomRight()); + break; + case LEFT: + line.setP1(r.topLeft()); + line.setP2(r.bottomLeft()); + break; + case RIGHT: + line.setP1(r.topRight()); + line.setP2(r.bottomRight()); + break; + default: + assert(!"Unreachable"); + } + + return Proximity::pointAndLineSegment(mouse_pos, line); } void ImageView::dragInitiated(const QPointF& mouse_pos) { - m_beforeResizing.middleWidgetRect = virtualToWidget().mapRect(m_middleRect); - m_beforeResizing.virtToWidget = virtualToWidget(); - m_beforeResizing.widgetToVirt = widgetToVirtual(); - m_beforeResizing.mousePos = mouse_pos; - m_beforeResizing.focalPoint = getWidgetFocalPoint(); + m_beforeResizing.middleWidgetRect = virtualToWidget().mapRect(m_middleRect); + m_beforeResizing.virtToWidget = virtualToWidget(); + m_beforeResizing.widgetToVirt = widgetToVirtual(); + m_beforeResizing.mousePos = mouse_pos; + m_beforeResizing.focalPoint = getWidgetFocalPoint(); } void ImageView::innerRectDragContinuation(int edge_mask, const QPointF& mouse_pos) { - // What really happens when we resize the inner box is resizing - // the middle box in the opposite direction and moving the scene - // on screen so that the object being dragged is still under mouse. - - const QPointF delta(mouse_pos - m_beforeResizing.mousePos); - qreal left_adjust = 0; - qreal right_adjust = 0; - qreal top_adjust = 0; - qreal bottom_adjust = 0; - - if (edge_mask & LEFT) { - left_adjust = delta.x(); - if (m_leftRightLinked) { - right_adjust = -left_adjust; - } - } else if (edge_mask & RIGHT) { - right_adjust = delta.x(); - if (m_leftRightLinked) { - left_adjust = -right_adjust; - } + // What really happens when we resize the inner box is resizing + // the middle box in the opposite direction and moving the scene + // on screen so that the object being dragged is still under mouse. + + const QPointF delta(mouse_pos - m_beforeResizing.mousePos); + qreal left_adjust = 0; + qreal right_adjust = 0; + qreal top_adjust = 0; + qreal bottom_adjust = 0; + + if (edge_mask & LEFT) { + left_adjust = delta.x(); + if (m_leftRightLinked) { + right_adjust = -left_adjust; } - if (edge_mask & TOP) { - top_adjust = delta.y(); - if (m_topBottomLinked) { - bottom_adjust = -top_adjust; - } - } else if (edge_mask & BOTTOM) { - bottom_adjust = delta.y(); - if (m_topBottomLinked) { - top_adjust = -bottom_adjust; - } + } else if (edge_mask & RIGHT) { + right_adjust = delta.x(); + if (m_leftRightLinked) { + left_adjust = -right_adjust; } + } + if (edge_mask & TOP) { + top_adjust = delta.y(); + if (m_topBottomLinked) { + bottom_adjust = -top_adjust; + } + } else if (edge_mask & BOTTOM) { + bottom_adjust = delta.y(); + if (m_topBottomLinked) { + top_adjust = -bottom_adjust; + } + } - QRectF widget_rect(m_beforeResizing.middleWidgetRect); - widget_rect.adjust(-left_adjust, -top_adjust, -right_adjust, -bottom_adjust); + QRectF widget_rect(m_beforeResizing.middleWidgetRect); + widget_rect.adjust(-left_adjust, -top_adjust, -right_adjust, -bottom_adjust); - m_middleRect = m_beforeResizing.widgetToVirt.mapRect(widget_rect); - forceNonNegativeHardMargins(m_middleRect); - widget_rect = m_beforeResizing.virtToWidget.mapRect(m_middleRect); + m_middleRect = m_beforeResizing.widgetToVirt.mapRect(widget_rect); + forceNonNegativeHardMargins(m_middleRect); + widget_rect = m_beforeResizing.virtToWidget.mapRect(m_middleRect); - qreal effective_dx = 0; - qreal effective_dy = 0; + qreal effective_dx = 0; + qreal effective_dy = 0; - const QRectF& old_widget_rect = m_beforeResizing.middleWidgetRect; - if (edge_mask & LEFT) { - effective_dx = old_widget_rect.left() - widget_rect.left(); - } else if (edge_mask & RIGHT) { - effective_dx = old_widget_rect.right() - widget_rect.right(); - } - if (edge_mask & TOP) { - effective_dy = old_widget_rect.top() - widget_rect.top(); - } else if (edge_mask & BOTTOM) { - effective_dy = old_widget_rect.bottom() - widget_rect.bottom(); - } + const QRectF& old_widget_rect = m_beforeResizing.middleWidgetRect; + if (edge_mask & LEFT) { + effective_dx = old_widget_rect.left() - widget_rect.left(); + } else if (edge_mask & RIGHT) { + effective_dx = old_widget_rect.right() - widget_rect.right(); + } + if (edge_mask & TOP) { + effective_dy = old_widget_rect.top() - widget_rect.top(); + } else if (edge_mask & BOTTOM) { + effective_dy = old_widget_rect.bottom() - widget_rect.bottom(); + } - // Updating the focal point is what makes the image move - // as we drag an inner edge. - QPointF fp(m_beforeResizing.focalPoint); - fp += QPointF(effective_dx, effective_dy); - setWidgetFocalPoint(fp); + // Updating the focal point is what makes the image move + // as we drag an inner edge. + QPointF fp(m_beforeResizing.focalPoint); + fp += QPointF(effective_dx, effective_dy); + setWidgetFocalPoint(fp); - m_aggregateHardSizeMM - = m_ptrSettings->getAggregateHardSizeMM(m_pageId, origRectToSizeMM(m_middleRect), m_alignment); + m_aggregateHardSizeMM = m_settings->getAggregateHardSizeMM(m_pageId, origRectToSizeMM(m_middleRect), m_alignment); - recalcOuterRect(); + recalcOuterRect(); - updatePresentationTransform(DONT_FIT); + updatePresentationTransform(DONT_FIT); - emit marginsSetLocally(calcHardMarginsMM()); + emit marginsSetLocally(calcHardMarginsMM()); } // ImageView::innerRectDragContinuation void ImageView::middleRectDragContinuation(const int edge_mask, const QPointF& mouse_pos) { - const QPointF delta(mouse_pos - m_beforeResizing.mousePos); - qreal left_adjust = 0; - qreal right_adjust = 0; - qreal top_adjust = 0; - qreal bottom_adjust = 0; - - const QRectF bounds(maxViewportRect()); - const QRectF old_middle_rect(m_beforeResizing.middleWidgetRect); - - if (edge_mask & LEFT) { - left_adjust = delta.x(); - if (old_middle_rect.left() + left_adjust < bounds.left()) { - left_adjust = bounds.left() - old_middle_rect.left(); - } - if (m_leftRightLinked) { - right_adjust = -left_adjust; - } - } else if (edge_mask & RIGHT) { - right_adjust = delta.x(); - if (old_middle_rect.right() + right_adjust > bounds.right()) { - right_adjust = bounds.right() - old_middle_rect.right(); - } - if (m_leftRightLinked) { - left_adjust = -right_adjust; - } + const QPointF delta(mouse_pos - m_beforeResizing.mousePos); + qreal left_adjust = 0; + qreal right_adjust = 0; + qreal top_adjust = 0; + qreal bottom_adjust = 0; + + const QRectF bounds(maxViewportRect()); + const QRectF old_middle_rect(m_beforeResizing.middleWidgetRect); + + if (edge_mask & LEFT) { + left_adjust = delta.x(); + if (old_middle_rect.left() + left_adjust < bounds.left()) { + left_adjust = bounds.left() - old_middle_rect.left(); } - if (edge_mask & TOP) { - top_adjust = delta.y(); - if (old_middle_rect.top() + top_adjust < bounds.top()) { - top_adjust = bounds.top() - old_middle_rect.top(); - } - if (m_topBottomLinked) { - bottom_adjust = -top_adjust; - } - } else if (edge_mask & BOTTOM) { - bottom_adjust = delta.y(); - if (old_middle_rect.bottom() + bottom_adjust > bounds.bottom()) { - bottom_adjust = bounds.bottom() - old_middle_rect.bottom(); - } - if (m_topBottomLinked) { - top_adjust = -bottom_adjust; - } + if (m_leftRightLinked) { + right_adjust = -left_adjust; + } + } else if (edge_mask & RIGHT) { + right_adjust = delta.x(); + if (old_middle_rect.right() + right_adjust > bounds.right()) { + right_adjust = bounds.right() - old_middle_rect.right(); + } + if (m_leftRightLinked) { + left_adjust = -right_adjust; + } + } + if (edge_mask & TOP) { + top_adjust = delta.y(); + if (old_middle_rect.top() + top_adjust < bounds.top()) { + top_adjust = bounds.top() - old_middle_rect.top(); + } + if (m_topBottomLinked) { + bottom_adjust = -top_adjust; + } + } else if (edge_mask & BOTTOM) { + bottom_adjust = delta.y(); + if (old_middle_rect.bottom() + bottom_adjust > bounds.bottom()) { + bottom_adjust = bounds.bottom() - old_middle_rect.bottom(); + } + if (m_topBottomLinked) { + top_adjust = -bottom_adjust; } + } - { - QRectF widget_rect(old_middle_rect); - widget_rect.adjust(left_adjust, top_adjust, right_adjust, bottom_adjust); + { + QRectF widget_rect(old_middle_rect); + widget_rect.adjust(left_adjust, top_adjust, right_adjust, bottom_adjust); - m_middleRect = m_beforeResizing.widgetToVirt.mapRect(widget_rect); - forceNonNegativeHardMargins(m_middleRect); // invalidates widget_rect - } + m_middleRect = m_beforeResizing.widgetToVirt.mapRect(widget_rect); + forceNonNegativeHardMargins(m_middleRect); // invalidates widget_rect + } - m_aggregateHardSizeMM - = m_ptrSettings->getAggregateHardSizeMM(m_pageId, origRectToSizeMM(m_middleRect), m_alignment); + m_aggregateHardSizeMM = m_settings->getAggregateHardSizeMM(m_pageId, origRectToSizeMM(m_middleRect), m_alignment); - recalcOuterRect(); + recalcOuterRect(); - updatePresentationTransform(DONT_FIT); + updatePresentationTransform(DONT_FIT); - emit marginsSetLocally(calcHardMarginsMM()); + emit marginsSetLocally(calcHardMarginsMM()); } // ImageView::middleRectDragContinuation void ImageView::dragFinished() { - const AggregateSizeChanged agg_size_changed(commitHardMargins(calcHardMarginsMM())); + const AggregateSizeChanged agg_size_changed(commitHardMargins(calcHardMarginsMM())); - const QRectF extended_viewport(maxViewportRect().adjusted(-0.5, -0.5, 0.5, 0.5)); - if (extended_viewport.contains(m_beforeResizing.middleWidgetRect)) { - updatePresentationTransform(FIT); - } else { - updatePresentationTransform(DONT_FIT); - } + const QRectF extended_viewport(maxViewportRect().adjusted(-0.5, -0.5, 0.5, 0.5)); + if (extended_viewport.contains(m_beforeResizing.middleWidgetRect)) { + updatePresentationTransform(FIT); + } else { + updatePresentationTransform(DONT_FIT); + } - invalidateThumbnails(agg_size_changed); + invalidateThumbnails(agg_size_changed); } /** @@ -461,30 +517,33 @@ void ImageView::dragFinished() { * m_aggregateHardSizeMM and m_alignment, updates the displayed area. */ void ImageView::recalcBoxesAndFit(const Margins& margins_mm) { - const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); - const QTransform mm_to_virt(m_mmToPixelsXform * imageToVirtual()); + const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); + const QTransform mm_to_virt(m_mmToPixelsXform * imageToVirtual()); - QPolygonF poly_mm(virt_to_mm.map(m_innerRect)); - Utils::extendPolyRectWithMargins(poly_mm, margins_mm); + QPolygonF poly_mm(virt_to_mm.map(m_innerRect)); + Utils::extendPolyRectWithMargins(poly_mm, margins_mm); - const QRectF middle_rect(mm_to_virt.map(poly_mm).boundingRect()); + const QRectF middle_rect(mm_to_virt.map(poly_mm).boundingRect()); - const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); - const Margins soft_margins_mm(Utils::calcSoftMarginsMM( - hard_size_mm, m_aggregateHardSizeMM, m_alignment, m_ptrSettings->getPageParams(m_pageId)->contentRect(), - m_ptrSettings->getPageParams(m_pageId)->contentSizeMM(), m_ptrSettings->getAggregateContentRect(), - m_ptrSettings->getPageParams(m_pageId)->pageRect())); + const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); + const Margins soft_margins_mm(Utils::calcSoftMarginsMM( + hard_size_mm, m_aggregateHardSizeMM, m_alignment, m_settings->getPageParams(m_pageId)->contentRect(), + m_settings->getPageParams(m_pageId)->contentSizeMM(), m_settings->getAggregateContentRect(), + m_settings->getPageParams(m_pageId)->pageRect())); - Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); + Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); - const QRectF outer_rect(mm_to_virt.map(poly_mm).boundingRect()); - updateTransformAndFixFocalPoint( - ImagePresentation(imageToVirtual(), m_xform.resultingPreCropArea().intersected(outer_rect), outer_rect), - CENTER_IF_FITS); + const QRectF outer_rect(mm_to_virt.map(poly_mm).boundingRect()); + updateTransformAndFixFocalPoint( + ImagePresentation(imageToVirtual(), m_xform.resultingPreCropArea().intersected(outer_rect), outer_rect), + CENTER_IF_FITS); + + m_middleRect = middle_rect; + m_outerRect = outer_rect; - m_middleRect = middle_rect; - m_outerRect = outer_rect; - updatePhysSize(); + updatePhysSize(); + + forceInscribeGuides(); } /** @@ -494,57 +553,56 @@ void ImageView::recalcBoxesAndFit(const Margins& margins_mm) { * \note virtualToImage() and imageToVirtual() are not affected by this. */ void ImageView::updatePresentationTransform(const FitMode fit_mode) { - if (fit_mode == DONT_FIT) { - updateTransformPreservingScale(ImagePresentation( - imageToVirtual(), m_xform.resultingPreCropArea().intersected(m_outerRect), m_outerRect)); - } else { - setZoomLevel(1.0); - updateTransformAndFixFocalPoint( - ImagePresentation(imageToVirtual(), m_xform.resultingPreCropArea().intersected(m_outerRect), - m_outerRect), - CENTER_IF_FITS); - } + if (fit_mode == DONT_FIT) { + updateTransformPreservingScale( + ImagePresentation(imageToVirtual(), m_xform.resultingPreCropArea().intersected(m_outerRect), m_outerRect)); + } else { + setZoomLevel(1.0); + updateTransformAndFixFocalPoint( + ImagePresentation(imageToVirtual(), m_xform.resultingPreCropArea().intersected(m_outerRect), m_outerRect), + CENTER_IF_FITS); + } } void ImageView::forceNonNegativeHardMargins(QRectF& middle_rect) const { - if (middle_rect.left() > m_innerRect.left()) { - middle_rect.setLeft(m_innerRect.left()); - } - if (middle_rect.right() < m_innerRect.right()) { - middle_rect.setRight(m_innerRect.right()); - } - if (middle_rect.top() > m_innerRect.top()) { - middle_rect.setTop(m_innerRect.top()); - } - if (middle_rect.bottom() < m_innerRect.bottom()) { - middle_rect.setBottom(m_innerRect.bottom()); - } + if (middle_rect.left() > m_innerRect.left()) { + middle_rect.setLeft(m_innerRect.left()); + } + if (middle_rect.right() < m_innerRect.right()) { + middle_rect.setRight(m_innerRect.right()); + } + if (middle_rect.top() > m_innerRect.top()) { + middle_rect.setTop(m_innerRect.top()); + } + if (middle_rect.bottom() < m_innerRect.bottom()) { + middle_rect.setBottom(m_innerRect.bottom()); + } } /** * \brief Calculates margins in millimeters between m_innerRect and m_middleRect. */ Margins ImageView::calcHardMarginsMM() const { - const QPointF center(m_innerRect.center()); + const QPointF center(m_innerRect.center()); - const QLineF top_margin_line(QPointF(center.x(), m_middleRect.top()), QPointF(center.x(), m_innerRect.top())); + const QLineF top_margin_line(QPointF(center.x(), m_middleRect.top()), QPointF(center.x(), m_innerRect.top())); - const QLineF bottom_margin_line(QPointF(center.x(), m_innerRect.bottom()), - QPointF(center.x(), m_middleRect.bottom())); + const QLineF bottom_margin_line(QPointF(center.x(), m_innerRect.bottom()), + QPointF(center.x(), m_middleRect.bottom())); - const QLineF left_margin_line(QPointF(m_middleRect.left(), center.y()), QPointF(m_innerRect.left(), center.y())); + const QLineF left_margin_line(QPointF(m_middleRect.left(), center.y()), QPointF(m_innerRect.left(), center.y())); - const QLineF right_margin_line(QPointF(m_innerRect.right(), center.y()), QPointF(m_middleRect.right(), center.y())); + const QLineF right_margin_line(QPointF(m_innerRect.right(), center.y()), QPointF(m_middleRect.right(), center.y())); - const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); + const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); - Margins margins; - margins.setTop(virt_to_mm.map(top_margin_line).length()); - margins.setBottom(virt_to_mm.map(bottom_margin_line).length()); - margins.setLeft(virt_to_mm.map(left_margin_line).length()); - margins.setRight(virt_to_mm.map(right_margin_line).length()); + Margins margins; + margins.setTop(virt_to_mm.map(top_margin_line).length()); + margins.setBottom(virt_to_mm.map(bottom_margin_line).length()); + margins.setLeft(virt_to_mm.map(left_margin_line).length()); + margins.setRight(virt_to_mm.map(right_margin_line).length()); - return margins; + return margins; } // ImageView::calcHardMarginsMM /** @@ -552,61 +610,556 @@ Margins ImageView::calcHardMarginsMM() const { * and m_alignment. */ void ImageView::recalcOuterRect() { - const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); - const QTransform mm_to_virt(m_mmToPixelsXform * imageToVirtual()); + const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); + const QTransform mm_to_virt(m_mmToPixelsXform * imageToVirtual()); + + QPolygonF poly_mm(virt_to_mm.map(m_middleRect)); - QPolygonF poly_mm(virt_to_mm.map(m_middleRect)); + const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); + const Margins soft_margins_mm(Utils::calcSoftMarginsMM( + hard_size_mm, m_aggregateHardSizeMM, m_alignment, m_settings->getPageParams(m_pageId)->contentRect(), + m_settings->getPageParams(m_pageId)->contentSizeMM(), m_settings->getAggregateContentRect(), + m_settings->getPageParams(m_pageId)->pageRect())); - const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); - const Margins soft_margins_mm(Utils::calcSoftMarginsMM( - hard_size_mm, m_aggregateHardSizeMM, m_alignment, m_ptrSettings->getPageParams(m_pageId)->contentRect(), - m_ptrSettings->getPageParams(m_pageId)->contentSizeMM(), m_ptrSettings->getAggregateContentRect(), - m_ptrSettings->getPageParams(m_pageId)->pageRect())); + Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); - Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); + m_outerRect = mm_to_virt.map(poly_mm).boundingRect(); + updatePhysSize(); - m_outerRect = mm_to_virt.map(poly_mm).boundingRect(); - updatePhysSize(); + forceInscribeGuides(); } QSizeF ImageView::origRectToSizeMM(const QRectF& rect) const { - const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); + const QTransform virt_to_mm(virtualToImage() * m_pixelsToMmXform); - const QLineF hor_line(rect.topLeft(), rect.topRight()); - const QLineF vert_line(rect.topLeft(), rect.bottomLeft()); + const QLineF hor_line(rect.topLeft(), rect.topRight()); + const QLineF vert_line(rect.topLeft(), rect.bottomLeft()); - const QSizeF size_mm(virt_to_mm.map(hor_line).length(), virt_to_mm.map(vert_line).length()); + const QSizeF size_mm(virt_to_mm.map(hor_line).length(), virt_to_mm.map(vert_line).length()); - return size_mm; + return size_mm; } ImageView::AggregateSizeChanged ImageView::commitHardMargins(const Margins& margins_mm) { - m_ptrSettings->setHardMarginsMM(m_pageId, margins_mm); - m_aggregateHardSizeMM = m_ptrSettings->getAggregateHardSizeMM(); + m_settings->setHardMarginsMM(m_pageId, margins_mm); + m_aggregateHardSizeMM = m_settings->getAggregateHardSizeMM(); - AggregateSizeChanged changed = AGGREGATE_SIZE_UNCHANGED; - if (m_committedAggregateHardSizeMM != m_aggregateHardSizeMM) { - changed = AGGREGATE_SIZE_CHANGED; - } + AggregateSizeChanged changed = AGGREGATE_SIZE_UNCHANGED; + if (m_committedAggregateHardSizeMM != m_aggregateHardSizeMM) { + changed = AGGREGATE_SIZE_CHANGED; + } - m_committedAggregateHardSizeMM = m_aggregateHardSizeMM; + m_committedAggregateHardSizeMM = m_aggregateHardSizeMM; - return changed; + return changed; } void ImageView::invalidateThumbnails(const AggregateSizeChanged agg_size_changed) { - if (agg_size_changed == AGGREGATE_SIZE_CHANGED) { - emit invalidateAllThumbnails(); - } else { - emit invalidateThumbnail(m_pageId); - } + if (agg_size_changed == AGGREGATE_SIZE_CHANGED) { + emit invalidateAllThumbnails(); + } else { + emit invalidateThumbnail(m_pageId); + } } void ImageView::updatePhysSize() { - if (m_outerRect.isValid()) { - infoProvider().setPhysSize(m_outerRect.size()); + if (m_outerRect.isValid()) { + infoProvider().setPhysSize(m_outerRect.size()); + } else { + ImageViewBase::updatePhysSize(); + } +} + +void ImageView::setupContextMenuInteraction() { + m_addHorizontalGuideAction = m_contextMenu->addAction(tr("Add a horizontal guide")); + m_addVerticalGuideAction = m_contextMenu->addAction(tr("Add a vertical guide")); + m_removeAllGuidesAction = m_contextMenu->addAction(tr("Remove all the guides")); + m_removeGuideUnderMouseAction = m_contextMenu->addAction(tr("Remove this guide")); + m_guideActionsSeparator = m_contextMenu->addSeparator(); + m_showMiddleRectAction = m_contextMenu->addAction(tr("Show hard margins rectangle")); + m_showMiddleRectAction->setCheckable(true); + m_showMiddleRectAction->setChecked(m_settings->isShowingMiddleRectEnabled()); + + connect(m_addHorizontalGuideAction, &QAction::triggered, + [this]() { addHorizontalGuide(widgetToGuideCs().map(m_lastContextMenuPos).y()); }); + connect(m_addVerticalGuideAction, &QAction::triggered, + [this]() { addVerticalGuide(widgetToGuideCs().map(m_lastContextMenuPos).x()); }); + connect(m_removeAllGuidesAction, &QAction::triggered, boost::bind(&ImageView::removeAllGuides, this)); + connect(m_removeGuideUnderMouseAction, &QAction::triggered, [this]() { removeGuide(m_guideUnderMouse); }); + connect(m_showMiddleRectAction, &QAction::toggled, [this](bool checked) { + if (!m_alignment.isNull() && !m_nullContentRect) { + enableMiddleRectInteraction(checked); + m_settings->enableShowingMiddleRect(checked); + } + }); +} + +void ImageView::onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { + if (interaction.captured()) { + // No context menus during resizing. + return; + } + if (m_alignment.isNull()) { + return; + } + + const QPointF eventPos = QPointF(0.5, 0.5) + event->pos(); + // No context menus outside the outer rect. + if (!m_outerRect.contains(widgetToVirtual().map(eventPos))) { + return; + } + + m_guideUnderMouse = getGuideUnderMouse(eventPos, interaction); + if (m_guideUnderMouse == -1) { + m_addHorizontalGuideAction->setVisible(true); + m_addVerticalGuideAction->setVisible(true); + m_removeAllGuidesAction->setVisible(!m_guides.empty()); + m_removeGuideUnderMouseAction->setVisible(false); + m_guideActionsSeparator->setVisible(!m_nullContentRect); + m_showMiddleRectAction->setVisible(!m_nullContentRect); + + m_lastContextMenuPos = eventPos; + } else { + m_addHorizontalGuideAction->setVisible(false); + m_addVerticalGuideAction->setVisible(false); + m_removeAllGuidesAction->setVisible(false); + m_removeGuideUnderMouseAction->setVisible(true); + m_guideActionsSeparator->setVisible(false); + m_showMiddleRectAction->setVisible(false); + } + + m_contextMenu->popup(event->globalPos()); +} + +void ImageView::setupGuides() { + for (const Guide& guide : m_settings->guides()) { + m_guides[m_guidesFreeIndex] = guide; + setupGuideInteraction(m_guidesFreeIndex++); + } +} + +void ImageView::addHorizontalGuide(double y) { + if (m_alignment.isNull()) { + return; + } + + m_guides[m_guidesFreeIndex] = Guide(Qt::Horizontal, y); + setupGuideInteraction(m_guidesFreeIndex++); + + update(); + syncGuidesSettings(); +} + +void ImageView::addVerticalGuide(double x) { + if (m_alignment.isNull()) { + return; + } + + m_guides[m_guidesFreeIndex] = Guide(Qt::Vertical, x); + setupGuideInteraction(m_guidesFreeIndex++); + + update(); + syncGuidesSettings(); +} + +void ImageView::removeAllGuides() { + if (m_alignment.isNull()) { + return; + } + + m_draggableGuideHandlers.clear(); + m_draggableGuides.clear(); + + m_guides.clear(); + m_guidesFreeIndex = 0; + + update(); + syncGuidesSettings(); +} + +void ImageView::removeGuide(const int index) { + if (m_alignment.isNull()) { + return; + } + if (m_guides.find(index) == m_guides.end()) { + return; + } + + m_draggableGuideHandlers.erase(index); + m_draggableGuides.erase(index); + + m_guides.erase(index); + + update(); + syncGuidesSettings(); +} + +QTransform ImageView::widgetToGuideCs() const { + QTransform xform(widgetToVirtual()); + xform *= QTransform().translate(-m_outerRect.center().x(), -m_outerRect.center().y()); + + return xform; +} + +QTransform ImageView::guideToWidgetCs() const { + return widgetToGuideCs().inverted(); +} + +void ImageView::syncGuidesSettings() { + m_settings->guides().clear(); + for (const auto& idxAndGuide : m_guides) { + m_settings->guides().push_back(idxAndGuide.second); + } +} + +void ImageView::setupGuideInteraction(const int index) { + m_draggableGuides[index].setProximityPriority(1); + m_draggableGuides[index].setPositionCallback(boost::bind(&ImageView::guidePosition, this, index)); + m_draggableGuides[index].setMoveRequestCallback(boost::bind(&ImageView::guideMoveRequest, this, index, _1)); + m_draggableGuides[index].setDragFinishedCallback(boost::bind(&ImageView::guideDragFinished, this)); + + const Qt::CursorShape cursorShape + = (m_guides[index].getOrientation() == Qt::Horizontal) ? Qt::SplitVCursor : Qt::SplitHCursor; + m_draggableGuideHandlers[index].setObject(&m_draggableGuides[index]); + m_draggableGuideHandlers[index].setProximityCursor(cursorShape); + m_draggableGuideHandlers[index].setInteractionCursor(cursorShape); + m_draggableGuideHandlers[index].setProximityStatusTip(tr("Drag the guide.")); + m_draggableGuideHandlers[index].setKeyboardModifiers({Qt::ShiftModifier}); + + if (!m_alignment.isNull()) { + makeLastFollower(m_draggableGuideHandlers[index]); + } +} + +QLineF ImageView::guidePosition(const int index) const { + return widgetGuideLine(index); +} + +void ImageView::guideMoveRequest(const int index, QLineF line) { + const QRectF valid_area(virtualToWidget().mapRect(m_outerRect)); + + // Limit movement. + if (m_guides[index].getOrientation() == Qt::Horizontal) { + const double linePos = line.y1(); + const double top = valid_area.top() - linePos; + const double bottom = linePos - valid_area.bottom(); + if (top > 0.0) { + line.translate(0.0, top); + } else if (bottom > 0.0) { + line.translate(0.0, -bottom); + } + } else { + const double linePos = line.x1(); + const double left = valid_area.left() - linePos; + const double right = linePos - valid_area.right(); + if (left > 0.0) { + line.translate(left, 0.0); + } else if (right > 0.0) { + line.translate(-right, 0.0); + } + } + + m_guides[index] = widgetToGuideCs().map(line); + update(); +} + +void ImageView::guideDragFinished() { + syncGuidesSettings(); +} + +QLineF ImageView::widgetGuideLine(const int index) const { + const QRectF widget_rect(viewport()->rect()); + const Guide& guide = m_guides.at(index); + QLineF guideLine = guideToWidgetCs().map(guide); + if (guide.getOrientation() == Qt::Horizontal) { + guideLine = QLineF(widget_rect.left(), guideLine.y1(), widget_rect.right(), guideLine.y2()); + } else { + guideLine = QLineF(guideLine.x1(), widget_rect.top(), guideLine.x2(), widget_rect.bottom()); + } + + return guideLine; +} + +int ImageView::getGuideUnderMouse(const QPointF& screenMousePos, const InteractionState& state) const { + for (const auto& idxAndGuide : m_guides) { + const QLineF guide(widgetGuideLine(idxAndGuide.first)); + if (Proximity::pointAndLineSegment(screenMousePos, guide) <= state.proximityThreshold()) { + return idxAndGuide.first; + } + } + + return -1; +} + +void ImageView::enableGuidesInteraction(const bool state) { + if (state) { + for (auto& idxAndGuideHandler : m_draggableGuideHandlers) { + makeLastFollower(idxAndGuideHandler.second); + } + } else { + for (auto& idxAndGuideHandler : m_draggableGuideHandlers) { + idxAndGuideHandler.second.unlink(); + } + } +} + +void ImageView::forceInscribeGuides() { + if (m_alignment.isNull()) { + return; + } + + bool need_sync = false; + for (const auto& idxAndGuide : m_guides) { + const Guide& guide = idxAndGuide.second; + const double pos = guide.getPosition(); + if (guide.getOrientation() == Qt::Vertical) { + if (std::abs(pos) > (m_outerRect.width() / 2)) { + m_guides[idxAndGuide.first].setPosition(std::copysign(m_outerRect.width() / 2, pos)); + need_sync = true; + } } else { - ImageViewBase::updatePhysSize(); + if (std::abs(pos) > (m_outerRect.height() / 2)) { + m_guides[idxAndGuide.first].setPosition(std::copysign(m_outerRect.height() / 2, pos)); + need_sync = true; + } } + } + + if (need_sync) { + syncGuidesSettings(); + } +} + +void ImageView::innerRectMoveRequest(const QPointF& mouse_pos, const Qt::KeyboardModifiers mask) { + QPointF delta(mouse_pos - m_beforeResizing.mousePos); + if (mask == m_innerRectVerticalDragModifier) { + delta.setX(0); + } else if (mask == m_innerRectHorizontalDragModifier) { + delta.setY(0); + } + + QRectF widget_rect(m_beforeResizing.middleWidgetRect); + widget_rect.translate(-delta); + m_middleRect = m_beforeResizing.widgetToVirt.mapRect(widget_rect); + forceNonNegativeHardMargins(m_middleRect); + + // Updating the focal point is what makes the image move + // as we drag an inner edge. + QPointF fp(m_beforeResizing.focalPoint); + fp += delta; + setWidgetFocalPoint(fp); + + m_aggregateHardSizeMM = m_settings->getAggregateHardSizeMM(m_pageId, origRectToSizeMM(m_middleRect), m_alignment); + + recalcOuterRect(); + + updatePresentationTransform(DONT_FIT); + + emit marginsSetLocally(calcHardMarginsMM()); +} + +Proximity ImageView::rectProximity(const QRectF& box, const QPointF& mouse_pos) const { + double value = virtualToWidget().mapRect(box).contains(mouse_pos) ? 0 : std::numeric_limits::max(); + return Proximity::fromSqDist(value); +} + +void ImageView::buildContentImage(const GrayImage& gray_image, const ImageTransformation& xform) { + ImageTransformation xform_150dpi(xform); + xform_150dpi.preScaleToDpi(Dpi(150, 150)); + + if (xform_150dpi.resultingRect().toRect().isEmpty()) { + return; + } + + QImage gray150(transformToGray(gray_image, xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), + OutsidePixels::assumeColor(Qt::white))); + + m_contentImage = binarizeWolf(gray150, QSize(51, 51), 50); + + PolygonRasterizer::fillExcept(m_contentImage, WHITE, xform_150dpi.resultingPreCropArea(), Qt::WindingFill); + + Despeckle::despeckleInPlace(m_contentImage, Dpi(150, 150), Despeckle::NORMAL, EmptyTaskStatus()); + + m_originalToContentImage = xform_150dpi.transform(); + m_contentImageToOriginal = m_originalToContentImage.inverted(); +} + +QRect ImageView::findContentInArea(const QRect& area) const { + const uint32_t* image_line = m_contentImage.data(); + const int image_stride = m_contentImage.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + int top = std::numeric_limits::max(); + int left = std::numeric_limits::max(); + int bottom = std::numeric_limits::min(); + int right = std::numeric_limits::min(); + + image_line += area.top() * image_stride; + for (int y = area.top(); y <= area.bottom(); ++y) { + for (int x = area.left(); x <= area.right(); ++x) { + if (image_line[x >> 5] & (msb >> (x & 31))) { + top = std::min(top, y); + left = std::min(left, x); + bottom = std::max(bottom, y); + right = std::max(right, x); + } + } + image_line += image_stride; + } + + if (top > bottom) { + return QRect(); + } + + QRect found_area = QRect(left, top, right - left + 1, bottom - top + 1); + found_area.adjust(-1, -1, 1, 1); + + return found_area; +} + +void ImageView::onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) { + if (event->button() == Qt::LeftButton) { + if (!m_alignment.isNull() && !m_guides.empty()) { + attachContentToNearestGuide(QPointF(0.5, 0.5) + event->pos(), event->modifiers()); + } + } +} + +void ImageView::attachContentToNearestGuide(const QPointF& pos, const Qt::KeyboardModifiers mask) { + const QTransform widget_to_content_image(widgetToImage() * m_originalToContentImage); + const QTransform content_image_to_virtual(m_contentImageToOriginal * imageToVirtual()); + + const QPointF content_pos = widget_to_content_image.map(pos); + + QRect finding_area((content_pos - QPointF(15, 15)).toPoint(), QSize(30, 30)); + finding_area = finding_area.intersected(m_contentImage.rect()); + if (finding_area.isEmpty()) { + return; + } + + QRect found_area = findContentInArea(finding_area); + if (found_area.isEmpty()) { + return; + } + + const QRectF found_area_in_virtual = content_image_to_virtual.mapRect(QRectF(found_area)).intersected(m_innerRect); + if (found_area_in_virtual.isEmpty()) { + return; + } + + QPointF delta; + { + const bool only_horizontal_direction = (mask == m_innerRectHorizontalDragModifier); + const bool only_vertical_direction = (mask == m_innerRectVerticalDragModifier); + const bool both_directions = (mask == (m_innerRectVerticalDragModifier | m_innerRectHorizontalDragModifier)); + + double min_dist_x = std::numeric_limits::max(); + double min_dist_y = std::numeric_limits::max(); + for (const auto& idxAndGuide : m_guides) { + const Guide& guide = idxAndGuide.second; + if (guide.getOrientation() == Qt::Vertical) { + if (only_vertical_direction) { + continue; + } + + const double guide_pos_in_virtual = guide.getPosition() + m_outerRect.center().x(); + const double diff_left = guide_pos_in_virtual - found_area_in_virtual.left(); + const double diff_right = guide_pos_in_virtual - found_area_in_virtual.right(); + const double diff = std::abs(diff_left) <= std::abs(diff_right) ? diff_left : diff_right; + const double dist = std::abs(diff); + const double min_dist = (both_directions) ? min_dist_x : std::min(min_dist_x, min_dist_y); + if (dist < min_dist) { + min_dist_x = dist; + delta.setX(diff); + if (!both_directions) { + delta.setY(0.0); + } + } + } else { + if (only_horizontal_direction) { + continue; + } + + const double guide_pos_in_virtual = guide.getPosition() + m_outerRect.center().y(); + const double diff_top = guide_pos_in_virtual - found_area_in_virtual.top(); + const double diff_bottom = guide_pos_in_virtual - found_area_in_virtual.bottom(); + const double diff = std::abs(diff_top) <= std::abs(diff_bottom) ? diff_top : diff_bottom; + const double dist = std::abs(diff); + const double min_dist = (both_directions) ? min_dist_y : std::min(min_dist_x, min_dist_y); + if (dist < min_dist) { + min_dist_y = dist; + delta.setY(diff); + if (!both_directions) { + delta.setX(0.0); + } + } + } + } + } + if (delta.isNull()) { + return; + } + + { + QRectF corrected_middle_rect = m_middleRect; + corrected_middle_rect.translate(-delta); + corrected_middle_rect |= m_innerRect; + + { + // Correct the delta in case of the middle rect size changed. + // It means that the center is shifted resulting + // the guide will change its position, so we need an extra addition to delta. + const double x_correction + = std::copysign(std::max(0.0, corrected_middle_rect.width() - m_middleRect.width()), delta.x()); + const double y_correction + = std::copysign(std::max(0.0, corrected_middle_rect.height() - m_middleRect.height()), delta.y()); + delta.setX(delta.x() + x_correction); + delta.setY(delta.y() + y_correction); + + corrected_middle_rect.translate(-x_correction, -y_correction); + corrected_middle_rect |= m_innerRect; + } + + { + // Restrict the delta value in order not to be out of the outer rect. + const double x_correction + = std::copysign(std::max(0.0, corrected_middle_rect.width() - m_outerRect.width()), delta.x()); + const double y_correction + = std::copysign(std::max(0.0, corrected_middle_rect.height() - m_outerRect.height()), delta.y()); + delta.setX(delta.x() - x_correction); + delta.setY(delta.y() - y_correction); + } + } + + // Move the page content. + dragInitiated(virtualToWidget().map(QPointF(0, 0))); + innerRectMoveRequest(virtualToWidget().map(delta)); + dragFinished(); +} + +void ImageView::enableMiddleRectInteraction(const bool state) { + bool internal_state = m_middleCornerHandlers[0].is_linked(); + if (state == internal_state) { + // Don't enable or disable the interaction if that's already done. + return; + } + + if (state) { + for (int i = 0; i < 4; ++i) { + makeLastFollower(m_middleCornerHandlers[i]); + makeLastFollower(m_middleEdgeHandlers[i]); + } + } else { + for (int i = 0; i < 4; ++i) { + m_middleCornerHandlers[i].unlink(); + m_middleEdgeHandlers[i].unlink(); + } + } + + update(); +} + +bool ImageView::isShowingMiddleRectEnabled() const { + return (!m_nullContentRect && m_settings->isShowingMiddleRectEnabled()) || m_alignment.isNull(); } } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/ImageView.h b/filters/page_layout/ImageView.h index 1b6c68728..bdebce58c 100644 --- a/filters/page_layout/ImageView.h +++ b/filters/page_layout/ImageView.h @@ -19,210 +19,301 @@ #ifndef PAGE_LAYOUT_IMAGEVIEW_H_ #define PAGE_LAYOUT_IMAGEVIEW_H_ -#include "ImageViewBase.h" -#include "ImageTransformation.h" -#include "InteractionHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Alignment.h" #include "DragHandler.h" -#include "ZoomHandler.h" #include "DraggableObject.h" +#include "Guide.h" +#include "ImageTransformation.h" +#include "ImageViewBase.h" +#include "InteractionHandler.h" #include "ObjectDragHandler.h" -#include "Alignment.h" -#include "intrusive_ptr.h" #include "PageId.h" -#include -#include -#include -#include -#include +#include "ZoomHandler.h" +#include "intrusive_ptr.h" class Margins; +namespace imageproc { +class GrayImage; +} + namespace page_layout { class OptionsWidget; class Settings; class ImageView : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - ImageView(const intrusive_ptr& settings, - const PageId& page_id, - const QImage& image, - const QImage& downscaled_image, - const ImageTransformation& xform, - const QRectF& adapted_content_rect, - const OptionsWidget& opt_widget); + Q_OBJECT + public: + ImageView(const intrusive_ptr& settings, + const PageId& page_id, + const QImage& image, + const QImage& downscaled_image, + const imageproc::GrayImage& gray_image, + const ImageTransformation& xform, + const QRectF& adapted_content_rect, + const OptionsWidget& opt_widget); - ~ImageView() override; + ~ImageView() override; -signals: + signals: - void invalidateThumbnail(const PageId& page_id); + void invalidateThumbnail(const PageId& page_id); - void invalidateAllThumbnails(); + void invalidateAllThumbnails(); - void marginsSetLocally(const Margins& margins_mm); + void marginsSetLocally(const Margins& margins_mm); -public slots: + public slots: - void marginsSetExternally(const Margins& margins_mm); + void marginsSetExternally(const Margins& margins_mm); - void leftRightLinkToggled(bool linked); + void leftRightLinkToggled(bool linked); - void topBottomLinkToggled(bool linked); + void topBottomLinkToggled(bool linked); - void alignmentChanged(const Alignment& alignment); + void alignmentChanged(const Alignment& alignment); - void aggregateHardSizeChanged(); + void aggregateHardSizeChanged(); -protected: - void updatePhysSize() override; + protected: + void updatePhysSize() override; -private: - enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; + private: + enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; - enum FitMode { FIT, DONT_FIT }; + enum FitMode { FIT, DONT_FIT }; - enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; + enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; - struct StateBeforeResizing { - /** - * Transformation from virtual image coordinates to widget coordinates. - */ - QTransform virtToWidget; + struct StateBeforeResizing { + /** + * Transformation from virtual image coordinates to widget coordinates. + */ + QTransform virtToWidget; - /** - * Transformation from widget coordinates to virtual image coordinates. - */ - QTransform widgetToVirt; + /** + * Transformation from widget coordinates to virtual image coordinates. + */ + QTransform widgetToVirt; - /** - * m_middleRect in widget coordinates. - */ - QRectF middleWidgetRect; + /** + * m_middleRect in widget coordinates. + */ + QRectF middleWidgetRect; - /** - * Mouse pointer position in widget coordinates. - */ - QPointF mousePos; + /** + * Mouse pointer position in widget coordinates. + */ + QPointF mousePos; - /** - * The point in image that is to be centered on the screen, - * in pixel image coordinates. - */ - QPointF focalPoint; - }; + /** + * The point in image that is to be centered on the screen, + * in pixel image coordinates. + */ + QPointF focalPoint; + }; - void onPaint(QPainter& painter, const InteractionState& interaction) override; + void onPaint(QPainter& painter, const InteractionState& interaction) override; - Proximity cornerProximity(int edge_mask, const QRectF* box, const QPointF& mouse_pos) const; + void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) override; - Proximity edgeProximity(int edge_mask, const QRectF* box, const QPointF& mouse_pos) const; + void onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) override; - void dragInitiated(const QPointF& mouse_pos); + Proximity cornerProximity(int edge_mask, const QRectF* box, const QPointF& mouse_pos) const; - void innerRectDragContinuation(int edge_mask, const QPointF& mouse_pos); + Proximity edgeProximity(int edge_mask, const QRectF* box, const QPointF& mouse_pos) const; - void middleRectDragContinuation(int edge_mask, const QPointF& mouse_pos); + void dragInitiated(const QPointF& mouse_pos); - void dragFinished(); + void innerRectDragContinuation(int edge_mask, const QPointF& mouse_pos); - void recalcBoxesAndFit(const Margins& margins_mm); + void middleRectDragContinuation(int edge_mask, const QPointF& mouse_pos); - void updatePresentationTransform(FitMode fit_mode); + void dragFinished(); - void forceNonNegativeHardMargins(QRectF& middle_rect) const; + void recalcBoxesAndFit(const Margins& margins_mm); - Margins calcHardMarginsMM() const; + void updatePresentationTransform(FitMode fit_mode); - void recalcOuterRect(); + void forceNonNegativeHardMargins(QRectF& middle_rect) const; - QSizeF origRectToSizeMM(const QRectF& rect) const; + Margins calcHardMarginsMM() const; - AggregateSizeChanged commitHardMargins(const Margins& margins_mm); + void recalcOuterRect(); - void invalidateThumbnails(AggregateSizeChanged agg_size_changed); + QSizeF origRectToSizeMM(const QRectF& rect) const; - DraggableObject m_innerCorners[4]; - ObjectDragHandler m_innerCornerHandlers[4]; - DraggableObject m_innerEdges[4]; - ObjectDragHandler m_innerEdgeHandlers[4]; + AggregateSizeChanged commitHardMargins(const Margins& margins_mm); - DraggableObject m_middleCorners[4]; - ObjectDragHandler m_middleCornerHandlers[4]; - DraggableObject m_middleEdges[4]; - ObjectDragHandler m_middleEdgeHandlers[4]; + void invalidateThumbnails(AggregateSizeChanged agg_size_changed); - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + void setupContextMenuInteraction(); - intrusive_ptr m_ptrSettings; + void setupGuides(); - const PageId m_pageId; + void addHorizontalGuide(double y); - /** - * Transformation between the pixel image coordinates and millimeters, - * assuming that point (0, 0) in pixel coordinates corresponds to point - * (0, 0) in millimeter coordinates. - */ - const QTransform m_pixelsToMmXform; - const QTransform m_mmToPixelsXform; + void addVerticalGuide(double x); - const ImageTransformation m_xform; + void removeAllGuides(); - /** - * Content box in virtual image coordinates. - */ - const QRectF m_innerRect; + void removeGuide(int index); - /** - * \brief Content box + hard margins in virtual image coordinates. - * - * Hard margins are margins that will be there no matter what. - * Soft margins are those added to extend the page to match its - * size with other pages. - */ - QRectF m_middleRect; + QTransform widgetToGuideCs() const; - /** - * \brief Content box + hard + soft margins in virtual image coordinates. - * - * Hard margins are margins that will be there no matter what. - * Soft margins are those added to extend the page to match its - * size with other pages. - */ - QRectF m_outerRect; + QTransform guideToWidgetCs() const; - /** - * \brief Aggregate (max width + max height) hard page size. - * - * This one is for displaying purposes only. It changes during - * dragging, and it may differ from what - * m_ptrSettings->getAggregateHardSizeMM() would return. - * - * \see m_committedAggregateHardSizeMM - */ - QSizeF m_aggregateHardSizeMM; + void syncGuidesSettings(); - /** - * \brief Aggregate (max width + max height) hard page size. - * - * This one is supposed to be the cached version of what - * m_ptrSettings->getAggregateHardSizeMM() would return. - * - * \see m_aggregateHardSizeMM - */ - QSizeF m_committedAggregateHardSizeMM; + void setupGuideInteraction(int index); - Alignment m_alignment; + QLineF guidePosition(int index) const; - /** - * Some data saved at the beginning of a resizing operation. - */ - StateBeforeResizing m_beforeResizing; + void guideMoveRequest(int index, QLineF line); + + void guideDragFinished(); + + QLineF widgetGuideLine(int index) const; + + int getGuideUnderMouse(const QPointF& screenMousePos, const InteractionState& state) const; + + void enableGuidesInteraction(bool state); + + void forceInscribeGuides(); + + Proximity rectProximity(const QRectF& box, const QPointF& mouse_pos) const; + + void innerRectMoveRequest(const QPointF& mouse_pos, Qt::KeyboardModifiers mask = Qt::NoModifier); + + void buildContentImage(const imageproc::GrayImage& gray_image, const ImageTransformation& xform); + + void attachContentToNearestGuide(const QPointF& pos, Qt::KeyboardModifiers mask = Qt::NoModifier); + + QRect findContentInArea(const QRect& area) const; + + void enableMiddleRectInteraction(bool state); + + bool isShowingMiddleRectEnabled() const; + + + DraggableObject m_innerCorners[4]; + ObjectDragHandler m_innerCornerHandlers[4]; + DraggableObject m_innerEdges[4]; + ObjectDragHandler m_innerEdgeHandlers[4]; + + DraggableObject m_middleCorners[4]; + ObjectDragHandler m_middleCornerHandlers[4]; + DraggableObject m_middleEdges[4]; + ObjectDragHandler m_middleEdgeHandlers[4]; + + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; + + intrusive_ptr m_settings; + + const PageId m_pageId; + + /** + * Transformation between the pixel image coordinates and millimeters, + * assuming that point (0, 0) in pixel coordinates corresponds to point + * (0, 0) in millimeter coordinates. + */ + const QTransform m_pixelsToMmXform; + const QTransform m_mmToPixelsXform; + + const ImageTransformation m_xform; + + /** + * Content box in virtual image coordinates. + */ + const QRectF m_innerRect; + + /** + * \brief Content box + hard margins in virtual image coordinates. + * + * Hard margins are margins that will be there no matter what. + * Soft margins are those added to extend the page to match its + * size with other pages. + */ + QRectF m_middleRect; + + /** + * \brief Content box + hard + soft margins in virtual image coordinates. + * + * Hard margins are margins that will be there no matter what. + * Soft margins are those added to extend the page to match its + * size with other pages. + */ + QRectF m_outerRect; + + /** + * \brief Aggregate (max width + max height) hard page size. + * + * This one is for displaying purposes only. It changes during + * dragging, and it may differ from what + * m_settings->getAggregateHardSizeMM() would return. + * + * \see m_committedAggregateHardSizeMM + */ + QSizeF m_aggregateHardSizeMM; + + /** + * \brief Aggregate (max width + max height) hard page size. + * + * This one is supposed to be the cached version of what + * m_settings->getAggregateHardSizeMM() would return. + * + * \see m_aggregateHardSizeMM + */ + QSizeF m_committedAggregateHardSizeMM; + + Alignment m_alignment; + + /** + * Some data saved at the beginning of a resizing operation. + */ + StateBeforeResizing m_beforeResizing; - bool m_leftRightLinked; + bool m_leftRightLinked; + bool m_topBottomLinked; - bool m_topBottomLinked; + /** Guides settings. */ + std::unordered_map m_guides; + int m_guidesFreeIndex; + + std::unordered_map m_draggableGuides; + std::unordered_map m_draggableGuideHandlers; + + QMenu* m_contextMenu; + QAction* m_addHorizontalGuideAction; + QAction* m_addVerticalGuideAction; + QAction* m_removeAllGuidesAction; + QAction* m_removeGuideUnderMouseAction; + QAction* m_guideActionsSeparator; + QAction* m_showMiddleRectAction; + QPointF m_lastContextMenuPos; + int m_guideUnderMouse; + + DraggableObject m_innerRectArea; + ObjectDragHandler m_innerRectAreaHandler; + + Qt::KeyboardModifier m_innerRectVerticalDragModifier; + Qt::KeyboardModifier m_innerRectHorizontalDragModifier; + + imageproc::BinaryImage m_contentImage; + QTransform m_originalToContentImage; + QTransform m_contentImageToOriginal; + + const bool m_nullContentRect; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_IMAGEVIEW_H_ diff --git a/filters/page_layout/OptionsWidget.cpp b/filters/page_layout/OptionsWidget.cpp index a2ab585aa..c299ab8f3 100644 --- a/filters/page_layout/OptionsWidget.cpp +++ b/filters/page_layout/OptionsWidget.cpp @@ -17,541 +17,532 @@ */ #include "OptionsWidget.h" -#include "Settings.h" -#include "ApplyDialog.h" +#include +#include +#include #include "../../Utils.h" +#include "ApplyDialog.h" #include "ScopedIncDec.h" +#include "Settings.h" #include "imageproc/Constants.h" -#include -#include -#include using namespace imageproc::constants; namespace page_layout { OptionsWidget::OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), - m_pageSelectionAccessor(page_selection_accessor), - m_leftRightLinked(true), - m_topBottomLinked(true) { - { - QSettings app_settings; - m_leftRightLinked = app_settings.value("margins/leftRightLinked", true).toBool(); - m_topBottomLinked = app_settings.value("margins/topBottomLinked", true).toBool(); - } - - m_chainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-24.png"))); - m_brokenChainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-broken-24.png"))); - - setupUi(this); - updateLinkDisplay(topBottomLink, m_topBottomLinked); - updateLinkDisplay(leftRightLink, m_leftRightLinked); - updateAlignmentButtonsEnabled(); - - Utils::mapSetValue(m_alignmentByButton, alignTopLeftBtn, Alignment(Alignment::TOP, Alignment::LEFT)); - Utils::mapSetValue(m_alignmentByButton, alignTopBtn, Alignment(Alignment::TOP, Alignment::HCENTER)); - Utils::mapSetValue(m_alignmentByButton, alignTopRightBtn, Alignment(Alignment::TOP, Alignment::RIGHT)); - Utils::mapSetValue(m_alignmentByButton, alignLeftBtn, Alignment(Alignment::VCENTER, Alignment::LEFT)); - Utils::mapSetValue(m_alignmentByButton, alignCenterBtn, Alignment(Alignment::VCENTER, Alignment::HCENTER)); - Utils::mapSetValue(m_alignmentByButton, alignRightBtn, Alignment(Alignment::VCENTER, Alignment::RIGHT)); - Utils::mapSetValue(m_alignmentByButton, alignBottomLeftBtn, Alignment(Alignment::BOTTOM, Alignment::LEFT)); - Utils::mapSetValue(m_alignmentByButton, alignBottomBtn, Alignment(Alignment::BOTTOM, Alignment::HCENTER)); - Utils::mapSetValue(m_alignmentByButton, alignBottomRightBtn, Alignment(Alignment::BOTTOM, Alignment::RIGHT)); - - m_alignmentButtonGroup = std::make_unique(this); - for (const auto& buttonAndAlignment : m_alignmentByButton) { - m_alignmentButtonGroup->addButton(buttonAndAlignment.first); - } - - setupUiConnections(); + : m_settings(std::move(settings)), + m_pageSelectionAccessor(page_selection_accessor), + m_leftRightLinked(true), + m_topBottomLinked(true) { + { + QSettings app_settings; + m_leftRightLinked = app_settings.value("margins/leftRightLinked", true).toBool(); + m_topBottomLinked = app_settings.value("margins/topBottomLinked", true).toBool(); + } + + m_chainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-24.png"))); + m_brokenChainIcon.addPixmap(QPixmap(QString::fromLatin1(":/icons/stock-vchain-broken-24.png"))); + + setupUi(this); + updateLinkDisplay(topBottomLink, m_topBottomLinked); + updateLinkDisplay(leftRightLink, m_leftRightLinked); + updateAlignmentButtonsEnabled(); + + Utils::mapSetValue(m_alignmentByButton, alignTopLeftBtn, Alignment(Alignment::TOP, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignTopBtn, Alignment(Alignment::TOP, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignTopRightBtn, Alignment(Alignment::TOP, Alignment::RIGHT)); + Utils::mapSetValue(m_alignmentByButton, alignLeftBtn, Alignment(Alignment::VCENTER, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignCenterBtn, Alignment(Alignment::VCENTER, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignRightBtn, Alignment(Alignment::VCENTER, Alignment::RIGHT)); + Utils::mapSetValue(m_alignmentByButton, alignBottomLeftBtn, Alignment(Alignment::BOTTOM, Alignment::LEFT)); + Utils::mapSetValue(m_alignmentByButton, alignBottomBtn, Alignment(Alignment::BOTTOM, Alignment::HCENTER)); + Utils::mapSetValue(m_alignmentByButton, alignBottomRightBtn, Alignment(Alignment::BOTTOM, Alignment::RIGHT)); + + m_alignmentButtonGroup = std::make_unique(this); + for (const auto& buttonAndAlignment : m_alignmentByButton) { + m_alignmentButtonGroup->addButton(buttonAndAlignment.first); + } + + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::preUpdateUI(const PageInfo& page_info, const Margins& margins_mm, const Alignment& alignment) { - removeUiConnections(); - - m_pageId = page_info.id(); - m_dpi = page_info.metadata().dpi(); - m_marginsMM = margins_mm; - m_alignment = alignment; - - auto old_ignore = static_cast(m_ignoreMarginChanges); - m_ignoreMarginChanges = true; - - for (const auto& kv : m_alignmentByButton) { - if (m_alignment.isAuto() || m_alignment.isOriginal()) { - if (!m_alignment.isAutoHorizontal() && (kv.second.vertical() == Alignment::VCENTER) - && (kv.second.horizontal() == m_alignment.horizontal())) { - kv.first->setChecked(true); - break; - } else if (!m_alignment.isAutoVertical() && (kv.second.horizontal() == Alignment::HCENTER) - && (kv.second.vertical() == m_alignment.vertical())) { - kv.first->setChecked(true); - break; - } - } else if (kv.second == m_alignment) { - kv.first->setChecked(true); - break; - } - } + removeUiConnections(); - updateUnits(UnitsProvider::getInstance()->getUnits()); + m_pageId = page_info.id(); + m_dpi = page_info.metadata().dpi(); + m_marginsMM = margins_mm; + m_alignment = alignment; - alignWithOthersCB->blockSignals(true); - alignWithOthersCB->setChecked(!alignment.isNull()); - alignWithOthersCB->blockSignals(false); + auto old_ignore = static_cast(m_ignoreMarginChanges); + m_ignoreMarginChanges = true; - alignmentMode->blockSignals(true); - if (alignment.isAuto()) { - alignmentMode->setCurrentIndex(0); - autoAlignSettingsGroup->setVisible(true); - } else if (alignment.isOriginal()) { - alignmentMode->setCurrentIndex(2); - autoAlignSettingsGroup->setVisible(true); - } else { - alignmentMode->setCurrentIndex(1); - autoAlignSettingsGroup->setVisible(false); + for (const auto& kv : m_alignmentByButton) { + if (m_alignment.isAuto() || m_alignment.isOriginal()) { + if (!m_alignment.isAutoHorizontal() && (kv.second.vertical() == Alignment::VCENTER) + && (kv.second.horizontal() == m_alignment.horizontal())) { + kv.first->setChecked(true); + break; + } else if (!m_alignment.isAutoVertical() && (kv.second.horizontal() == Alignment::HCENTER) + && (kv.second.vertical() == m_alignment.vertical())) { + kv.first->setChecked(true); + break; + } + } else if (kv.second == m_alignment) { + kv.first->setChecked(true); + break; } - alignmentMode->blockSignals(false); - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); - - autoMargins->setChecked(m_ptrSettings->isPageAutoMarginsEnabled(m_pageId)); - updateMarginsControlsEnabled(); - - m_leftRightLinked = m_leftRightLinked && (margins_mm.left() == margins_mm.right()); - m_topBottomLinked = m_topBottomLinked && (margins_mm.top() == margins_mm.bottom()); - updateLinkDisplay(topBottomLink, m_topBottomLinked); - updateLinkDisplay(leftRightLink, m_leftRightLinked); - - marginsGroup->setEnabled(false); - alignmentGroup->setEnabled(false); - - m_ignoreMarginChanges = old_ignore; - - setupUiConnections(); + } + + updateUnits(UnitsProvider::getInstance()->getUnits()); + + alignWithOthersCB->blockSignals(true); + alignWithOthersCB->setChecked(!alignment.isNull()); + alignWithOthersCB->blockSignals(false); + + alignmentMode->blockSignals(true); + if (alignment.isAuto()) { + alignmentMode->setCurrentIndex(0); + autoAlignSettingsGroup->setVisible(true); + } else if (alignment.isOriginal()) { + alignmentMode->setCurrentIndex(2); + autoAlignSettingsGroup->setVisible(true); + } else { + alignmentMode->setCurrentIndex(1); + autoAlignSettingsGroup->setVisible(false); + } + alignmentMode->blockSignals(false); + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); + + autoMargins->setChecked(m_settings->isPageAutoMarginsEnabled(m_pageId)); + updateMarginsControlsEnabled(); + + m_leftRightLinked = m_leftRightLinked && (margins_mm.left() == margins_mm.right()); + m_topBottomLinked = m_topBottomLinked && (margins_mm.top() == margins_mm.bottom()); + updateLinkDisplay(topBottomLink, m_topBottomLinked); + updateLinkDisplay(leftRightLink, m_leftRightLinked); + + marginsGroup->setEnabled(false); + alignmentGroup->setEnabled(false); + + m_ignoreMarginChanges = old_ignore; + + setupUiConnections(); } // OptionsWidget::preUpdateUI void OptionsWidget::postUpdateUI() { - removeUiConnections(); + removeUiConnections(); - marginsGroup->setEnabled(true); - alignmentGroup->setEnabled(true); + marginsGroup->setEnabled(true); + alignmentGroup->setEnabled(true); - m_marginsMM = m_ptrSettings->getHardMarginsMM(m_pageId); - updateMarginsDisplay(); + m_marginsMM = m_settings->getHardMarginsMM(m_pageId); + updateMarginsDisplay(); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::marginsSetExternally(const Margins& margins_mm) { - m_marginsMM = margins_mm; + m_marginsMM = margins_mm; - if (autoMargins->isChecked()) { - autoMargins->setChecked(false); - m_ptrSettings->setPageAutoMarginsEnabled(m_pageId, false); - updateMarginsControlsEnabled(); - } + if (autoMargins->isChecked()) { + autoMargins->setChecked(false); + m_settings->setPageAutoMarginsEnabled(m_pageId, false); + updateMarginsControlsEnabled(); + } - updateMarginsDisplay(); + updateMarginsDisplay(); } void OptionsWidget::updateUnits(const Units units) { - removeUiConnections(); - - int decimals; - double step; - switch (units) { - case PIXELS: - case MILLIMETRES: - decimals = 1; - step = 1.0; - break; - default: - decimals = 2; - step = 0.01; - break; - } - - topMarginSpinBox->setDecimals(decimals); - topMarginSpinBox->setSingleStep(step); - bottomMarginSpinBox->setDecimals(decimals); - bottomMarginSpinBox->setSingleStep(step); - leftMarginSpinBox->setDecimals(decimals); - leftMarginSpinBox->setSingleStep(step); - rightMarginSpinBox->setDecimals(decimals); - rightMarginSpinBox->setSingleStep(step); - - updateMarginsDisplay(); - - setupUiConnections(); + removeUiConnections(); + + int decimals; + double step; + switch (units) { + case PIXELS: + case MILLIMETRES: + decimals = 1; + step = 1.0; + break; + default: + decimals = 2; + step = 0.01; + break; + } + + topMarginSpinBox->setDecimals(decimals); + topMarginSpinBox->setSingleStep(step); + bottomMarginSpinBox->setDecimals(decimals); + bottomMarginSpinBox->setSingleStep(step); + leftMarginSpinBox->setDecimals(decimals); + leftMarginSpinBox->setSingleStep(step); + rightMarginSpinBox->setDecimals(decimals); + rightMarginSpinBox->setSingleStep(step); + + updateMarginsDisplay(); + + setupUiConnections(); } void OptionsWidget::horMarginsChanged(const double val) { - if (m_ignoreMarginChanges) { - return; - } + if (m_ignoreMarginChanges) { + return; + } - if (m_leftRightLinked) { - const ScopedIncDec ignore_scope(m_ignoreMarginChanges); - leftMarginSpinBox->setValue(val); - rightMarginSpinBox->setValue(val); - } + if (m_leftRightLinked) { + const ScopedIncDec ignore_scope(m_ignoreMarginChanges); + leftMarginSpinBox->setValue(val); + rightMarginSpinBox->setValue(val); + } - double dummy; - double leftMarginSpinBoxValue = leftMarginSpinBox->value(); - double rightMarginSpinBoxValue = rightMarginSpinBox->value(); - UnitsProvider::getInstance()->convertTo(leftMarginSpinBoxValue, dummy, MILLIMETRES, m_dpi); - UnitsProvider::getInstance()->convertTo(rightMarginSpinBoxValue, dummy, MILLIMETRES, m_dpi); + double dummy; + double leftMarginSpinBoxValue = leftMarginSpinBox->value(); + double rightMarginSpinBoxValue = rightMarginSpinBox->value(); + UnitsProvider::getInstance()->convertTo(leftMarginSpinBoxValue, dummy, MILLIMETRES, m_dpi); + UnitsProvider::getInstance()->convertTo(rightMarginSpinBoxValue, dummy, MILLIMETRES, m_dpi); - m_marginsMM.setLeft(leftMarginSpinBoxValue); - m_marginsMM.setRight(rightMarginSpinBoxValue); + m_marginsMM.setLeft(leftMarginSpinBoxValue); + m_marginsMM.setRight(rightMarginSpinBoxValue); - emit marginsSetLocally(m_marginsMM); + emit marginsSetLocally(m_marginsMM); } void OptionsWidget::vertMarginsChanged(const double val) { - if (m_ignoreMarginChanges) { - return; - } + if (m_ignoreMarginChanges) { + return; + } - if (m_topBottomLinked) { - const ScopedIncDec ignore_scope(m_ignoreMarginChanges); - topMarginSpinBox->setValue(val); - bottomMarginSpinBox->setValue(val); - } + if (m_topBottomLinked) { + const ScopedIncDec ignore_scope(m_ignoreMarginChanges); + topMarginSpinBox->setValue(val); + bottomMarginSpinBox->setValue(val); + } - double dummy; - double topMarginSpinBoxValue = topMarginSpinBox->value(); - double bottomMarginSpinBoxValue = bottomMarginSpinBox->value(); - UnitsProvider::getInstance()->convertTo(dummy, topMarginSpinBoxValue, MILLIMETRES, m_dpi); - UnitsProvider::getInstance()->convertTo(dummy, bottomMarginSpinBoxValue, MILLIMETRES, m_dpi); + double dummy; + double topMarginSpinBoxValue = topMarginSpinBox->value(); + double bottomMarginSpinBoxValue = bottomMarginSpinBox->value(); + UnitsProvider::getInstance()->convertTo(dummy, topMarginSpinBoxValue, MILLIMETRES, m_dpi); + UnitsProvider::getInstance()->convertTo(dummy, bottomMarginSpinBoxValue, MILLIMETRES, m_dpi); - m_marginsMM.setTop(topMarginSpinBoxValue); - m_marginsMM.setBottom(bottomMarginSpinBoxValue); + m_marginsMM.setTop(topMarginSpinBoxValue); + m_marginsMM.setBottom(bottomMarginSpinBoxValue); - emit marginsSetLocally(m_marginsMM); + emit marginsSetLocally(m_marginsMM); } void OptionsWidget::topBottomLinkClicked() { - m_topBottomLinked = !m_topBottomLinked; - QSettings().setValue("margins/topBottomLinked", m_topBottomLinked); - updateLinkDisplay(topBottomLink, m_topBottomLinked); - topBottomLinkToggled(m_topBottomLinked); + m_topBottomLinked = !m_topBottomLinked; + QSettings().setValue("margins/topBottomLinked", m_topBottomLinked); + updateLinkDisplay(topBottomLink, m_topBottomLinked); + topBottomLinkToggled(m_topBottomLinked); } void OptionsWidget::leftRightLinkClicked() { - m_leftRightLinked = !m_leftRightLinked; - QSettings().setValue("margins/leftRightLinked", m_leftRightLinked); - updateLinkDisplay(leftRightLink, m_leftRightLinked); - leftRightLinkToggled(m_leftRightLinked); + m_leftRightLinked = !m_leftRightLinked; + QSettings().setValue("margins/leftRightLinked", m_leftRightLinked); + updateLinkDisplay(leftRightLink, m_leftRightLinked); + leftRightLinkToggled(m_leftRightLinked); } void OptionsWidget::alignWithOthersToggled() { - m_alignment.setNull(!alignWithOthersCB->isChecked()); - updateAlignmentButtonsEnabled(); - emit alignmentChanged(m_alignment); + m_alignment.setNull(!alignWithOthersCB->isChecked()); + updateAlignmentButtonsEnabled(); + emit alignmentChanged(m_alignment); } void OptionsWidget::autoMarginsToggled(bool checked) { - if (m_ignoreMarginChanges) { - return; - } + if (m_ignoreMarginChanges) { + return; + } - m_ptrSettings->setPageAutoMarginsEnabled(m_pageId, checked); - updateMarginsControlsEnabled(); + m_settings->setPageAutoMarginsEnabled(m_pageId, checked); + updateMarginsControlsEnabled(); - emit reloadRequested(); + emit reloadRequested(); } void OptionsWidget::alignmentModeChanged(int idx) { - switch (idx) { - case 0: - m_alignment.setVertical(Alignment::VAUTO); - m_alignment.setHorizontal(Alignment::HAUTO); - autoAlignSettingsGroup->setVisible(true); - updateAutoModeButtons(); - break; - case 1: - m_alignment = m_alignmentByButton.at(getCheckedAlignmentButton()); - autoAlignSettingsGroup->setVisible(false); - break; - case 2: - m_alignment.setVertical(Alignment::VORIGINAL); - if (m_ptrSettings->isPageAutoMarginsEnabled(m_pageId)) { - m_alignment.setHorizontal(Alignment::HORIGINAL); - } else { - m_alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); - } - autoAlignSettingsGroup->setVisible(true); - updateAutoModeButtons(); - break; - default: - break; - } - - updateAlignmentButtonsEnabled(); - emit alignmentChanged(m_alignment); + switch (idx) { + case 0: + m_alignment.setVertical(Alignment::VAUTO); + m_alignment.setHorizontal(Alignment::HAUTO); + autoAlignSettingsGroup->setVisible(true); + updateAutoModeButtons(); + break; + case 1: + m_alignment = m_alignmentByButton.at(getCheckedAlignmentButton()); + autoAlignSettingsGroup->setVisible(false); + break; + case 2: + m_alignment.setVertical(Alignment::VORIGINAL); + if (m_settings->isPageAutoMarginsEnabled(m_pageId)) { + m_alignment.setHorizontal(Alignment::HORIGINAL); + } else { + m_alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); + } + autoAlignSettingsGroup->setVisible(true); + updateAutoModeButtons(); + break; + default: + break; + } + + updateAlignmentButtonsEnabled(); + emit alignmentChanged(m_alignment); } void OptionsWidget::alignmentButtonClicked() { - if (m_ignoreAlignmentButtonsChanges) { - return; - } + if (m_ignoreAlignmentButtonsChanges) { + return; + } - auto* const button = dynamic_cast(sender()); - assert(button); + auto* const button = dynamic_cast(sender()); + assert(button); - const Alignment& alignment = m_alignmentByButton.at(button); + const Alignment& alignment = m_alignmentByButton.at(button); - if (m_alignment.isAutoVertical()) { - m_alignment.setHorizontal(alignment.horizontal()); - } else if (m_alignment.isAutoHorizontal()) { - m_alignment.setVertical(alignment.vertical()); - } else { - m_alignment = alignment; - } + if (m_alignment.isAutoVertical()) { + m_alignment.setHorizontal(alignment.horizontal()); + } else if (m_alignment.isAutoHorizontal()) { + m_alignment.setVertical(alignment.vertical()); + } else { + m_alignment = alignment; + } - emit alignmentChanged(m_alignment); + emit alignmentChanged(m_alignment); } void OptionsWidget::showApplyMarginsDialog() { - auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Margins")); - connect(dialog, SIGNAL(accepted(const std::set&)), this, SLOT(applyMargins(const std::set&))); - dialog->show(); + auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Margins")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, SLOT(applyMargins(const std::set&))); + dialog->show(); } void OptionsWidget::showApplyAlignmentDialog() { - auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle(tr("Apply Alignment")); - connect(dialog, SIGNAL(accepted(const std::set&)), this, SLOT(applyAlignment(const std::set&))); - dialog->show(); + auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("Apply Alignment")); + connect(dialog, SIGNAL(accepted(const std::set&)), this, SLOT(applyAlignment(const std::set&))); + dialog->show(); } void OptionsWidget::applyMargins(const std::set& pages) { - if (pages.empty()) { - return; + if (pages.empty()) { + return; + } + + const bool autoMarginsEnabled = m_settings->isPageAutoMarginsEnabled(m_pageId); + for (const PageId& page_id : pages) { + if (page_id == m_pageId) { + continue; } - const bool autoMarginsEnabled = m_ptrSettings->isPageAutoMarginsEnabled(m_pageId); - for (const PageId& page_id : pages) { - if (page_id == m_pageId) { - continue; - } - - m_ptrSettings->setPageAutoMarginsEnabled(page_id, autoMarginsEnabled); - if (autoMarginsEnabled) { - m_ptrSettings->invalidateContentSize(page_id); - } else { - m_ptrSettings->setHardMarginsMM(page_id, m_marginsMM); - } + m_settings->setPageAutoMarginsEnabled(page_id, autoMarginsEnabled); + if (autoMarginsEnabled) { + m_settings->invalidateContentSize(page_id); + } else { + m_settings->setHardMarginsMM(page_id, m_marginsMM); } + } - emit aggregateHardSizeChanged(); - emit invalidateAllThumbnails(); + emit aggregateHardSizeChanged(); + emit invalidateAllThumbnails(); } void OptionsWidget::applyAlignment(const std::set& pages) { - if (pages.empty()) { - return; - } + if (pages.empty()) { + return; + } - for (const PageId& page_id : pages) { - if (page_id == m_pageId) { - continue; - } - - m_ptrSettings->setPageAlignment(page_id, m_alignment); + for (const PageId& page_id : pages) { + if (page_id == m_pageId) { + continue; } - emit invalidateAllThumbnails(); + m_settings->setPageAlignment(page_id, m_alignment); + } + + emit invalidateAllThumbnails(); } void OptionsWidget::updateMarginsDisplay() { - const ScopedIncDec ignore_scope(m_ignoreMarginChanges); - - double topMarginValue = m_marginsMM.top(); - double bottomMarginValue = m_marginsMM.bottom(); - double leftMarginValue = m_marginsMM.left(); - double rightMarginValue = m_marginsMM.right(); - UnitsProvider::getInstance()->convertFrom(leftMarginValue, topMarginValue, MILLIMETRES, m_dpi); - UnitsProvider::getInstance()->convertFrom(rightMarginValue, bottomMarginValue, MILLIMETRES, m_dpi); - - topMarginSpinBox->setValue(topMarginValue); - bottomMarginSpinBox->setValue(bottomMarginValue); - leftMarginSpinBox->setValue(leftMarginValue); - rightMarginSpinBox->setValue(rightMarginValue); - - m_leftRightLinked = m_leftRightLinked && (leftMarginSpinBox->value() == rightMarginSpinBox->value()); - m_topBottomLinked = m_topBottomLinked && (topMarginSpinBox->value() == bottomMarginSpinBox->value()); - updateLinkDisplay(topBottomLink, m_topBottomLinked); - updateLinkDisplay(leftRightLink, m_leftRightLinked); + const ScopedIncDec ignore_scope(m_ignoreMarginChanges); + + double topMarginValue = m_marginsMM.top(); + double bottomMarginValue = m_marginsMM.bottom(); + double leftMarginValue = m_marginsMM.left(); + double rightMarginValue = m_marginsMM.right(); + UnitsProvider::getInstance()->convertFrom(leftMarginValue, topMarginValue, MILLIMETRES, m_dpi); + UnitsProvider::getInstance()->convertFrom(rightMarginValue, bottomMarginValue, MILLIMETRES, m_dpi); + + topMarginSpinBox->setValue(topMarginValue); + bottomMarginSpinBox->setValue(bottomMarginValue); + leftMarginSpinBox->setValue(leftMarginValue); + rightMarginSpinBox->setValue(rightMarginValue); + + m_leftRightLinked = m_leftRightLinked && (leftMarginSpinBox->value() == rightMarginSpinBox->value()); + m_topBottomLinked = m_topBottomLinked && (topMarginSpinBox->value() == bottomMarginSpinBox->value()); + updateLinkDisplay(topBottomLink, m_topBottomLinked); + updateLinkDisplay(leftRightLink, m_leftRightLinked); } void OptionsWidget::updateLinkDisplay(QToolButton* button, const bool linked) { - button->setIcon(linked ? m_chainIcon : m_brokenChainIcon); + button->setIcon(linked ? m_chainIcon : m_brokenChainIcon); } void OptionsWidget::updateAlignmentButtonsEnabled() { - bool enableHorizontalButtons = !m_alignment.isAutoHorizontal() ? alignWithOthersCB->isChecked() : false; - bool enableVerticalButtons = !m_alignment.isAutoVertical() ? alignWithOthersCB->isChecked() : false; - - alignTopLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignTopBtn->setEnabled(enableVerticalButtons); - alignTopRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignLeftBtn->setEnabled(enableHorizontalButtons); - alignCenterBtn->setEnabled(enableHorizontalButtons || enableVerticalButtons); - alignRightBtn->setEnabled(enableHorizontalButtons); - alignBottomLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); - alignBottomBtn->setEnabled(enableVerticalButtons); - alignBottomRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + bool enableHorizontalButtons = !m_alignment.isAutoHorizontal() ? alignWithOthersCB->isChecked() : false; + bool enableVerticalButtons = !m_alignment.isAutoVertical() ? alignWithOthersCB->isChecked() : false; + + alignTopLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignTopBtn->setEnabled(enableVerticalButtons); + alignTopRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignLeftBtn->setEnabled(enableHorizontalButtons); + alignCenterBtn->setEnabled(enableHorizontalButtons || enableVerticalButtons); + alignRightBtn->setEnabled(enableHorizontalButtons); + alignBottomLeftBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); + alignBottomBtn->setEnabled(enableVerticalButtons); + alignBottomRightBtn->setEnabled(enableHorizontalButtons && enableVerticalButtons); } void OptionsWidget::updateMarginsControlsEnabled() { - const bool enabled = !m_ptrSettings->isPageAutoMarginsEnabled(m_pageId); - - topMarginSpinBox->setEnabled(enabled); - bottomMarginSpinBox->setEnabled(enabled); - leftMarginSpinBox->setEnabled(enabled); - rightMarginSpinBox->setEnabled(enabled); - topBottomLink->setEnabled(enabled); - leftRightLink->setEnabled(enabled); + const bool enabled = !m_settings->isPageAutoMarginsEnabled(m_pageId); + + topMarginSpinBox->setEnabled(enabled); + bottomMarginSpinBox->setEnabled(enabled); + leftMarginSpinBox->setEnabled(enabled); + rightMarginSpinBox->setEnabled(enabled); + topBottomLink->setEnabled(enabled); + leftRightLink->setEnabled(enabled); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - connect(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - connect(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - connect(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - connect(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); - connect(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); - connect(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); - connect(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); - connect(applyMarginsBtn, SIGNAL(clicked()), this, SLOT(showApplyMarginsDialog())); - connect(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled())); - connect(applyAlignmentBtn, SIGNAL(clicked()), this, SLOT(showApplyAlignmentDialog())); - connect(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); - connect(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); - - for (const auto& kv : m_alignmentByButton) { - connect(kv.first, SIGNAL(clicked()), this, SLOT(alignmentButtonClicked())); - } + CONNECT(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); + CONNECT(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); + CONNECT(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); + CONNECT(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); + CONNECT(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); + CONNECT(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); + CONNECT(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); + CONNECT(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); + CONNECT(applyMarginsBtn, SIGNAL(clicked()), this, SLOT(showApplyMarginsDialog())); + CONNECT(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled())); + CONNECT(applyAlignmentBtn, SIGNAL(clicked()), this, SLOT(showApplyAlignmentDialog())); + CONNECT(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); + CONNECT(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); + + for (const auto& kv : m_alignmentByButton) { + CONNECT(kv.first, SIGNAL(clicked()), this, SLOT(alignmentButtonClicked())); + } } +#undef CONNECT + void OptionsWidget::removeUiConnections() { - disconnect(topMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - disconnect(bottomMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(vertMarginsChanged(double))); - disconnect(leftMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - disconnect(rightMarginSpinBox, SIGNAL(valueChanged(double)), this, SLOT(horMarginsChanged(double))); - disconnect(autoMargins, SIGNAL(toggled(bool)), this, SLOT(autoMarginsToggled(bool))); - disconnect(alignmentMode, SIGNAL(currentIndexChanged(int)), this, SLOT(alignmentModeChanged(int))); - disconnect(topBottomLink, SIGNAL(clicked()), this, SLOT(topBottomLinkClicked())); - disconnect(leftRightLink, SIGNAL(clicked()), this, SLOT(leftRightLinkClicked())); - disconnect(applyMarginsBtn, SIGNAL(clicked()), this, SLOT(showApplyMarginsDialog())); - disconnect(alignWithOthersCB, SIGNAL(toggled(bool)), this, SLOT(alignWithOthersToggled())); - disconnect(applyAlignmentBtn, SIGNAL(clicked()), this, SLOT(showApplyAlignmentDialog())); - disconnect(autoHorizontalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoHorizontalAligningToggled(bool))); - disconnect(autoVerticalAligningCB, SIGNAL(toggled(bool)), this, SLOT(autoVerticalAligningToggled(bool))); - - for (const auto& kv : m_alignmentByButton) { - disconnect(kv.first, SIGNAL(clicked()), this, SLOT(alignmentButtonClicked())); - } + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } bool OptionsWidget::leftRightLinked() const { - return m_leftRightLinked; + return m_leftRightLinked; } bool OptionsWidget::topBottomLinked() const { - return m_topBottomLinked; + return m_topBottomLinked; } const Margins& OptionsWidget::marginsMM() const { - return m_marginsMM; + return m_marginsMM; } const Alignment& OptionsWidget::alignment() const { - return m_alignment; + return m_alignment; } void OptionsWidget::autoHorizontalAligningToggled(const bool checked) { - if (checked) { - m_alignment.setHorizontal((alignmentMode->currentIndex() == 0) ? Alignment::HAUTO : Alignment::HORIGINAL); - } else { - m_alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); - } - - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); - emit alignmentChanged(m_alignment); + if (checked) { + m_alignment.setHorizontal((alignmentMode->currentIndex() == 0) ? Alignment::HAUTO : Alignment::HORIGINAL); + } else { + m_alignment.setHorizontal(m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()); + } + + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); + emit alignmentChanged(m_alignment); } void OptionsWidget::autoVerticalAligningToggled(const bool checked) { - if (checked) { - m_alignment.setVertical((alignmentMode->currentIndex() == 0) ? Alignment::VAUTO : Alignment::VORIGINAL); - } else { - m_alignment.setVertical(m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()); - } - - updateAlignmentButtonsEnabled(); - updateAutoModeButtons(); - emit alignmentChanged(m_alignment); + if (checked) { + m_alignment.setVertical((alignmentMode->currentIndex() == 0) ? Alignment::VAUTO : Alignment::VORIGINAL); + } else { + m_alignment.setVertical(m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()); + } + + updateAlignmentButtonsEnabled(); + updateAutoModeButtons(); + emit alignmentChanged(m_alignment); } void OptionsWidget::updateAutoModeButtons() { - const ScopedIncDec scope_guard(m_ignoreAlignmentButtonsChanges); + const ScopedIncDec scope_guard(m_ignoreAlignmentButtonsChanges); - if (m_alignment.isAuto() || m_alignment.isOriginal()) { - autoVerticalAligningCB->setChecked(m_alignment.isAutoVertical()); - autoHorizontalAligningCB->setChecked(m_alignment.isAutoHorizontal()); - - if (autoVerticalAligningCB->isChecked() && !autoHorizontalAligningCB->isChecked()) { - autoVerticalAligningCB->setEnabled(false); - } else if (autoHorizontalAligningCB->isChecked() && !autoVerticalAligningCB->isChecked()) { - autoHorizontalAligningCB->setEnabled(false); - } else { - autoVerticalAligningCB->setEnabled(true); - autoHorizontalAligningCB->setEnabled(true); - } - } + if (m_alignment.isAuto() || m_alignment.isOriginal()) { + autoVerticalAligningCB->setChecked(m_alignment.isAutoVertical()); + autoHorizontalAligningCB->setChecked(m_alignment.isAutoHorizontal()); - if (m_alignment.isAutoVertical() && !m_alignment.isAutoHorizontal()) { - switch (m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()) { - case Alignment::LEFT: - alignLeftBtn->setChecked(true); - break; - case Alignment::RIGHT: - alignRightBtn->setChecked(true); - break; - default: - alignCenterBtn->setChecked(true); - break; - } - } else if (m_alignment.isAutoHorizontal() && !m_alignment.isAutoVertical()) { - switch (m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()) { - case Alignment::TOP: - alignTopBtn->setChecked(true); - break; - case Alignment::BOTTOM: - alignBottomBtn->setChecked(true); - break; - default: - alignCenterBtn->setChecked(true); - break; - } + if (autoVerticalAligningCB->isChecked() && !autoHorizontalAligningCB->isChecked()) { + autoVerticalAligningCB->setEnabled(false); + } else if (autoHorizontalAligningCB->isChecked() && !autoVerticalAligningCB->isChecked()) { + autoHorizontalAligningCB->setEnabled(false); + } else { + autoVerticalAligningCB->setEnabled(true); + autoHorizontalAligningCB->setEnabled(true); + } + } + + if (m_alignment.isAutoVertical() && !m_alignment.isAutoHorizontal()) { + switch (m_alignmentByButton.at(getCheckedAlignmentButton()).horizontal()) { + case Alignment::LEFT: + alignLeftBtn->setChecked(true); + break; + case Alignment::RIGHT: + alignRightBtn->setChecked(true); + break; + default: + alignCenterBtn->setChecked(true); + break; } + } else if (m_alignment.isAutoHorizontal() && !m_alignment.isAutoVertical()) { + switch (m_alignmentByButton.at(getCheckedAlignmentButton()).vertical()) { + case Alignment::TOP: + alignTopBtn->setChecked(true); + break; + case Alignment::BOTTOM: + alignBottomBtn->setChecked(true); + break; + default: + alignCenterBtn->setChecked(true); + break; + } + } } QToolButton* OptionsWidget::getCheckedAlignmentButton() const { - auto* checkedButton = dynamic_cast(m_alignmentButtonGroup->checkedButton()); - if (!checkedButton) { - checkedButton = alignCenterBtn; - } + auto* checkedButton = dynamic_cast(m_alignmentButtonGroup->checkedButton()); + if (!checkedButton) { + checkedButton = alignCenterBtn; + } - return checkedButton; + return checkedButton; } } // namespace page_layout diff --git a/filters/page_layout/OptionsWidget.h b/filters/page_layout/OptionsWidget.h index fd0306493..15af817e1 100644 --- a/filters/page_layout/OptionsWidget.h +++ b/filters/page_layout/OptionsWidget.h @@ -19,18 +19,19 @@ #ifndef PAGE_LAYOUT_OPTIONSWIDGET_H_ #define PAGE_LAYOUT_OPTIONSWIDGET_H_ -#include "ui_PageLayoutOptionsWidget.h" -#include "FilterOptionsWidget.h" -#include "PageSelectionAccessor.h" -#include "intrusive_ptr.h" -#include "Margins.h" -#include "Alignment.h" -#include "PageId.h" +#include #include +#include #include -#include #include -#include +#include +#include "Alignment.h" +#include "FilterOptionsWidget.h" +#include "Margins.h" +#include "PageId.h" +#include "PageSelectionAccessor.h" +#include "intrusive_ptr.h" +#include "ui_PageLayoutOptionsWidget.h" class QToolButton; class ProjectPages; @@ -39,106 +40,108 @@ namespace page_layout { class Settings; class OptionsWidget : public FilterOptionsWidget, public UnitsObserver, private Ui::PageLayoutOptionsWidget { - Q_OBJECT -public: - OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); + + ~OptionsWidget() override; - ~OptionsWidget() override; + void preUpdateUI(const PageInfo& page_info, const Margins& margins_mm, const Alignment& alignment); - void preUpdateUI(const PageInfo& page_info, const Margins& margins_mm, const Alignment& alignment); + void postUpdateUI(); - void postUpdateUI(); + bool leftRightLinked() const; - bool leftRightLinked() const; + bool topBottomLinked() const; - bool topBottomLinked() const; + const Margins& marginsMM() const; - const Margins& marginsMM() const; + const Alignment& alignment() const; - const Alignment& alignment() const; + void updateUnits(Units units) override; - void updateUnits(Units units) override; + signals: -signals: + void leftRightLinkToggled(bool linked); - void leftRightLinkToggled(bool linked); + void topBottomLinkToggled(bool linked); - void topBottomLinkToggled(bool linked); + void alignmentChanged(const Alignment& alignment); - void alignmentChanged(const Alignment& alignment); + void marginsSetLocally(const Margins& margins_mm); - void marginsSetLocally(const Margins& margins_mm); + void aggregateHardSizeChanged(); - void aggregateHardSizeChanged(); + public slots: -public slots: + void marginsSetExternally(const Margins& margins_mm); - void marginsSetExternally(const Margins& margins_mm); + private slots: -private slots: + void horMarginsChanged(double val); - void horMarginsChanged(double val); + void vertMarginsChanged(double val); - void vertMarginsChanged(double val); + void autoMarginsToggled(bool checked); - void autoMarginsToggled(bool checked); + void alignmentModeChanged(int idx); - void alignmentModeChanged(int idx); + void topBottomLinkClicked(); - void topBottomLinkClicked(); + void leftRightLinkClicked(); - void leftRightLinkClicked(); + void alignWithOthersToggled(); - void alignWithOthersToggled(); + void alignmentButtonClicked(); - void alignmentButtonClicked(); + void autoHorizontalAligningToggled(bool checked); - void autoHorizontalAligningToggled(bool checked); + void autoVerticalAligningToggled(bool checked); - void autoVerticalAligningToggled(bool checked); + void showApplyMarginsDialog(); - void showApplyMarginsDialog(); + void showApplyAlignmentDialog(); - void showApplyAlignmentDialog(); + void applyMargins(const std::set& pages); - void applyMargins(const std::set& pages); + void applyAlignment(const std::set& pages); - void applyAlignment(const std::set& pages); + private: + typedef std::unordered_map AlignmentByButton; -private: - typedef std::unordered_map AlignmentByButton; + void updateMarginsDisplay(); - void updateMarginsDisplay(); + void updateLinkDisplay(QToolButton* button, bool linked); - void updateLinkDisplay(QToolButton* button, bool linked); + void updateAlignmentButtonsEnabled(); - void updateAlignmentButtonsEnabled(); + void updateMarginsControlsEnabled(); - void updateMarginsControlsEnabled(); + void updateAutoModeButtons(); - void updateAutoModeButtons(); + QToolButton* getCheckedAlignmentButton() const; - QToolButton* getCheckedAlignmentButton() const; + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + intrusive_ptr m_settings; + PageSelectionAccessor m_pageSelectionAccessor; + QIcon m_chainIcon; + QIcon m_brokenChainIcon; + AlignmentByButton m_alignmentByButton; + PageId m_pageId; + Dpi m_dpi; + Margins m_marginsMM; + Alignment m_alignment; + bool m_leftRightLinked; + bool m_topBottomLinked; + std::unique_ptr m_alignmentButtonGroup; - intrusive_ptr m_ptrSettings; - PageSelectionAccessor m_pageSelectionAccessor; - QIcon m_chainIcon; - QIcon m_brokenChainIcon; - AlignmentByButton m_alignmentByButton; - PageId m_pageId; - Dpi m_dpi; - Margins m_marginsMM; - Alignment m_alignment; - bool m_leftRightLinked; - bool m_topBottomLinked; - std::unique_ptr m_alignmentButtonGroup; + int m_ignoreMarginChanges = 0; + int m_ignoreAlignmentButtonsChanges = 0; - int m_ignoreMarginChanges = 0; - int m_ignoreAlignmentButtonsChanges = 0; + std::list m_connectionList; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_OPTIONSWIDGET_H_ diff --git a/filters/page_layout/OrderByHeightProvider.cpp b/filters/page_layout/OrderByHeightProvider.cpp index e7f9a8303..a69672167 100644 --- a/filters/page_layout/OrderByHeightProvider.cpp +++ b/filters/page_layout/OrderByHeightProvider.cpp @@ -22,37 +22,36 @@ #include "Params.h" namespace page_layout { -OrderByHeightProvider::OrderByHeightProvider(intrusive_ptr settings) : m_ptrSettings(std::move(settings)) { -} +OrderByHeightProvider::OrderByHeightProvider(intrusive_ptr settings) : m_settings(std::move(settings)) {} bool OrderByHeightProvider::precedes(const PageId& lhs_page, const bool lhs_incomplete, const PageId& rhs_page, const bool rhs_incomplete) const { - const std::unique_ptr lhs_params(m_ptrSettings->getPageParams(lhs_page)); - const std::unique_ptr rhs_params(m_ptrSettings->getPageParams(rhs_page)); - - QSizeF lhs_size; - if (lhs_params) { - const Margins margins(lhs_params->hardMarginsMM()); - lhs_size = lhs_params->contentSizeMM(); - lhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); - } - QSizeF rhs_size; - if (rhs_params) { - const Margins margins(rhs_params->hardMarginsMM()); - rhs_size = rhs_params->contentSizeMM(); - rhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); - } - - const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); - const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); - - if (lhs_valid != rhs_valid) { - // Invalid (unknown) sizes go to the back. - return lhs_valid; - } - - return lhs_size.height() < rhs_size.height(); + const std::unique_ptr lhs_params(m_settings->getPageParams(lhs_page)); + const std::unique_ptr rhs_params(m_settings->getPageParams(rhs_page)); + + QSizeF lhs_size; + if (lhs_params) { + const Margins margins(lhs_params->hardMarginsMM()); + lhs_size = lhs_params->contentSizeMM(); + lhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); + } + QSizeF rhs_size; + if (rhs_params) { + const Margins margins(rhs_params->hardMarginsMM()); + rhs_size = rhs_params->contentSizeMM(); + rhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); + } + + const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); + const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); + + if (lhs_valid != rhs_valid) { + // Invalid (unknown) sizes go to the back. + return lhs_valid; + } + + return lhs_size.height() < rhs_size.height(); } // OrderByHeightProvider::precedes } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/OrderByHeightProvider.h b/filters/page_layout/OrderByHeightProvider.h index babce1072..c694c82ca 100644 --- a/filters/page_layout/OrderByHeightProvider.h +++ b/filters/page_layout/OrderByHeightProvider.h @@ -19,22 +19,22 @@ #ifndef PAGE_LAYOUT_ORDER_BY_HEIGHT_PROVIDER_H_ #define PAGE_LAYOUT_ORDER_BY_HEIGHT_PROVIDER_H_ +#include "PageOrderProvider.h" #include "Settings.h" #include "intrusive_ptr.h" -#include "PageOrderProvider.h" namespace page_layout { class OrderByHeightProvider : public PageOrderProvider { -public: - explicit OrderByHeightProvider(intrusive_ptr settings); + public: + explicit OrderByHeightProvider(intrusive_ptr settings); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_settings; }; } // namespace page_layout #endif diff --git a/filters/page_layout/OrderByWidthProvider.cpp b/filters/page_layout/OrderByWidthProvider.cpp index cd79dc642..9fbfa1a66 100644 --- a/filters/page_layout/OrderByWidthProvider.cpp +++ b/filters/page_layout/OrderByWidthProvider.cpp @@ -22,37 +22,36 @@ #include "Params.h" namespace page_layout { -OrderByWidthProvider::OrderByWidthProvider(intrusive_ptr settings) : m_ptrSettings(std::move(settings)) { -} +OrderByWidthProvider::OrderByWidthProvider(intrusive_ptr settings) : m_settings(std::move(settings)) {} bool OrderByWidthProvider::precedes(const PageId& lhs_page, const bool lhs_incomplete, const PageId& rhs_page, const bool rhs_incomplete) const { - const std::unique_ptr lhs_params(m_ptrSettings->getPageParams(lhs_page)); - const std::unique_ptr rhs_params(m_ptrSettings->getPageParams(rhs_page)); - - QSizeF lhs_size; - if (lhs_params) { - const Margins margins(lhs_params->hardMarginsMM()); - lhs_size = lhs_params->contentSizeMM(); - lhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); - } - QSizeF rhs_size; - if (rhs_params) { - const Margins margins(rhs_params->hardMarginsMM()); - rhs_size = rhs_params->contentSizeMM(); - rhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); - } - - const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); - const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); - - if (lhs_valid != rhs_valid) { - // Invalid (unknown) sizes go to the back. - return lhs_valid; - } - - return lhs_size.width() < rhs_size.width(); + const std::unique_ptr lhs_params(m_settings->getPageParams(lhs_page)); + const std::unique_ptr rhs_params(m_settings->getPageParams(rhs_page)); + + QSizeF lhs_size; + if (lhs_params) { + const Margins margins(lhs_params->hardMarginsMM()); + lhs_size = lhs_params->contentSizeMM(); + lhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); + } + QSizeF rhs_size; + if (rhs_params) { + const Margins margins(rhs_params->hardMarginsMM()); + rhs_size = rhs_params->contentSizeMM(); + rhs_size += QSizeF(margins.left() + margins.right(), margins.top() + margins.bottom()); + } + + const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); + const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); + + if (lhs_valid != rhs_valid) { + // Invalid (unknown) sizes go to the back. + return lhs_valid; + } + + return lhs_size.width() < rhs_size.width(); } // OrderByWidthProvider::precedes } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/OrderByWidthProvider.h b/filters/page_layout/OrderByWidthProvider.h index 7e350b0b5..f1ad34be0 100644 --- a/filters/page_layout/OrderByWidthProvider.h +++ b/filters/page_layout/OrderByWidthProvider.h @@ -19,22 +19,22 @@ #ifndef PAGE_LAYOUT_ORDER_BY_WIDTH_PROVIDER_H_ #define PAGE_LAYOUT_ORDER_BY_WIDTH_PROVIDER_H_ +#include "PageOrderProvider.h" #include "Settings.h" #include "intrusive_ptr.h" -#include "PageOrderProvider.h" namespace page_layout { class OrderByWidthProvider : public PageOrderProvider { -public: - explicit OrderByWidthProvider(intrusive_ptr settings); + public: + explicit OrderByWidthProvider(intrusive_ptr settings); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_settings; }; } // namespace page_layout #endif diff --git a/filters/page_layout/Params.cpp b/filters/page_layout/Params.cpp index 1d7fb4f87..14ebc19ec 100644 --- a/filters/page_layout/Params.cpp +++ b/filters/page_layout/Params.cpp @@ -27,58 +27,56 @@ Params::Params(const Margins& hard_margins_mm, const QSizeF& content_size_mm, const Alignment& alignment, const bool auto_margins) - : m_hardMarginsMM(hard_margins_mm), - m_pageRect(page_rect), - m_contentRect(content_rect), - m_contentSizeMM(content_size_mm), - m_alignment(alignment), - m_autoMargins(auto_margins) { -} + : m_hardMarginsMM(hard_margins_mm), + m_pageRect(page_rect), + m_contentRect(content_rect), + m_contentSizeMM(content_size_mm), + m_alignment(alignment), + m_autoMargins(auto_margins) {} Params::Params(const QDomElement& el) - : m_hardMarginsMM(XmlUnmarshaller::margins(el.namedItem("hardMarginsMM").toElement())), - m_pageRect(XmlUnmarshaller::rectF(el.namedItem("pageRect").toElement())), - m_contentRect(XmlUnmarshaller::rectF(el.namedItem("contentRect").toElement())), - m_contentSizeMM(XmlUnmarshaller::sizeF(el.namedItem("contentSizeMM").toElement())), - m_alignment(el.namedItem("alignment").toElement()), - m_autoMargins(el.attribute("autoMargins") == "1") { -} + : m_hardMarginsMM(XmlUnmarshaller::margins(el.namedItem("hardMarginsMM").toElement())), + m_pageRect(XmlUnmarshaller::rectF(el.namedItem("pageRect").toElement())), + m_contentRect(XmlUnmarshaller::rectF(el.namedItem("contentRect").toElement())), + m_contentSizeMM(XmlUnmarshaller::sizeF(el.namedItem("contentSizeMM").toElement())), + m_alignment(el.namedItem("alignment").toElement()), + m_autoMargins(el.attribute("autoMargins") == "1") {} QDomElement Params::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); + XmlMarshaller marshaller(doc); - QDomElement el(doc.createElement(name)); - el.appendChild(marshaller.margins(m_hardMarginsMM, "hardMarginsMM")); - el.appendChild(marshaller.rectF(m_pageRect, "pageRect")); - el.appendChild(marshaller.rectF(m_contentRect, "contentRect")); - el.appendChild(marshaller.sizeF(m_contentSizeMM, "contentSizeMM")); - el.appendChild(m_alignment.toXml(doc, "alignment")); - el.setAttribute("autoMargins", m_autoMargins ? "1" : "0"); + QDomElement el(doc.createElement(name)); + el.appendChild(marshaller.margins(m_hardMarginsMM, "hardMarginsMM")); + el.appendChild(marshaller.rectF(m_pageRect, "pageRect")); + el.appendChild(marshaller.rectF(m_contentRect, "contentRect")); + el.appendChild(marshaller.sizeF(m_contentSizeMM, "contentSizeMM")); + el.appendChild(m_alignment.toXml(doc, "alignment")); + el.setAttribute("autoMargins", m_autoMargins ? "1" : "0"); - return el; + return el; } const Margins& Params::hardMarginsMM() const { - return m_hardMarginsMM; + return m_hardMarginsMM; } const QRectF& Params::contentRect() const { - return m_contentRect; + return m_contentRect; } const QRectF& Params::pageRect() const { - return m_pageRect; + return m_pageRect; } const QSizeF& Params::contentSizeMM() const { - return m_contentSizeMM; + return m_contentSizeMM; } const Alignment& Params::alignment() const { - return m_alignment; + return m_alignment; } bool Params::isAutoMarginsEnabled() const { - return m_autoMargins; + return m_autoMargins; } } // namespace page_layout diff --git a/filters/page_layout/Params.h b/filters/page_layout/Params.h index e156fefb0..a2c8deb19 100644 --- a/filters/page_layout/Params.h +++ b/filters/page_layout/Params.h @@ -19,10 +19,10 @@ #ifndef PAGE_LAYOUT_PARAMS_H_ #define PAGE_LAYOUT_PARAMS_H_ -#include "Margins.h" -#include "Alignment.h" -#include #include +#include +#include "Alignment.h" +#include "Margins.h" class QDomDocument; class QDomElement; @@ -30,38 +30,38 @@ class QString; namespace page_layout { class Params { - // Member-wise copying is OK. -public: - Params(const Margins& hard_margins_mm, - const QRectF& page_rect, - const QRectF& content_rect, - const QSizeF& content_size_mm, - const Alignment& alignment, - bool auto_margins); + // Member-wise copying is OK. + public: + Params(const Margins& hard_margins_mm, + const QRectF& page_rect, + const QRectF& content_rect, + const QSizeF& content_size_mm, + const Alignment& alignment, + bool auto_margins); - explicit Params(const QDomElement& el); + explicit Params(const QDomElement& el); - const Margins& hardMarginsMM() const; + const Margins& hardMarginsMM() const; - const QRectF& contentRect() const; + const QRectF& contentRect() const; - const QRectF& pageRect() const; + const QRectF& pageRect() const; - const QSizeF& contentSizeMM() const; + const QSizeF& contentSizeMM() const; - const Alignment& alignment() const; + const Alignment& alignment() const; - bool isAutoMarginsEnabled() const; + bool isAutoMarginsEnabled() const; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - Margins m_hardMarginsMM; - QRectF m_contentRect; - QRectF m_pageRect; - QSizeF m_contentSizeMM; - Alignment m_alignment; - bool m_autoMargins; + private: + Margins m_hardMarginsMM; + QRectF m_contentRect; + QRectF m_pageRect; + QSizeF m_contentSizeMM; + Alignment m_alignment; + bool m_autoMargins; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_PARAMS_H_ diff --git a/filters/page_layout/Settings.cpp b/filters/page_layout/Settings.cpp index df87fde4b..e967950fc 100644 --- a/filters/page_layout/Settings.cpp +++ b/filters/page_layout/Settings.cpp @@ -17,21 +17,21 @@ */ #include "Settings.h" -#include "PageId.h" -#include "PageSequence.h" -#include "Params.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" +#include #include #include -#include +#include +#include +#include #include #include -#include -#include -#include +#include #include -#include +#include "AbstractRelinker.h" +#include "PageId.h" +#include "PageSequence.h" +#include "Params.h" +#include "RelinkablePath.h" using namespace ::boost; @@ -39,220 +39,215 @@ using namespace ::boost::multi_index; namespace page_layout { class Settings::Item { -public: - PageId pageId; - Margins hardMarginsMM; - QRectF pageRect; - QRectF contentRect; - QSizeF contentSizeMM; - Alignment alignment; - bool autoMargins; + public: + PageId pageId; + Margins hardMarginsMM; + QRectF pageRect; + QRectF contentRect; + QSizeF contentSizeMM; + Alignment alignment; + bool autoMargins; - Item(const PageId& page_id, - const Margins& hard_margins_mm, - const QRectF& page_rect, - const QRectF& content_rect, - const QSizeF& content_size_mm, - const Alignment& alignment, - bool auto_margins); + Item(const PageId& page_id, + const Margins& hard_margins_mm, + const QRectF& page_rect, + const QRectF& content_rect, + const QSizeF& content_size_mm, + const Alignment& alignment, + bool auto_margins); - double hardWidthMM() const; + double hardWidthMM() const; - double hardHeightMM() const; + double hardHeightMM() const; - double influenceHardWidthMM() const; + double influenceHardWidthMM() const; - double influenceHardHeightMM() const; + double influenceHardHeightMM() const; - bool alignedWithOthers() const { - return !alignment.isNull(); - } + bool alignedWithOthers() const { return !alignment.isNull(); } }; class Settings::ModifyMargins { -public: - ModifyMargins(const Margins& margins_mm, const bool auto_margins) - : m_marginsMM(margins_mm), m_autoMargins(auto_margins) { - } - - void operator()(Item& item) { - item.hardMarginsMM = m_marginsMM; - item.autoMargins = m_autoMargins; - } - -private: - Margins m_marginsMM; - bool m_autoMargins; + public: + ModifyMargins(const Margins& margins_mm, const bool auto_margins) + : m_marginsMM(margins_mm), m_autoMargins(auto_margins) {} + + void operator()(Item& item) { + item.hardMarginsMM = m_marginsMM; + item.autoMargins = m_autoMargins; + } + + private: + Margins m_marginsMM; + bool m_autoMargins; }; class Settings::ModifyAlignment { -public: - explicit ModifyAlignment(const Alignment& alignment) : m_alignment(alignment) { - } + public: + explicit ModifyAlignment(const Alignment& alignment) : m_alignment(alignment) {} - void operator()(Item& item) { - item.alignment = m_alignment; - } + void operator()(Item& item) { item.alignment = m_alignment; } -private: - Alignment m_alignment; + private: + Alignment m_alignment; }; class Settings::ModifyContentSize { -public: - ModifyContentSize(const QSizeF& content_size_mm, const QRectF& content_rect, const QRectF& page_rect) - : m_contentSizeMM(content_size_mm), m_contentRect(content_rect), m_pageRect(page_rect) { - } - - void operator()(Item& item) { - item.contentSizeMM = m_contentSizeMM; - item.contentRect = m_contentRect; - item.pageRect = m_pageRect; - } - -private: - QSizeF m_contentSizeMM; - QRectF m_contentRect; - QRectF m_pageRect; + public: + ModifyContentSize(const QSizeF& content_size_mm, const QRectF& content_rect, const QRectF& page_rect) + : m_contentSizeMM(content_size_mm), m_contentRect(content_rect), m_pageRect(page_rect) {} + + void operator()(Item& item) { + item.contentSizeMM = m_contentSizeMM; + item.contentRect = m_contentRect; + item.pageRect = m_pageRect; + } + + private: + QSizeF m_contentSizeMM; + QRectF m_contentRect; + QRectF m_pageRect; }; class Settings::Impl { -public: - Impl(); + public: + Impl(); - ~Impl(); + ~Impl(); - void clear(); + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - void removePagesMissingFrom(const PageSequence& pages); + void removePagesMissingFrom(const PageSequence& pages); - bool checkEverythingDefined(const PageSequence& pages, const PageId* ignore) const; + bool checkEverythingDefined(const PageSequence& pages, const PageId* ignore) const; - std::unique_ptr getPageParams(const PageId& page_id) const; + std::unique_ptr getPageParams(const PageId& page_id) const; - bool isParamsNull(const PageId& page_id) const; + bool isParamsNull(const PageId& page_id) const; - void setPageParams(const PageId& page_id, const Params& params); + void setPageParams(const PageId& page_id, const Params& params); - Params updateContentSizeAndGetParams(const PageId& page_id, - const QRectF& page_rect, - const QRectF& content_rect, - const QSizeF& content_size_mm, - QSizeF* agg_hard_size_before, - QSizeF* agg_hard_size_after); + Params updateContentSizeAndGetParams(const PageId& page_id, + const QRectF& page_rect, + const QRectF& content_rect, + const QSizeF& content_size_mm, + QSizeF* agg_hard_size_before, + QSizeF* agg_hard_size_after); - const QRectF& updateAggregateContentRect(); + const QRectF& updateAggregateContentRect(); - const QRectF& getAggregateContentRect() { - return m_aggregateContentRect; - } + const QRectF& getAggregateContentRect() { return m_aggregateContentRect; } - void setAggregateContentRect(const QRectF& aggregateContentRect) { - m_aggregateContentRect = aggregateContentRect; - } + void setAggregateContentRect(const QRectF& aggregateContentRect) { m_aggregateContentRect = aggregateContentRect; } + + Margins getHardMarginsMM(const PageId& page_id) const; + + void setHardMarginsMM(const PageId& page_id, const Margins& margins_mm); - Margins getHardMarginsMM(const PageId& page_id) const; + Alignment getPageAlignment(const PageId& page_id) const; - void setHardMarginsMM(const PageId& page_id, const Margins& margins_mm); + AggregateSizeChanged setPageAlignment(const PageId& page_id, const Alignment& alignment); - Alignment getPageAlignment(const PageId& page_id) const; + AggregateSizeChanged setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm); - AggregateSizeChanged setPageAlignment(const PageId& page_id, const Alignment& alignment); + void invalidateContentSize(const PageId& page_id); - AggregateSizeChanged setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm); + QSizeF getAggregateHardSizeMM() const; - void invalidateContentSize(const PageId& page_id); + QSizeF getAggregateHardSizeMMLocked() const; - QSizeF getAggregateHardSizeMM() const; + QSizeF getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const; - QSizeF getAggregateHardSizeMMLocked() const; + bool isPageAutoMarginsEnabled(const PageId& page_id); - QSizeF getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const; + void setPageAutoMarginsEnabled(const PageId& page_id, bool state); - bool isPageAutoMarginsEnabled(const PageId& page_id); + const DeviationProvider& deviationProvider() const; - void setPageAutoMarginsEnabled(const PageId& page_id, bool state); + std::vector& guides(); - const DeviationProvider& deviationProvider() const; + bool isShowingMiddleRectEnabled() const; -private: - class SequencedTag; - class DescWidthTag; + void enableShowingMiddleRect(bool state); - class DescHeightTag; + private: + class SequencedTag; + class DescWidthTag; + class DescHeightTag; - typedef multi_index_container< - Item, - indexed_by>, - sequenced>, - ordered_non_unique, - // ORDER BY alignedWithOthers DESC, hardWidthMM DESC - composite_key, - const_mem_fun>, - composite_key_compare, std::greater>>, - ordered_non_unique, - // ORDER BY alignedWithOthers DESC, hardHeightMM DESC - composite_key, - const_mem_fun>, - composite_key_compare, std::greater>>>> - Container; + typedef multi_index_container< + Item, + indexed_by>, + sequenced>, + ordered_non_unique, + // ORDER BY alignedWithOthers DESC, hardWidthMM DESC + composite_key, + const_mem_fun>, + composite_key_compare, std::greater>>, + ordered_non_unique, + // ORDER BY alignedWithOthers DESC, hardHeightMM DESC + composite_key, + const_mem_fun>, + composite_key_compare, std::greater>>>> + Container; - typedef Container::index::type UnorderedItems; - typedef Container::index::type DescWidthOrder; - typedef Container::index::type DescHeightOrder; + typedef Container::index::type UnorderedItems; + typedef Container::index::type DescWidthOrder; + typedef Container::index::type DescHeightOrder; - mutable QMutex m_mutex; - Container m_items; - UnorderedItems& m_unorderedItems; - DescWidthOrder& m_descWidthOrder; - DescHeightOrder& m_descHeightOrder; - const QRectF m_invalidRect; - const QSizeF m_invalidSize; - const Margins m_defaultHardMarginsMM; - const Alignment m_defaultAlignment; - QRectF m_aggregateContentRect; - const bool m_autoMarginsDefault; - DeviationProvider m_deviationProvider; + mutable QMutex m_mutex; + Container m_items; + UnorderedItems& m_unorderedItems; + DescWidthOrder& m_descWidthOrder; + DescHeightOrder& m_descHeightOrder; + const QRectF m_invalidRect; + const QSizeF m_invalidSize; + const Margins m_defaultHardMarginsMM; + const Alignment m_defaultAlignment; + QRectF m_aggregateContentRect; + const bool m_autoMarginsDefault; + DeviationProvider m_deviationProvider; + std::vector m_guides; + bool m_showMiddleRect; }; /*=============================== Settings ==================================*/ -Settings::Settings() : m_ptrImpl(new Impl()) { -} +Settings::Settings() : m_impl(new Impl()) {} Settings::~Settings() = default; void Settings::clear() { - return m_ptrImpl->clear(); + return m_impl->clear(); } void Settings::performRelinking(const AbstractRelinker& relinker) { - m_ptrImpl->performRelinking(relinker); + m_impl->performRelinking(relinker); } void Settings::removePagesMissingFrom(const PageSequence& pages) { - m_ptrImpl->removePagesMissingFrom(pages); + m_impl->removePagesMissingFrom(pages); } bool Settings::checkEverythingDefined(const PageSequence& pages, const PageId* ignore) const { - return m_ptrImpl->checkEverythingDefined(pages, ignore); + return m_impl->checkEverythingDefined(pages, ignore); } std::unique_ptr Settings::getPageParams(const PageId& page_id) const { - return m_ptrImpl->getPageParams(page_id); + return m_impl->getPageParams(page_id); } void Settings::setPageParams(const PageId& page_id, const Params& params) { - return m_ptrImpl->setPageParams(page_id, params); + return m_impl->setPageParams(page_id, params); } Params Settings::updateContentSizeAndGetParams(const PageId& page_id, @@ -261,70 +256,82 @@ Params Settings::updateContentSizeAndGetParams(const PageId& page_id, const QSizeF& content_size_mm, QSizeF* agg_hard_size_before, QSizeF* agg_hard_size_after) { - return m_ptrImpl->updateContentSizeAndGetParams(page_id, page_rect, content_rect, content_size_mm, - agg_hard_size_before, agg_hard_size_after); + return m_impl->updateContentSizeAndGetParams(page_id, page_rect, content_rect, content_size_mm, agg_hard_size_before, + agg_hard_size_after); } const QRectF& Settings::updateAggregateContentRect() { - return m_ptrImpl->updateAggregateContentRect(); + return m_impl->updateAggregateContentRect(); } const QRectF& Settings::getAggregateContentRect() { - return m_ptrImpl->getAggregateContentRect(); + return m_impl->getAggregateContentRect(); } void Settings::setAggregateContentRect(const QRectF& contentRect) { - m_ptrImpl->setAggregateContentRect(contentRect); + m_impl->setAggregateContentRect(contentRect); } Margins Settings::getHardMarginsMM(const PageId& page_id) const { - return m_ptrImpl->getHardMarginsMM(page_id); + return m_impl->getHardMarginsMM(page_id); } void Settings::setHardMarginsMM(const PageId& page_id, const Margins& margins_mm) { - m_ptrImpl->setHardMarginsMM(page_id, margins_mm); + m_impl->setHardMarginsMM(page_id, margins_mm); } Alignment Settings::getPageAlignment(const PageId& page_id) const { - return m_ptrImpl->getPageAlignment(page_id); + return m_impl->getPageAlignment(page_id); } Settings::AggregateSizeChanged Settings::setPageAlignment(const PageId& page_id, const Alignment& alignment) { - return m_ptrImpl->setPageAlignment(page_id, alignment); + return m_impl->setPageAlignment(page_id, alignment); } Settings::AggregateSizeChanged Settings::setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm) { - return m_ptrImpl->setContentSizeMM(page_id, content_size_mm); + return m_impl->setContentSizeMM(page_id, content_size_mm); } void Settings::invalidateContentSize(const PageId& page_id) { - return m_ptrImpl->invalidateContentSize(page_id); + return m_impl->invalidateContentSize(page_id); } QSizeF Settings::getAggregateHardSizeMM() const { - return m_ptrImpl->getAggregateHardSizeMM(); + return m_impl->getAggregateHardSizeMM(); } QSizeF Settings::getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const { - return m_ptrImpl->getAggregateHardSizeMM(page_id, hard_size_mm, alignment); + return m_impl->getAggregateHardSizeMM(page_id, hard_size_mm, alignment); } bool Settings::isPageAutoMarginsEnabled(const PageId& page_id) { - return m_ptrImpl->isPageAutoMarginsEnabled(page_id); + return m_impl->isPageAutoMarginsEnabled(page_id); } void Settings::setPageAutoMarginsEnabled(const PageId& page_id, const bool state) { - return m_ptrImpl->setPageAutoMarginsEnabled(page_id, state); + return m_impl->setPageAutoMarginsEnabled(page_id, state); } bool Settings::isParamsNull(const PageId& page_id) const { - return m_ptrImpl->isParamsNull(page_id); + return m_impl->isParamsNull(page_id); } const DeviationProvider& Settings::deviationProvider() const { - return m_ptrImpl->deviationProvider(); + return m_impl->deviationProvider(); +} + +std::vector& Settings::guides() { + return m_impl->guides(); +} + +bool Settings::isShowingMiddleRectEnabled() const { + return m_impl->isShowingMiddleRectEnabled(); +} + +void Settings::enableShowingMiddleRect(const bool state) { + m_impl->enableShowingMiddleRect(state); } /*============================== Settings::Item =============================*/ @@ -336,149 +343,149 @@ Settings::Item::Item(const PageId& page_id, const QSizeF& content_size_mm, const Alignment& alignment, const bool auto_margins) - : pageId(page_id), - hardMarginsMM(hard_margins_mm), - pageRect(page_rect), - contentRect(content_rect), - contentSizeMM(content_size_mm), - alignment(alignment), - autoMargins(auto_margins) { -} + : pageId(page_id), + hardMarginsMM(hard_margins_mm), + pageRect(page_rect), + contentRect(content_rect), + contentSizeMM(content_size_mm), + alignment(alignment), + autoMargins(auto_margins) {} double Settings::Item::hardWidthMM() const { - return contentSizeMM.width() + hardMarginsMM.left() + hardMarginsMM.right(); + return contentSizeMM.width() + hardMarginsMM.left() + hardMarginsMM.right(); } double Settings::Item::hardHeightMM() const { - return contentSizeMM.height() + hardMarginsMM.top() + hardMarginsMM.bottom(); + return contentSizeMM.height() + hardMarginsMM.top() + hardMarginsMM.bottom(); } double Settings::Item::influenceHardWidthMM() const { - return alignment.isNull() ? 0.0 : hardWidthMM(); + return alignment.isNull() ? 0.0 : hardWidthMM(); } double Settings::Item::influenceHardHeightMM() const { - return alignment.isNull() ? 0.0 : hardHeightMM(); + return alignment.isNull() ? 0.0 : hardHeightMM(); } /*============================= Settings::Impl ==============================*/ Settings::Impl::Impl() - : m_items(), - m_unorderedItems(m_items.get()), - m_descWidthOrder(m_items.get()), - m_descHeightOrder(m_items.get()), - m_invalidRect(), - m_invalidSize(), - m_defaultHardMarginsMM(Margins(10.0, 5.0, 10.0, 5.0)), - m_defaultAlignment(Alignment::TOP, Alignment::HCENTER), - m_autoMarginsDefault(false) { - m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { - auto it(m_items.find(pageId)); - if (it != m_items.end()) { - if (it->alignment.isNull()) { - return std::sqrt(it->hardWidthMM() * it->hardHeightMM() / 4 / 25.4); - } else { - return .0; - } - } else { - return .0; - }; - }); + : m_items(), + m_unorderedItems(m_items.get()), + m_descWidthOrder(m_items.get()), + m_descHeightOrder(m_items.get()), + m_invalidRect(), + m_invalidSize(), + m_defaultHardMarginsMM(Margins(10.0, 5.0, 10.0, 5.0)), + m_defaultAlignment(Alignment::TOP, Alignment::HCENTER), + m_autoMarginsDefault(false), + m_showMiddleRect(true) { + m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { + auto it(m_items.find(pageId)); + if (it != m_items.end()) { + if (it->alignment.isNull()) { + return std::sqrt(it->hardWidthMM() * it->hardHeightMM() / 4 / 25.4); + } else { + return .0; + } + } else { + return .0; + }; + }); } Settings::Impl::~Impl() = default; void Settings::Impl::clear() { - const QMutexLocker locker(&m_mutex); - m_items.clear(); - m_deviationProvider.clear(); + const QMutexLocker locker(&m_mutex); + m_items.clear(); + m_deviationProvider.clear(); } void Settings::Impl::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); - Container new_items; - - for (const Item& item : m_unorderedItems) { - const RelinkablePath old_path(item.pageId.imageId().filePath(), RelinkablePath::File); - Item new_item(item); - new_item.pageId.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_items.insert(new_item); - } + QMutexLocker locker(&m_mutex); + Container new_items; - m_items.swap(new_items); + for (const Item& item : m_unorderedItems) { + const RelinkablePath old_path(item.pageId.imageId().filePath(), RelinkablePath::File); + Item new_item(item); + new_item.pageId.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_items.insert(new_item); + } - m_deviationProvider.clear(); - for (const Item& item : m_unorderedItems) { - m_deviationProvider.addOrUpdate(item.pageId); - } + m_items.swap(new_items); + + m_deviationProvider.clear(); + for (const Item& item : m_unorderedItems) { + m_deviationProvider.addOrUpdate(item.pageId); + } } void Settings::Impl::removePagesMissingFrom(const PageSequence& pages) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - std::vector sorted_pages; + std::vector sorted_pages; - sorted_pages.reserve(pages.numPages()); - for (const PageInfo& page : pages) { - sorted_pages.push_back(page.id()); - } - std::sort(sorted_pages.begin(), sorted_pages.end()); - - UnorderedItems::const_iterator it(m_unorderedItems.begin()); - const UnorderedItems::const_iterator end(m_unorderedItems.end()); - while (it != end) { - if (std::binary_search(sorted_pages.begin(), sorted_pages.end(), it->pageId)) { - ++it; - } else { - m_deviationProvider.remove(it->pageId); - m_unorderedItems.erase(it++); - } + sorted_pages.reserve(pages.numPages()); + for (const PageInfo& page : pages) { + sorted_pages.push_back(page.id()); + } + std::sort(sorted_pages.begin(), sorted_pages.end()); + + UnorderedItems::const_iterator it(m_unorderedItems.begin()); + const UnorderedItems::const_iterator end(m_unorderedItems.end()); + while (it != end) { + if (std::binary_search(sorted_pages.begin(), sorted_pages.end(), it->pageId)) { + ++it; + } else { + m_deviationProvider.remove(it->pageId); + m_unorderedItems.erase(it++); } + } } bool Settings::Impl::checkEverythingDefined(const PageSequence& pages, const PageId* ignore) const { - const QMutexLocker locker(&m_mutex); - - for (const PageInfo& page_info : pages) { - if (ignore && (*ignore == page_info.id())) { - continue; - } - const Container::iterator it(m_items.find(page_info.id())); - if ((it == m_items.end()) || !it->contentSizeMM.isValid()) { - return false; - } + const QMutexLocker locker(&m_mutex); + + for (const PageInfo& page_info : pages) { + if (ignore && (*ignore == page_info.id())) { + continue; } + const Container::iterator it(m_items.find(page_info.id())); + if ((it == m_items.end()) || !it->contentSizeMM.isValid()) { + return false; + } + } - return true; + return true; } std::unique_ptr Settings::Impl::getPageParams(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.find(page_id)); - if (it == m_items.end()) { - return nullptr; - } + const Container::iterator it(m_items.find(page_id)); + if (it == m_items.end()) { + return nullptr; + } - return std::make_unique(it->hardMarginsMM, it->pageRect, it->contentRect, it->contentSizeMM, it->alignment, - it->autoMargins); + return std::make_unique(it->hardMarginsMM, it->pageRect, it->contentRect, it->contentSizeMM, it->alignment, + it->autoMargins); } void Settings::Impl::setPageParams(const PageId& page_id, const Params& params) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Item new_item(page_id, params.hardMarginsMM(), params.pageRect(), params.contentRect(), - params.contentSizeMM(), params.alignment(), params.isAutoMarginsEnabled()); + const Item new_item(page_id, params.hardMarginsMM(), params.pageRect(), params.contentRect(), params.contentSizeMM(), + params.alignment(), params.isAutoMarginsEnabled()); - const Container::iterator it(m_items.lower_bound(page_id)); - if ((it == m_items.end()) || (page_id < it->pageId)) { - m_items.insert(it, new_item); - } else { - m_items.replace(it, new_item); - } + const Container::iterator it(m_items.lower_bound(page_id)); + if ((it == m_items.end()) || (page_id < it->pageId)) { + m_items.insert(it, new_item); + } else { + m_items.replace(it, new_item); + } - m_deviationProvider.addOrUpdate(page_id); + m_deviationProvider.addOrUpdate(page_id); } Params Settings::Impl::updateContentSizeAndGetParams(const PageId& page_id, @@ -487,257 +494,269 @@ Params Settings::Impl::updateContentSizeAndGetParams(const PageId& page_id, const QSizeF& content_size_mm, QSizeF* agg_hard_size_before, QSizeF* agg_hard_size_after) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - if (agg_hard_size_before) { - *agg_hard_size_before = getAggregateHardSizeMMLocked(); - } + if (agg_hard_size_before) { + *agg_hard_size_before = getAggregateHardSizeMMLocked(); + } - const Container::iterator it(m_items.lower_bound(page_id)); - Container::iterator item_it(it); - if ((it == m_items.end()) || (page_id < it->pageId)) { - const Item item(page_id, m_defaultHardMarginsMM, page_rect, content_rect, content_size_mm, m_defaultAlignment, - m_autoMarginsDefault); - item_it = m_items.insert(it, item); - } else { - m_items.modify(it, ModifyContentSize(content_size_mm, content_rect, page_rect)); - } + const Container::iterator it(m_items.lower_bound(page_id)); + Container::iterator item_it(it); + if ((it == m_items.end()) || (page_id < it->pageId)) { + const Item item(page_id, m_defaultHardMarginsMM, page_rect, content_rect, content_size_mm, m_defaultAlignment, + m_autoMarginsDefault); + item_it = m_items.insert(it, item); + } else { + m_items.modify(it, ModifyContentSize(content_size_mm, content_rect, page_rect)); + } - if (agg_hard_size_after) { - *agg_hard_size_after = getAggregateHardSizeMMLocked(); - } + if (agg_hard_size_after) { + *agg_hard_size_after = getAggregateHardSizeMMLocked(); + } - updateAggregateContentRect(); + updateAggregateContentRect(); - m_deviationProvider.addOrUpdate(page_id); + m_deviationProvider.addOrUpdate(page_id); - return Params(item_it->hardMarginsMM, item_it->pageRect, item_it->contentRect, item_it->contentSizeMM, - item_it->alignment, item_it->autoMargins); + return Params(item_it->hardMarginsMM, item_it->pageRect, item_it->contentRect, item_it->contentSizeMM, + item_it->alignment, item_it->autoMargins); } // Settings::Impl::updateContentSizeAndGetParams const QRectF& Settings::Impl::updateAggregateContentRect() { - Container::iterator it = m_items.begin(); - if (it == m_items.end()) { - return m_aggregateContentRect; - } + Container::iterator it = m_items.begin(); + if (it == m_items.end()) { + return m_aggregateContentRect; + } - m_aggregateContentRect = it->contentRect; - for (; it != m_items.end(); it++) { - if (it->contentRect == m_invalidRect) { - continue; - } - if (it->alignment.isNull()) { - continue; - } + m_aggregateContentRect = it->contentRect; + for (; it != m_items.end(); it++) { + if (it->contentRect == m_invalidRect) { + continue; + } + if (it->alignment.isNull()) { + continue; + } - const QRectF page_rect(it->pageRect); - QRectF content_rect(it->contentRect.translated(-page_rect.x(), -page_rect.y())); + const QRectF page_rect(it->pageRect); + QRectF content_rect(it->contentRect.translated(-page_rect.x(), -page_rect.y())); - m_aggregateContentRect |= content_rect; - } + m_aggregateContentRect |= content_rect; + } - return m_aggregateContentRect; + return m_aggregateContentRect; } // Settings::Impl::updateContentRect Margins Settings::Impl::getHardMarginsMM(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.find(page_id)); - if (it == m_items.end()) { - return m_defaultHardMarginsMM; - } else { - return it->hardMarginsMM; - } + const Container::iterator it(m_items.find(page_id)); + if (it == m_items.end()) { + return m_defaultHardMarginsMM; + } else { + return it->hardMarginsMM; + } } void Settings::Impl::setHardMarginsMM(const PageId& page_id, const Margins& margins_mm) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.lower_bound(page_id)); - if ((it == m_items.end()) || (page_id < it->pageId)) { - const Item item(page_id, margins_mm, m_invalidRect, m_invalidRect, m_invalidSize, m_defaultAlignment, - m_autoMarginsDefault); - m_items.insert(it, item); - } else { - m_items.modify(it, ModifyMargins(margins_mm, it->autoMargins)); - } + const Container::iterator it(m_items.lower_bound(page_id)); + if ((it == m_items.end()) || (page_id < it->pageId)) { + const Item item(page_id, margins_mm, m_invalidRect, m_invalidRect, m_invalidSize, m_defaultAlignment, + m_autoMarginsDefault); + m_items.insert(it, item); + } else { + m_items.modify(it, ModifyMargins(margins_mm, it->autoMargins)); + } - m_deviationProvider.addOrUpdate(page_id); + m_deviationProvider.addOrUpdate(page_id); } Alignment Settings::Impl::getPageAlignment(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.find(page_id)); - if (it == m_items.end()) { - return m_defaultAlignment; - } else { - return it->alignment; - } + const Container::iterator it(m_items.find(page_id)); + if (it == m_items.end()) { + return m_defaultAlignment; + } else { + return it->alignment; + } } Settings::AggregateSizeChanged Settings::Impl::setPageAlignment(const PageId& page_id, const Alignment& alignment) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const QSizeF agg_size_before(getAggregateHardSizeMMLocked()); + const QSizeF agg_size_before(getAggregateHardSizeMMLocked()); - const Container::iterator it(m_items.lower_bound(page_id)); - if ((it == m_items.end()) || (page_id < it->pageId)) { - const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, m_invalidSize, alignment, - m_autoMarginsDefault); - m_items.insert(it, item); - } else { - if (alignment.isNull() != it->alignment.isNull()) { - updateAggregateContentRect(); - } - - m_items.modify(it, ModifyAlignment(alignment)); + const Container::iterator it(m_items.lower_bound(page_id)); + if ((it == m_items.end()) || (page_id < it->pageId)) { + const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, m_invalidSize, alignment, + m_autoMarginsDefault); + m_items.insert(it, item); + } else { + if (alignment.isNull() != it->alignment.isNull()) { + updateAggregateContentRect(); } - m_deviationProvider.addOrUpdate(page_id); + m_items.modify(it, ModifyAlignment(alignment)); + } - const QSizeF agg_size_after(getAggregateHardSizeMMLocked()); - if (agg_size_before == agg_size_after) { - return AGGREGATE_SIZE_UNCHANGED; - } else { - return AGGREGATE_SIZE_CHANGED; - } + m_deviationProvider.addOrUpdate(page_id); + + const QSizeF agg_size_after(getAggregateHardSizeMMLocked()); + if (agg_size_before == agg_size_after) { + return AGGREGATE_SIZE_UNCHANGED; + } else { + return AGGREGATE_SIZE_CHANGED; + } } Settings::AggregateSizeChanged Settings::Impl::setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const QSizeF agg_size_before(getAggregateHardSizeMMLocked()); + const QSizeF agg_size_before(getAggregateHardSizeMMLocked()); - const Container::iterator it(m_items.lower_bound(page_id)); - if ((it == m_items.end()) || (page_id < it->pageId)) { - const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, content_size_mm, - m_defaultAlignment, m_autoMarginsDefault); - m_items.insert(it, item); - } else { - m_items.modify(it, ModifyContentSize(content_size_mm, m_invalidRect, it->pageRect)); - } + const Container::iterator it(m_items.lower_bound(page_id)); + if ((it == m_items.end()) || (page_id < it->pageId)) { + const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, content_size_mm, m_defaultAlignment, + m_autoMarginsDefault); + m_items.insert(it, item); + } else { + m_items.modify(it, ModifyContentSize(content_size_mm, m_invalidRect, it->pageRect)); + } - m_deviationProvider.addOrUpdate(page_id); + m_deviationProvider.addOrUpdate(page_id); - const QSizeF agg_size_after(getAggregateHardSizeMMLocked()); - if (agg_size_before == agg_size_after) { - return AGGREGATE_SIZE_UNCHANGED; - } else { - return AGGREGATE_SIZE_CHANGED; - } + const QSizeF agg_size_after(getAggregateHardSizeMMLocked()); + if (agg_size_before == agg_size_after) { + return AGGREGATE_SIZE_UNCHANGED; + } else { + return AGGREGATE_SIZE_CHANGED; + } } void Settings::Impl::invalidateContentSize(const PageId& page_id) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.find(page_id)); - if (it != m_items.end()) { - m_items.modify(it, ModifyContentSize(m_invalidSize, m_invalidRect, it->pageRect)); - } + const Container::iterator it(m_items.find(page_id)); + if (it != m_items.end()) { + m_items.modify(it, ModifyContentSize(m_invalidSize, m_invalidRect, it->pageRect)); + } - m_deviationProvider.addOrUpdate(page_id); + m_deviationProvider.addOrUpdate(page_id); } QSizeF Settings::Impl::getAggregateHardSizeMM() const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - return getAggregateHardSizeMMLocked(); + return getAggregateHardSizeMMLocked(); } QSizeF Settings::Impl::getAggregateHardSizeMMLocked() const { - if (m_items.empty()) { - return QSizeF(0.0, 0.0); - } + if (m_items.empty()) { + return QSizeF(0.0, 0.0); + } - const Item& max_width_item = *m_descWidthOrder.begin(); - const Item& max_height_item = *m_descHeightOrder.begin(); + const Item& max_width_item = *m_descWidthOrder.begin(); + const Item& max_height_item = *m_descHeightOrder.begin(); - const double width = max_width_item.influenceHardWidthMM(); - const double height = max_height_item.influenceHardHeightMM(); + const double width = max_width_item.influenceHardWidthMM(); + const double height = max_height_item.influenceHardHeightMM(); - return QSizeF(width, height); + return QSizeF(width, height); } QSizeF Settings::Impl::getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const { - if (alignment.isNull()) { - return getAggregateHardSizeMM(); - } + if (alignment.isNull()) { + return getAggregateHardSizeMM(); + } - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - if (m_items.empty()) { - return QSizeF(0.0, 0.0); - } + if (m_items.empty()) { + return QSizeF(0.0, 0.0); + } + + double width = 0.0; - double width = 0.0; - - { - DescWidthOrder::iterator it(m_descWidthOrder.begin()); - if (it->pageId != page_id) { - width = it->influenceHardWidthMM(); - } else { - ++it; - if (it == m_descWidthOrder.end()) { - width = hard_size_mm.width(); - } else { - width = std::max(hard_size_mm.width(), qreal(it->influenceHardWidthMM())); - } - } + { + DescWidthOrder::iterator it(m_descWidthOrder.begin()); + if (it->pageId != page_id) { + width = it->influenceHardWidthMM(); + } else { + ++it; + if (it == m_descWidthOrder.end()) { + width = hard_size_mm.width(); + } else { + width = std::max(hard_size_mm.width(), qreal(it->influenceHardWidthMM())); + } } + } - double height = 0.0; - - { - DescHeightOrder::iterator it(m_descHeightOrder.begin()); - if (it->pageId != page_id) { - height = it->influenceHardHeightMM(); - } else { - ++it; - if (it == m_descHeightOrder.end()) { - height = hard_size_mm.height(); - } else { - height = std::max(hard_size_mm.height(), qreal(it->influenceHardHeightMM())); - } - } + double height = 0.0; + + { + DescHeightOrder::iterator it(m_descHeightOrder.begin()); + if (it->pageId != page_id) { + height = it->influenceHardHeightMM(); + } else { + ++it; + if (it == m_descHeightOrder.end()) { + height = hard_size_mm.height(); + } else { + height = std::max(hard_size_mm.height(), qreal(it->influenceHardHeightMM())); + } } + } - return QSizeF(width, height); + return QSizeF(width, height); } // Settings::Impl::getAggregateHardSizeMM bool Settings::Impl::isPageAutoMarginsEnabled(const PageId& page_id) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.find(page_id)); - if (it == m_items.end()) { - return m_autoMarginsDefault; - } else { - return it->autoMargins; - } + const Container::iterator it(m_items.find(page_id)); + if (it == m_items.end()) { + return m_autoMarginsDefault; + } else { + return it->autoMargins; + } } void Settings::Impl::setPageAutoMarginsEnabled(const PageId& page_id, const bool state) { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - const Container::iterator it(m_items.lower_bound(page_id)); - if ((it == m_items.end()) || (page_id < it->pageId)) { - const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, m_invalidSize, - m_defaultAlignment, state); - m_items.insert(it, item); - } else { - m_items.modify(it, ModifyMargins(it->hardMarginsMM, state)); - } + const Container::iterator it(m_items.lower_bound(page_id)); + if ((it == m_items.end()) || (page_id < it->pageId)) { + const Item item(page_id, m_defaultHardMarginsMM, m_invalidRect, m_invalidRect, m_invalidSize, m_defaultAlignment, + state); + m_items.insert(it, item); + } else { + m_items.modify(it, ModifyMargins(it->hardMarginsMM, state)); + } } bool Settings::Impl::isParamsNull(const PageId& page_id) const { - const QMutexLocker locker(&m_mutex); + const QMutexLocker locker(&m_mutex); - return (m_items.find(page_id) == m_items.end()); + return (m_items.find(page_id) == m_items.end()); } const DeviationProvider& Settings::Impl::deviationProvider() const { - return m_deviationProvider; + return m_deviationProvider; +} + +std::vector& Settings::Impl::guides() { + return m_guides; +} + +bool Settings::Impl::isShowingMiddleRectEnabled() const { + return m_showMiddleRect; +} + +void Settings::Impl::enableShowingMiddleRect(const bool state) { + m_showMiddleRect = state; } } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/Settings.h b/filters/page_layout/Settings.h index 0cc64a241..2d9b83657 100644 --- a/filters/page_layout/Settings.h +++ b/filters/page_layout/Settings.h @@ -19,11 +19,12 @@ #ifndef PAGE_LAYOUT_SETTINGS_H_ #define PAGE_LAYOUT_SETTINGS_H_ +#include +#include +#include "Guide.h" +#include "Margins.h" #include "NonCopyable.h" #include "ref_countable.h" -#include "Margins.h" -#include -#include class PageId; class Margins; @@ -37,144 +38,150 @@ class Params; class Alignment; class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) - -public: - enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; - - Settings(); - - ~Settings() override; - - /** - * \brief Removes all stored data. - */ - void clear(); - - void performRelinking(const AbstractRelinker& relinker); - - /** - * \brief Removes all stored data for pages that are not in the provided list. - */ - void removePagesMissingFrom(const PageSequence& pages); - - /** - * \brief Check that we have all the essential parameters for every - * page in the list. - * - * This check is used to allow of forbid going to the output stage. - * \param pages The list of pages to check. - * \param ignore The page to be ignored by the check. Optional. - */ - bool checkEverythingDefined(const PageSequence& pages, const PageId* ignore = nullptr) const; - - /** - * \brief Get all page parameters at once. - * - * May return a null unique_ptr if the specified page is unknown to us. - */ - std::unique_ptr getPageParams(const PageId& page_id) const; - - bool isParamsNull(const PageId& page_id) const; - - /** - * \brief Set all page parameters at once. - */ - void setPageParams(const PageId& page_id, const Params& params); - - /** - * \brief Updates content size and returns all parameters at once. - */ - Params updateContentSizeAndGetParams(const PageId& page_id, - const QRectF& page_rect, - const QRectF& content_rect, - const QSizeF& content_size_mm, - QSizeF* agg_hard_size_before = nullptr, - QSizeF* agg_hard_size_after = nullptr); - - const QRectF& updateAggregateContentRect(); - - const QRectF& getAggregateContentRect(); - - void setAggregateContentRect(const QRectF& contentRect); - - /** - * \brief Returns the hard margins for the specified page. - * - * Hard margins are margins that will be there no matter what. - * Soft margins are those added to extend the page to match its - * size with other pages. - * \par - * If no margins were assigned to the specified page, the default - * margins are returned. - */ - Margins getHardMarginsMM(const PageId& page_id) const; - - /** - * \brief Sets hard margins for the specified page. - * - * Hard margins are margins that will be there no matter what. - * Soft margins are those added to extend the page to match its - * size with other pages. - */ - void setHardMarginsMM(const PageId& page_id, const Margins& margins_mm); - - /** - * \brief Returns the alignment for the specified page. - * - * Alignments affect the distribution of soft margins. - * \par - * If no alignment was specified, the default alignment is returned, - * which is "center vertically and horizontally". - */ - Alignment getPageAlignment(const PageId& page_id) const; - - /** - * \brief Sets alignment for the specified page. - * - * Alignments affect the distribution of soft margins and whether this - * page's size affects others and vice versa. - */ - AggregateSizeChanged setPageAlignment(const PageId& page_id, const Alignment& alignment); - - /** - * \brief Sets content size in millimeters for the specified page. - * - * The content size comes from the "Select Content" filter. - */ - AggregateSizeChanged setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm); - - void invalidateContentSize(const PageId& page_id); - - /** - * \brief Returns the aggregate (max width + max height) hard page size. - */ - QSizeF getAggregateHardSizeMM() const; - - /** - * \brief Same as getAggregateHardSizeMM(), but assumes a specified - * size and alignment for a specified page. - * - * This function doesn't modify anything, it just pretends that - * the size and alignment of a specified page have changed. - */ - QSizeF getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const; - - bool isPageAutoMarginsEnabled(const PageId& page_id); - - void setPageAutoMarginsEnabled(const PageId& page_id, bool state); - - const DeviationProvider& deviationProvider() const; - -private: - class Impl; - class Item; - class ModifyMargins; - class ModifyAlignment; - - class ModifyContentSize; - - std::unique_ptr m_ptrImpl; + DECLARE_NON_COPYABLE(Settings) + + public: + enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; + + Settings(); + + ~Settings() override; + + /** + * \brief Removes all stored data. + */ + void clear(); + + void performRelinking(const AbstractRelinker& relinker); + + /** + * \brief Removes all stored data for pages that are not in the provided list. + */ + void removePagesMissingFrom(const PageSequence& pages); + + /** + * \brief Check that we have all the essential parameters for every + * page in the list. + * + * This check is used to allow of forbid going to the output stage. + * \param pages The list of pages to check. + * \param ignore The page to be ignored by the check. Optional. + */ + bool checkEverythingDefined(const PageSequence& pages, const PageId* ignore = nullptr) const; + + /** + * \brief Get all page parameters at once. + * + * May return a null unique_ptr if the specified page is unknown to us. + */ + std::unique_ptr getPageParams(const PageId& page_id) const; + + bool isParamsNull(const PageId& page_id) const; + + /** + * \brief Set all page parameters at once. + */ + void setPageParams(const PageId& page_id, const Params& params); + + /** + * \brief Updates content size and returns all parameters at once. + */ + Params updateContentSizeAndGetParams(const PageId& page_id, + const QRectF& page_rect, + const QRectF& content_rect, + const QSizeF& content_size_mm, + QSizeF* agg_hard_size_before = nullptr, + QSizeF* agg_hard_size_after = nullptr); + + const QRectF& updateAggregateContentRect(); + + const QRectF& getAggregateContentRect(); + + void setAggregateContentRect(const QRectF& contentRect); + + /** + * \brief Returns the hard margins for the specified page. + * + * Hard margins are margins that will be there no matter what. + * Soft margins are those added to extend the page to match its + * size with other pages. + * \par + * If no margins were assigned to the specified page, the default + * margins are returned. + */ + Margins getHardMarginsMM(const PageId& page_id) const; + + /** + * \brief Sets hard margins for the specified page. + * + * Hard margins are margins that will be there no matter what. + * Soft margins are those added to extend the page to match its + * size with other pages. + */ + void setHardMarginsMM(const PageId& page_id, const Margins& margins_mm); + + /** + * \brief Returns the alignment for the specified page. + * + * Alignments affect the distribution of soft margins. + * \par + * If no alignment was specified, the default alignment is returned, + * which is "center vertically and horizontally". + */ + Alignment getPageAlignment(const PageId& page_id) const; + + /** + * \brief Sets alignment for the specified page. + * + * Alignments affect the distribution of soft margins and whether this + * page's size affects others and vice versa. + */ + AggregateSizeChanged setPageAlignment(const PageId& page_id, const Alignment& alignment); + + /** + * \brief Sets content size in millimeters for the specified page. + * + * The content size comes from the "Select Content" filter. + */ + AggregateSizeChanged setContentSizeMM(const PageId& page_id, const QSizeF& content_size_mm); + + void invalidateContentSize(const PageId& page_id); + + /** + * \brief Returns the aggregate (max width + max height) hard page size. + */ + QSizeF getAggregateHardSizeMM() const; + + /** + * \brief Same as getAggregateHardSizeMM(), but assumes a specified + * size and alignment for a specified page. + * + * This function doesn't modify anything, it just pretends that + * the size and alignment of a specified page have changed. + */ + QSizeF getAggregateHardSizeMM(const PageId& page_id, const QSizeF& hard_size_mm, const Alignment& alignment) const; + + bool isPageAutoMarginsEnabled(const PageId& page_id); + + void setPageAutoMarginsEnabled(const PageId& page_id, bool state); + + const DeviationProvider& deviationProvider() const; + + std::vector& guides(); + + bool isShowingMiddleRectEnabled() const; + + void enableShowingMiddleRect(bool state); + + private: + class Impl; + class Item; + class ModifyMargins; + class ModifyAlignment; + + class ModifyContentSize; + + std::unique_ptr m_impl; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_SETTINGS_H_ diff --git a/filters/page_layout/Task.cpp b/filters/page_layout/Task.cpp index ccedc78a7..8130190ad 100644 --- a/filters/page_layout/Task.cpp +++ b/filters/page_layout/Task.cpp @@ -16,49 +16,51 @@ along with this program. If not, see . */ -#include -#include #include "Task.h" +#include +#include +#include "Dpm.h" #include "Filter.h" +#include "FilterData.h" +#include "FilterUiInterface.h" +#include "ImageView.h" #include "OptionsWidget.h" -#include "Settings.h" #include "Params.h" -#include "Utils.h" -#include "FilterUiInterface.h" +#include "Settings.h" #include "TaskStatus.h" -#include "FilterData.h" -#include "ImageView.h" +#include "Utils.h" #include "filters/output/Task.h" -#include "Dpm.h" + +using namespace imageproc; namespace page_layout { class Task::UiUpdater : public FilterResult { -public: - UiUpdater(intrusive_ptr filter, - intrusive_ptr settings, - const PageId& page_id, - const QImage& image, - const ImageTransformation& xform, - const QRectF& adapted_content_rect, - bool agg_size_changed, - bool batch); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrSettings; - PageId m_pageId; - QImage m_image; - QImage m_downscaledImage; - ImageTransformation m_xform; - QRectF m_adaptedContentRect; - bool m_aggSizeChanged; - bool m_batchProcessing; + public: + UiUpdater(intrusive_ptr filter, + intrusive_ptr settings, + const PageId& page_id, + const QImage& image, + const ImageTransformation& xform, + const GrayImage& gray_image, + const QRectF& adapted_content_rect, + bool agg_size_changed, + bool batch); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + intrusive_ptr m_settings; + PageId m_pageId; + QImage m_image; + QImage m_downscaledImage; + GrayImage m_grayImage; + ImageTransformation m_xform; + QRectF m_adaptedContentRect; + bool m_aggSizeChanged; + bool m_batchProcessing; }; @@ -68,12 +70,11 @@ Task::Task(intrusive_ptr filter, const PageId& page_id, bool batch, bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrNextTask(std::move(next_task)), - m_ptrSettings(std::move(settings)), - m_pageId(page_id), - m_batchProcessing(batch) { -} + : m_filter(std::move(filter)), + m_nextTask(std::move(next_task)), + m_settings(std::move(settings)), + m_pageId(page_id), + m_batchProcessing(batch) {} Task::~Task() = default; @@ -81,46 +82,46 @@ FilterResultPtr Task::process(const TaskStatus& status, const FilterData& data, const QRectF& page_rect, const QRectF& content_rect) { - status.throwIfCancelled(); + status.throwIfCancelled(); - const QSizeF content_size_mm(Utils::calcRectSizeMM(data.xform(), content_rect)); + const QSizeF content_size_mm(Utils::calcRectSizeMM(data.xform(), content_rect)); - if (m_ptrSettings->isPageAutoMarginsEnabled(m_pageId)) { - const Margins& margins_mm = Utils::calcMarginsMM(data.xform(), page_rect, content_rect); - m_ptrSettings->setHardMarginsMM(m_pageId, margins_mm); - } + if (m_settings->isPageAutoMarginsEnabled(m_pageId)) { + const Margins& margins_mm = Utils::calcMarginsMM(data.xform(), page_rect, content_rect); + m_settings->setHardMarginsMM(m_pageId, margins_mm); + } - QSizeF agg_hard_size_before; - QSizeF agg_hard_size_after; - const Params params(m_ptrSettings->updateContentSizeAndGetParams(m_pageId, page_rect, content_rect, content_size_mm, - &agg_hard_size_before, &agg_hard_size_after)); + QSizeF agg_hard_size_before; + QSizeF agg_hard_size_after; + const Params params(m_settings->updateContentSizeAndGetParams(m_pageId, page_rect, content_rect, content_size_mm, + &agg_hard_size_before, &agg_hard_size_after)); - const QRectF adapted_content_rect(Utils::adaptContentRect(data.xform(), content_rect)); + const QRectF adapted_content_rect(Utils::adaptContentRect(data.xform(), content_rect)); - if (m_ptrNextTask) { - const QPolygonF content_rect_phys(data.xform().transformBack().map(adapted_content_rect)); - const QPolygonF page_rect_phys(Utils::calcPageRectPhys(data.xform(), content_rect_phys, params, - agg_hard_size_after, - m_ptrSettings->getAggregateContentRect())); + if (m_nextTask) { + const QPolygonF content_rect_phys(data.xform().transformBack().map(adapted_content_rect)); + const QPolygonF page_rect_phys(Utils::calcPageRectPhys(data.xform(), content_rect_phys, params, agg_hard_size_after, + m_settings->getAggregateContentRect())); - ImageTransformation new_xform(data.xform()); - new_xform.setPostCropArea(shiftToRoundedOrigin(new_xform.transform().map(page_rect_phys))); + ImageTransformation new_xform(data.xform()); + new_xform.setPostCropArea(shiftToRoundedOrigin(new_xform.transform().map(page_rect_phys))); - return m_ptrNextTask->process(status, FilterData(data, new_xform), content_rect_phys); - } else { - return make_intrusive(m_ptrFilter, m_ptrSettings, m_pageId, data.origImage(), data.xform(), - adapted_content_rect, agg_hard_size_before != agg_hard_size_after, - m_batchProcessing); - } + return m_nextTask->process(status, FilterData(data, new_xform), content_rect_phys); + } else { + return make_intrusive(m_filter, m_settings, m_pageId, data.origImage(), data.xform(), + data.isBlackOnWhite() ? data.grayImage() : data.grayImage().inverted(), + adapted_content_rect, agg_hard_size_before != agg_hard_size_after, + m_batchProcessing); + } } QPolygonF Task::shiftToRoundedOrigin(const QPolygonF& poly) { - const double x = poly.boundingRect().left(); - const double y = poly.boundingRect().top(); - const double shift_value_x = -(x - std::round(x)); - const double shift_value_y = -(y - std::round(y)); + const double x = poly.boundingRect().left(); + const double y = poly.boundingRect().top(); + const double shift_value_x = -(x - std::round(x)); + const double shift_value_y = -(y - std::round(y)); - return poly.translated(shift_value_x, shift_value_y); + return poly.translated(shift_value_x, shift_value_y); } /*============================ Task::UiUpdater ==========================*/ @@ -130,51 +131,52 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const PageId& page_id, const QImage& image, const ImageTransformation& xform, + const GrayImage& gray_image, const QRectF& adapted_content_rect, const bool agg_size_changed, const bool batch) - : m_ptrFilter(std::move(filter)), - m_ptrSettings(std::move(settings)), - m_pageId(page_id), - m_image(image), - m_downscaledImage(ImageView::createDownscaledImage(image)), - m_xform(xform), - m_adaptedContentRect(adapted_content_rect), - m_aggSizeChanged(agg_size_changed), - m_batchProcessing(batch) { -} + : m_filter(std::move(filter)), + m_settings(std::move(settings)), + m_pageId(page_id), + m_image(image), + m_downscaledImage(ImageView::createDownscaledImage(image)), + m_xform(xform), + m_grayImage(gray_image), + m_adaptedContentRect(adapted_content_rect), + m_aggSizeChanged(agg_size_changed), + m_batchProcessing(batch) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - - if (m_aggSizeChanged) { - ui->invalidateAllThumbnails(); - } else { - ui->invalidateThumbnail(m_pageId); - } - - if (m_batchProcessing) { - return; - } - - auto* view = new ImageView(m_ptrSettings, m_pageId, m_image, m_downscaledImage, m_xform, m_adaptedContentRect, - *opt_widget); - ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP); - - QObject::connect(view, SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, - SIGNAL(invalidateThumbnail(const PageId&))); - QObject::connect(view, SIGNAL(invalidateAllThumbnails()), opt_widget, SIGNAL(invalidateAllThumbnails())); - QObject::connect(view, SIGNAL(marginsSetLocally(const Margins&)), opt_widget, - SLOT(marginsSetExternally(const Margins&))); - QObject::connect(opt_widget, SIGNAL(marginsSetLocally(const Margins&)), view, - SLOT(marginsSetExternally(const Margins&))); - QObject::connect(opt_widget, SIGNAL(topBottomLinkToggled(bool)), view, SLOT(topBottomLinkToggled(bool))); - QObject::connect(opt_widget, SIGNAL(leftRightLinkToggled(bool)), view, SLOT(leftRightLinkToggled(bool))); - QObject::connect(opt_widget, SIGNAL(alignmentChanged(const Alignment&)), view, - SLOT(alignmentChanged(const Alignment&))); - QObject::connect(opt_widget, SIGNAL(aggregateHardSizeChanged()), view, SLOT(aggregateHardSizeChanged())); + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + + if (m_aggSizeChanged) { + ui->invalidateAllThumbnails(); + } else { + ui->invalidateThumbnail(m_pageId); + } + + if (m_batchProcessing) { + return; + } + + auto* view = new ImageView(m_settings, m_pageId, m_image, m_downscaledImage, m_grayImage, m_xform, + m_adaptedContentRect, *opt_widget); + ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP); + + QObject::connect(view, SIGNAL(invalidateThumbnail(const PageId&)), opt_widget, + SIGNAL(invalidateThumbnail(const PageId&))); + QObject::connect(view, SIGNAL(invalidateAllThumbnails()), opt_widget, SIGNAL(invalidateAllThumbnails())); + QObject::connect(view, SIGNAL(marginsSetLocally(const Margins&)), opt_widget, + SLOT(marginsSetExternally(const Margins&))); + QObject::connect(opt_widget, SIGNAL(marginsSetLocally(const Margins&)), view, + SLOT(marginsSetExternally(const Margins&))); + QObject::connect(opt_widget, SIGNAL(topBottomLinkToggled(bool)), view, SLOT(topBottomLinkToggled(bool))); + QObject::connect(opt_widget, SIGNAL(leftRightLinkToggled(bool)), view, SLOT(leftRightLinkToggled(bool))); + QObject::connect(opt_widget, SIGNAL(alignmentChanged(const Alignment&)), view, + SLOT(alignmentChanged(const Alignment&))); + QObject::connect(opt_widget, SIGNAL(aggregateHardSizeChanged()), view, SLOT(aggregateHardSizeChanged())); } // Task::UiUpdater::updateUI } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/Task.h b/filters/page_layout/Task.h index 7d742dc99..808848f5c 100644 --- a/filters/page_layout/Task.h +++ b/filters/page_layout/Task.h @@ -19,11 +19,11 @@ #ifndef PAGE_LAYOUT_TASK_H_ #define PAGE_LAYOUT_TASK_H_ -#include "NonCopyable.h" -#include "ref_countable.h" +#include #include "FilterResult.h" +#include "NonCopyable.h" #include "PageId.h" -#include +#include "ref_countable.h" class TaskStatus; class FilterData; @@ -40,33 +40,33 @@ class Filter; class Settings; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(intrusive_ptr filter, - intrusive_ptr next_task, - intrusive_ptr settings, - const PageId& page_id, - bool batch, - bool debug); + public: + Task(intrusive_ptr filter, + intrusive_ptr next_task, + intrusive_ptr settings, + const PageId& page_id, + bool batch, + bool debug); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, - const FilterData& data, - const QRectF& page_rect, - const QRectF& content_rect); + FilterResultPtr process(const TaskStatus& status, + const FilterData& data, + const QRectF& page_rect, + const QRectF& content_rect); -private: - class UiUpdater; + private: + class UiUpdater; - static QPolygonF shiftToRoundedOrigin(const QPolygonF& poly); + static QPolygonF shiftToRoundedOrigin(const QPolygonF& poly); - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; - PageId m_pageId; - bool m_batchProcessing; + intrusive_ptr m_filter; + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; + PageId m_pageId; + bool m_batchProcessing; }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_TASK_H_ diff --git a/filters/page_layout/Thumbnail.cpp b/filters/page_layout/Thumbnail.cpp index fc1777293..58e71abc3 100644 --- a/filters/page_layout/Thumbnail.cpp +++ b/filters/page_layout/Thumbnail.cpp @@ -17,10 +17,10 @@ */ #include "Thumbnail.h" -#include "Utils.h" -#include "imageproc/PolygonUtils.h" #include #include +#include "Utils.h" +#include "imageproc/PolygonUtils.h" using namespace imageproc; @@ -33,82 +33,82 @@ Thumbnail::Thumbnail(intrusive_ptr thumbnail_cache, const QPolygonF& phys_content_rect, const QRectF& displayArea, bool deviant) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform, displayArea), - m_params(params), - m_virtContentRect(xform.transform().map(phys_content_rect).boundingRect()), - m_virtOuterRect(displayArea), - m_deviant(deviant) { - setExtendedClipArea(true); + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform, displayArea), + m_params(params), + m_virtContentRect(xform.transform().map(phys_content_rect).boundingRect()), + m_virtOuterRect(displayArea), + m_deviant(deviant) { + setExtendedClipArea(true); } void Thumbnail::paintOverImage(QPainter& painter, const QTransform& image_to_display, const QTransform& thumb_to_display) { - // We work in display coordinates because we want to be - // pixel-accurate with what we draw. - painter.setWorldTransform(QTransform()); - - const QTransform virt_to_display(virtToThumb() * thumb_to_display); - - const QRectF inner_rect(virt_to_display.map(m_virtContentRect).boundingRect()); - - // We extend the outer rectangle because otherwise we may get white - // thin lines near the edges due to rounding errors and the lack - // of subpixel accuracy. Doing that is actually OK, because what - // we paint will be clipped anyway. - const QRectF outer_rect(virt_to_display.map(m_virtOuterRect).boundingRect()); - - QPainterPath outer_outline; - outer_outline.addPolygon(outer_rect); - - QPainterPath content_outline; - content_outline.addPolygon(inner_rect); - - painter.setRenderHint(QPainter::Antialiasing, true); - - QColor bg_color; - QColor fg_color; - if (m_params.alignment().isNull()) { - // "Align with other pages" is turned off. - // Different color is useful on a thumbnail list to - // distinguish "safe" pages from potentially problematic ones. - bg_color = QColor(0x58, 0x7f, 0xf4, 70); - fg_color = QColor(0x00, 0x52, 0xff); - } else { - bg_color = QColor(0xbb, 0x00, 0xff, 40); - fg_color = QColor(0xbe, 0x5b, 0xec); - } - - // we round inner rect to check whether content rect is empty and this is just an adapted rect. - const bool isNullContentRect = m_virtContentRect.toRect().isEmpty(); - - // Draw margins. - if (!isNullContentRect) { - painter.fillPath(outer_outline.subtracted(content_outline), bg_color); - } else { - painter.fillPath(outer_outline, bg_color); - } - - QPen pen(fg_color); - pen.setCosmetic(true); - pen.setWidthF(1.0); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - - // inner rect - if (!isNullContentRect) { - painter.drawRect(inner_rect); - } - - // outer rect - if (!m_params.alignment().isNull()) { - pen.setStyle(Qt::DashLine); - } - painter.setPen(pen); - painter.drawRect(outer_rect); - - if (m_deviant) { - paintDeviant(painter); - } + // We work in display coordinates because we want to be + // pixel-accurate with what we draw. + painter.setWorldTransform(QTransform()); + + const QTransform virt_to_display(virtToThumb() * thumb_to_display); + + const QRectF inner_rect(virt_to_display.map(m_virtContentRect).boundingRect()); + + // We extend the outer rectangle because otherwise we may get white + // thin lines near the edges due to rounding errors and the lack + // of subpixel accuracy. Doing that is actually OK, because what + // we paint will be clipped anyway. + const QRectF outer_rect(virt_to_display.map(m_virtOuterRect).boundingRect()); + + QPainterPath outer_outline; + outer_outline.addPolygon(outer_rect); + + QPainterPath content_outline; + content_outline.addPolygon(inner_rect); + + painter.setRenderHint(QPainter::Antialiasing, true); + + QColor bg_color; + QColor fg_color; + if (m_params.alignment().isNull()) { + // "Align with other pages" is turned off. + // Different color is useful on a thumbnail list to + // distinguish "safe" pages from potentially problematic ones. + bg_color = QColor(0x58, 0x7f, 0xf4, 70); + fg_color = QColor(0x00, 0x52, 0xff); + } else { + bg_color = QColor(0xbb, 0x00, 0xff, 40); + fg_color = QColor(0xbe, 0x5b, 0xec); + } + + // we round inner rect to check whether content rect is empty and this is just an adapted rect. + const bool isNullContentRect = m_virtContentRect.toRect().isEmpty(); + + // Draw margins. + if (!isNullContentRect) { + painter.fillPath(outer_outline.subtracted(content_outline), bg_color); + } else { + painter.fillPath(outer_outline, bg_color); + } + + QPen pen(fg_color); + pen.setCosmetic(true); + pen.setWidthF(1.0); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + // inner rect + if (!isNullContentRect) { + painter.drawRect(inner_rect); + } + + // outer rect + if (!m_params.alignment().isNull()) { + pen.setStyle(Qt::DashLine); + } + painter.setPen(pen); + painter.drawRect(outer_rect); + + if (m_deviant) { + paintDeviant(painter); + } } // Thumbnail::paintOverImage } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/Thumbnail.h b/filters/page_layout/Thumbnail.h index 31fcdf587..a687cd629 100644 --- a/filters/page_layout/Thumbnail.h +++ b/filters/page_layout/Thumbnail.h @@ -19,37 +19,37 @@ #ifndef PAGE_LAYOUT_THUMBNAIL_H_ #define PAGE_LAYOUT_THUMBNAIL_H_ -#include "ThumbnailBase.h" -#include "Params.h" +#include +#include #include "ImageTransformation.h" +#include "Params.h" +#include "ThumbnailBase.h" #include "intrusive_ptr.h" -#include -#include class ThumbnailPixmapCache; class ImageId; namespace page_layout { class Thumbnail : public ThumbnailBase { -public: - Thumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const Params& params, - const ImageTransformation& xform, - const QPolygonF& phys_content_rect, - const QRectF& displayArea, - bool deviant); - - void paintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) override; - -private: - Params m_params; - QRectF m_virtContentRect; - QRectF m_virtOuterRect; - bool m_deviant; + public: + Thumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const Params& params, + const ImageTransformation& xform, + const QPolygonF& phys_content_rect, + const QRectF& displayArea, + bool deviant); + + void paintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) override; + + private: + Params m_params; + QRectF m_virtContentRect; + QRectF m_virtOuterRect; + bool m_deviant; }; } // namespace page_layout #endif diff --git a/filters/page_layout/Utils.cpp b/filters/page_layout/Utils.cpp index 465fc71d8..96de000b9 100644 --- a/filters/page_layout/Utils.cpp +++ b/filters/page_layout/Utils.cpp @@ -17,89 +17,89 @@ */ #include "Utils.h" -#include "Margins.h" -#include "Alignment.h" -#include "Params.h" -#include "ImageTransformation.h" -#include #include +#include #include +#include "Alignment.h" +#include "ImageTransformation.h" +#include "Margins.h" +#include "Params.h" namespace page_layout { QRectF Utils::adaptContentRect(const ImageTransformation& xform, const QRectF& content_rect) { - if (!content_rect.isEmpty()) { - return content_rect; - } + if (!content_rect.isEmpty()) { + return content_rect; + } - const QPointF center(xform.resultingRect().center()); - const QPointF delta(0.01, 0.01); + const QPointF center(xform.resultingRect().center()); + const QPointF delta(0.01, 0.01); - return QRectF(center - delta, center + delta); + return QRectF(center - delta, center + delta); } QSizeF Utils::calcRectSizeMM(const ImageTransformation& xform, const QRectF& rect) { - const QTransform virt_to_mm(xform.transformBack() * UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)); + const QTransform virt_to_mm(xform.transformBack() * UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)); - const QLineF hor_line(rect.topLeft(), rect.topRight()); - const QLineF ver_line(rect.topLeft(), rect.bottomLeft()); + const QLineF hor_line(rect.topLeft(), rect.topRight()); + const QLineF ver_line(rect.topLeft(), rect.bottomLeft()); - const double width = virt_to_mm.map(hor_line).length(); - const double height = virt_to_mm.map(ver_line).length(); + const double width = virt_to_mm.map(hor_line).length(); + const double height = virt_to_mm.map(ver_line).length(); - return QSizeF(width, height); + return QSizeF(width, height); } void Utils::extendPolyRectWithMargins(QPolygonF& poly_rect, const Margins& margins) { - const QPointF down_uv(getDownUnitVector(poly_rect)); - const QPointF right_uv(getRightUnitVector(poly_rect)); - - // top-left - poly_rect[0] -= down_uv * margins.top(); - poly_rect[0] -= right_uv * margins.left(); - - // top-right - poly_rect[1] -= down_uv * margins.top(); - poly_rect[1] += right_uv * margins.right(); - - // bottom-right - poly_rect[2] += down_uv * margins.bottom(); - poly_rect[2] += right_uv * margins.right(); - - // bottom-left - poly_rect[3] += down_uv * margins.bottom(); - poly_rect[3] -= right_uv * margins.left(); - - if (poly_rect.size() > 4) { - assert(poly_rect.size() == 5); - // This polygon is closed. - poly_rect[4] = poly_rect[3]; - } + const QPointF down_uv(getDownUnitVector(poly_rect)); + const QPointF right_uv(getRightUnitVector(poly_rect)); + + // top-left + poly_rect[0] -= down_uv * margins.top(); + poly_rect[0] -= right_uv * margins.left(); + + // top-right + poly_rect[1] -= down_uv * margins.top(); + poly_rect[1] += right_uv * margins.right(); + + // bottom-right + poly_rect[2] += down_uv * margins.bottom(); + poly_rect[2] += right_uv * margins.right(); + + // bottom-left + poly_rect[3] += down_uv * margins.bottom(); + poly_rect[3] -= right_uv * margins.left(); + + if (poly_rect.size() > 4) { + assert(poly_rect.size() == 5); + // This polygon is closed. + poly_rect[4] = poly_rect[3]; + } } Margins Utils::calcMarginsMM(const ImageTransformation& xform, const QRectF& page_rect, const QRectF& content_rect) { - const QSizeF content_size_mm(Utils::calcRectSizeMM(xform, content_rect)); + const QSizeF content_size_mm(Utils::calcRectSizeMM(xform, content_rect)); - const QSizeF page_size_mm(Utils::calcRectSizeMM(xform, page_rect)); + const QSizeF page_size_mm(Utils::calcRectSizeMM(xform, page_rect)); - double widthMM = page_size_mm.width() - content_size_mm.width(); - double heightMM = page_size_mm.height() - content_size_mm.height(); + double widthMM = page_size_mm.width() - content_size_mm.width(); + double heightMM = page_size_mm.height() - content_size_mm.height(); - double width = page_rect.width() - content_rect.width(); - double height = page_rect.height() - content_rect.height(); + double width = page_rect.width() - content_rect.width(); + double height = page_rect.height() - content_rect.height(); - auto left = double(content_rect.left() - page_rect.left()); - auto right = double(page_rect.right() - content_rect.right()); - auto top = double(content_rect.top() - page_rect.top()); - auto bottom = double(page_rect.bottom() - content_rect.bottom()); - double hspace = left + right; - double vspace = top + bottom; + auto left = double(content_rect.left() - page_rect.left()); + auto right = double(page_rect.right() - content_rect.right()); + auto top = double(content_rect.top() - page_rect.top()); + auto bottom = double(page_rect.bottom() - content_rect.bottom()); + double hspace = left + right; + double vspace = top + bottom; - double lMM = (hspace < 1.0) ? 0.0 : (left * widthMM / hspace); - double rMM = (hspace < 1.0) ? 0.0 : (right * widthMM / hspace); - double tMM = (vspace < 1.0) ? 0.0 : (top * heightMM / vspace); - double bMM = (vspace < 1.0) ? 0.0 : (bottom * heightMM / vspace); + double lMM = (hspace < 1.0) ? 0.0 : (left * widthMM / hspace); + double rMM = (hspace < 1.0) ? 0.0 : (right * widthMM / hspace); + double tMM = (vspace < 1.0) ? 0.0 : (top * heightMM / vspace); + double bMM = (vspace < 1.0) ? 0.0 : (bottom * heightMM / vspace); - return Margins(lMM, tMM, rMM, bMM); + return Margins(lMM, tMM, rMM, bMM); } Margins Utils::calcSoftMarginsMM(const QSizeF& hard_size_mm, @@ -109,129 +109,128 @@ Margins Utils::calcSoftMarginsMM(const QSizeF& hard_size_mm, const QSizeF& contentSizeMM, const QRectF& agg_content_rect, const QRectF& pageRect) { - if (alignment.isNull()) { - // This means we are not aligning this page with others. - return Margins(); + if (alignment.isNull()) { + // This means we are not aligning this page with others. + return Margins(); + } + + double top = 0.0; + double bottom = 0.0; + double left = 0.0; + double right = 0.0; + + const double delta_width = aggregate_hard_size_mm.width() - hard_size_mm.width(); + const double delta_height = aggregate_hard_size_mm.height() - hard_size_mm.height(); + + double aggLeftBorder = 0.0; + double aggRightBorder = delta_width; + double aggTopBorder = 0.0; + double aggBottomBorder = delta_height; + + Alignment correctedAlignment = alignment; + + if (!contentSizeMM.isEmpty() && !contentRect.isEmpty() && !pageRect.isEmpty() && !agg_content_rect.isEmpty()) { + const double pixelsPerMmHorizontal = contentRect.width() / contentSizeMM.width(); + const double pixelsPerMmVertical = contentRect.height() / contentSizeMM.height(); + + const QSizeF agg_content_size_mm(agg_content_rect.width() / pixelsPerMmHorizontal, + agg_content_rect.height() / pixelsPerMmVertical); + + QRectF correctedContentRect = contentRect.translated(-pageRect.x(), -pageRect.y()); + double content_rect_x_center_in_mm + = ((correctedContentRect.center().x() - agg_content_rect.left()) / agg_content_rect.width()) + * agg_content_size_mm.width(); + double content_rect_y_center_in_mm + = ((correctedContentRect.center().y() - agg_content_rect.top()) / agg_content_rect.height()) + * agg_content_size_mm.height(); + + if (delta_width > 0.1) { + aggLeftBorder = (content_rect_x_center_in_mm - (hard_size_mm.width() / 2)) + - ((agg_content_size_mm.width() - aggregate_hard_size_mm.width()) / 2); + if (aggLeftBorder < 0) { + aggLeftBorder = 0; + } else if (aggLeftBorder > delta_width) { + aggLeftBorder = delta_width; + } + aggRightBorder = delta_width - aggLeftBorder; + } + if (delta_height > 0.1) { + aggTopBorder = (content_rect_y_center_in_mm - (hard_size_mm.height() / 2)) + - ((agg_content_size_mm.height() - aggregate_hard_size_mm.height()) / 2); + if (aggTopBorder < 0) { + aggTopBorder = 0; + } else if (aggTopBorder > delta_height) { + aggTopBorder = delta_height; + } + aggBottomBorder = delta_height - aggTopBorder; } - double top = 0.0; - double bottom = 0.0; - double left = 0.0; - double right = 0.0; - - const double delta_width = aggregate_hard_size_mm.width() - hard_size_mm.width(); - const double delta_height = aggregate_hard_size_mm.height() - hard_size_mm.height(); - - double aggLeftBorder = 0.0; - double aggRightBorder = delta_width; - double aggTopBorder = 0.0; - double aggBottomBorder = delta_height; - - Alignment correctedAlignment = alignment; - - if (!contentSizeMM.isEmpty() && !contentRect.isEmpty() && !pageRect.isEmpty() && !agg_content_rect.isEmpty()) { - const double pixelsPerMmHorizontal = contentRect.width() / contentSizeMM.width(); - const double pixelsPerMmVertical = contentRect.height() / contentSizeMM.height(); - - const QSizeF agg_content_size_mm(agg_content_rect.width() / pixelsPerMmHorizontal, - agg_content_rect.height() / pixelsPerMmVertical); - - QRectF correctedContentRect = contentRect.translated(-pageRect.x(), -pageRect.y()); - double content_rect_x_center_in_mm - = ((correctedContentRect.center().x() - agg_content_rect.left()) / agg_content_rect.width()) - * agg_content_size_mm.width(); - double content_rect_y_center_in_mm - = ((correctedContentRect.center().y() - agg_content_rect.top()) / agg_content_rect.height()) - * agg_content_size_mm.height(); - - if (delta_width > 0.1) { - aggLeftBorder = (content_rect_x_center_in_mm - (hard_size_mm.width() / 2)) - - ((agg_content_size_mm.width() - aggregate_hard_size_mm.width()) / 2); - if (aggLeftBorder < 0) { - aggLeftBorder = 0; - } else if (aggLeftBorder > delta_width) { - aggLeftBorder = delta_width; - } - aggRightBorder = delta_width - aggLeftBorder; + if ((correctedAlignment.horizontal() == Alignment::HAUTO) || (correctedAlignment.vertical() == Alignment::VAUTO)) { + const double goldenRatio = (1 + std::sqrt(5)) / 2; + const double rightGridLine = agg_content_size_mm.width() / goldenRatio; + const double leftGridLine = agg_content_size_mm.width() - rightGridLine; + const double bottomGridLine = agg_content_size_mm.height() / goldenRatio; + const double topGridLine = agg_content_size_mm.height() - bottomGridLine; + + if (correctedAlignment.horizontal() == Alignment::HAUTO) { + if (content_rect_x_center_in_mm < leftGridLine) { + correctedAlignment.setHorizontal(Alignment::LEFT); + } else if (content_rect_x_center_in_mm > rightGridLine) { + correctedAlignment.setHorizontal(Alignment::RIGHT); + } else { + correctedAlignment.setHorizontal(Alignment::HCENTER); } - if (delta_height > 0.1) { - aggTopBorder = (content_rect_y_center_in_mm - (hard_size_mm.height() / 2)) - - ((agg_content_size_mm.height() - aggregate_hard_size_mm.height()) / 2); - if (aggTopBorder < 0) { - aggTopBorder = 0; - } else if (aggTopBorder > delta_height) { - aggTopBorder = delta_height; - } - aggBottomBorder = delta_height - aggTopBorder; - } - - if ((correctedAlignment.horizontal() == Alignment::HAUTO) - || (correctedAlignment.vertical() == Alignment::VAUTO)) { - const double goldenRatio = (1 + std::sqrt(5)) / 2; - const double rightGridLine = agg_content_size_mm.width() / goldenRatio; - const double leftGridLine = agg_content_size_mm.width() - rightGridLine; - const double bottomGridLine = agg_content_size_mm.height() / goldenRatio; - const double topGridLine = agg_content_size_mm.height() - bottomGridLine; - - if (correctedAlignment.horizontal() == Alignment::HAUTO) { - if (content_rect_x_center_in_mm < leftGridLine) { - correctedAlignment.setHorizontal(Alignment::LEFT); - } else if (content_rect_x_center_in_mm > rightGridLine) { - correctedAlignment.setHorizontal(Alignment::RIGHT); - } else { - correctedAlignment.setHorizontal(Alignment::HCENTER); - } - } - - if (correctedAlignment.vertical() == Alignment::VAUTO) { - if (content_rect_y_center_in_mm < topGridLine) { - correctedAlignment.setVertical(Alignment::TOP); - } else if (content_rect_y_center_in_mm > bottomGridLine) { - correctedAlignment.setVertical(Alignment::BOTTOM); - } else { - correctedAlignment.setVertical(Alignment::VCENTER); - } - } + } + + if (correctedAlignment.vertical() == Alignment::VAUTO) { + if (content_rect_y_center_in_mm < topGridLine) { + correctedAlignment.setVertical(Alignment::TOP); + } else if (content_rect_y_center_in_mm > bottomGridLine) { + correctedAlignment.setVertical(Alignment::BOTTOM); + } else { + correctedAlignment.setVertical(Alignment::VCENTER); } + } } - - if (delta_width > 0.0) { - switch (correctedAlignment.horizontal()) { - case Alignment::LEFT: - right = delta_width; - break; - case Alignment::HCENTER: - left = right = 0.5 * delta_width; - break; - case Alignment::RIGHT: - left = delta_width; - break; - default: - left = aggLeftBorder; - right = aggRightBorder; - break; - } + } + + if (delta_width > 0.0) { + switch (correctedAlignment.horizontal()) { + case Alignment::LEFT: + right = delta_width; + break; + case Alignment::HCENTER: + left = right = 0.5 * delta_width; + break; + case Alignment::RIGHT: + left = delta_width; + break; + default: + left = aggLeftBorder; + right = aggRightBorder; + break; } - - if (delta_height > 0.0) { - switch (correctedAlignment.vertical()) { - case Alignment::TOP: - bottom = delta_height; - break; - case Alignment::VCENTER: - top = bottom = 0.5 * delta_height; - break; - case Alignment::BOTTOM: - top = delta_height; - break; - default: - top = aggTopBorder; - bottom = aggBottomBorder; - break; - } + } + + if (delta_height > 0.0) { + switch (correctedAlignment.vertical()) { + case Alignment::TOP: + bottom = delta_height; + break; + case Alignment::VCENTER: + top = bottom = 0.5 * delta_height; + break; + case Alignment::BOTTOM: + top = delta_height; + break; + default: + top = aggTopBorder; + bottom = aggBottomBorder; + break; } + } - return Margins(left, top, right, bottom); + return Margins(left, top, right, bottom); } // Utils::calcSoftMarginsMM QPolygonF Utils::calcPageRectPhys(const ImageTransformation& xform, @@ -239,32 +238,32 @@ QPolygonF Utils::calcPageRectPhys(const ImageTransformation& xform, const Params& params, const QSizeF& aggregate_hard_size_mm, const QRectF& agg_content_rect) { - const QTransform pixelsToMmTransform(UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)); + const QTransform pixelsToMmTransform(UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)); - QPolygonF poly_mm(pixelsToMmTransform.map(content_rect_phys)); - extendPolyRectWithMargins(poly_mm, params.hardMarginsMM()); + QPolygonF poly_mm(pixelsToMmTransform.map(content_rect_phys)); + extendPolyRectWithMargins(poly_mm, params.hardMarginsMM()); - const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); - Margins soft_margins_mm(calcSoftMarginsMM(hard_size_mm, aggregate_hard_size_mm, params.alignment(), - params.contentRect(), params.contentSizeMM(), agg_content_rect, - params.pageRect())); + const QSizeF hard_size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length()); + Margins soft_margins_mm(calcSoftMarginsMM(hard_size_mm, aggregate_hard_size_mm, params.alignment(), + params.contentRect(), params.contentSizeMM(), agg_content_rect, + params.pageRect())); - extendPolyRectWithMargins(poly_mm, soft_margins_mm); + extendPolyRectWithMargins(poly_mm, soft_margins_mm); - return pixelsToMmTransform.inverted().map(poly_mm); + return pixelsToMmTransform.inverted().map(poly_mm); } QPointF Utils::getRightUnitVector(const QPolygonF& poly_rect) { - const QPointF top_left(poly_rect[0]); - const QPointF top_right(poly_rect[1]); + const QPointF top_left(poly_rect[0]); + const QPointF top_right(poly_rect[1]); - return QLineF(top_left, top_right).unitVector().p2() - top_left; + return QLineF(top_left, top_right).unitVector().p2() - top_left; } QPointF Utils::getDownUnitVector(const QPolygonF& poly_rect) { - const QPointF top_left(poly_rect[0]); - const QPointF bottom_left(poly_rect[3]); + const QPointF top_left(poly_rect[0]); + const QPointF bottom_left(poly_rect[3]); - return QLineF(top_left, bottom_left).unitVector().p2() - top_left; + return QLineF(top_left, bottom_left).unitVector().p2() - top_left; } } // namespace page_layout \ No newline at end of file diff --git a/filters/page_layout/Utils.h b/filters/page_layout/Utils.h index 2f093e6c8..6550b3650 100644 --- a/filters/page_layout/Utils.h +++ b/filters/page_layout/Utils.h @@ -31,70 +31,70 @@ class Alignment; class Params; class Utils { -public: - /** - * \brief Replace an empty content rectangle with a tiny centered one. - * - * If the content rectangle is empty (no content on the page), it - * creates various problems for us. So, we replace it with a tiny - * non-empty rectangle centered in the page's crop area, which - * is retrieved from the ImageTransformation. - */ - static QRectF adaptContentRect(const ImageTransformation& xform, const QRectF& content_rect); + public: + /** + * \brief Replace an empty content rectangle with a tiny centered one. + * + * If the content rectangle is empty (no content on the page), it + * creates various problems for us. So, we replace it with a tiny + * non-empty rectangle centered in the page's crop area, which + * is retrieved from the ImageTransformation. + */ + static QRectF adaptContentRect(const ImageTransformation& xform, const QRectF& content_rect); - /** - * \brief Calculates the physical size of a rectangle in a transformed space. - */ - static QSizeF calcRectSizeMM(const ImageTransformation& xform, const QRectF& rect); + /** + * \brief Calculates the physical size of a rectangle in a transformed space. + */ + static QSizeF calcRectSizeMM(const ImageTransformation& xform, const QRectF& rect); - /** - * \brief Extend a rectangle transformed into a polygon with margins. - * - * The first edge of the polygon is considered to be the top edge, the - * next one is right, and so on. The polygon must have 4 or 5 vertices - * (unclosed vs closed polygon). It must have 90 degree angles and - * must not be empty. - */ - static void extendPolyRectWithMargins(QPolygonF& poly_rect, const Margins& margins); + /** + * \brief Extend a rectangle transformed into a polygon with margins. + * + * The first edge of the polygon is considered to be the top edge, the + * next one is right, and so on. The polygon must have 4 or 5 vertices + * (unclosed vs closed polygon). It must have 90 degree angles and + * must not be empty. + */ + static void extendPolyRectWithMargins(QPolygonF& poly_rect, const Margins& margins); - /** - * \brief Calculates margins to extend hard_size_mm to aggregate_hard_size_mm. - * - * \param hard_size_mm Source size in millimeters. - * \param aggregate_hard_size_mm Target size in millimeters. - * \param alignment Determines how exactly to grow the size. - * \return Non-negative margins that extend \p hard_size_mm to - * \p aggregate_hard_size_mm. - */ - static Margins calcSoftMarginsMM(const QSizeF& hard_size_mm, - const QSizeF& aggregate_hard_size_mm, - const Alignment& alignment, - const QRectF& contentRect, - const QSizeF& contentSizeMM, - const QRectF& agg_content_rect, - const QRectF& pageRect); + /** + * \brief Calculates margins to extend hard_size_mm to aggregate_hard_size_mm. + * + * \param hard_size_mm Source size in millimeters. + * \param aggregate_hard_size_mm Target size in millimeters. + * \param alignment Determines how exactly to grow the size. + * \return Non-negative margins that extend \p hard_size_mm to + * \p aggregate_hard_size_mm. + */ + static Margins calcSoftMarginsMM(const QSizeF& hard_size_mm, + const QSizeF& aggregate_hard_size_mm, + const Alignment& alignment, + const QRectF& contentRect, + const QSizeF& contentSizeMM, + const QRectF& agg_content_rect, + const QRectF& pageRect); - static Margins calcMarginsMM(const ImageTransformation& xform, const QRectF& page_rect, const QRectF& content_rect); + static Margins calcMarginsMM(const ImageTransformation& xform, const QRectF& page_rect, const QRectF& content_rect); - /** - * \brief Calculates the page rect (content + hard margins + soft margins) - * - * \param xform Transformations applied to image. - * \param content_rect_phys Content rectangle in transformed coordinates. - * \param params Margins, aligment and other parameters. - * \param aggregate_hard_size_mm Maximum width and height across all pages. - * \return Page rectangle (as a polygon) in physical image coordinates. - */ - static QPolygonF calcPageRectPhys(const ImageTransformation& xform, - const QPolygonF& content_rect_phys, - const Params& params, - const QSizeF& aggregate_hard_size_mm, - const QRectF& agg_content_rect); + /** + * \brief Calculates the page rect (content + hard margins + soft margins) + * + * \param xform Transformations applied to image. + * \param content_rect_phys Content rectangle in transformed coordinates. + * \param params Margins, aligment and other parameters. + * \param aggregate_hard_size_mm Maximum width and height across all pages. + * \return Page rectangle (as a polygon) in physical image coordinates. + */ + static QPolygonF calcPageRectPhys(const ImageTransformation& xform, + const QPolygonF& content_rect_phys, + const Params& params, + const QSizeF& aggregate_hard_size_mm, + const QRectF& agg_content_rect); -private: - static QPointF getRightUnitVector(const QPolygonF& poly_rect); + private: + static QPointF getRightUnitVector(const QPolygonF& poly_rect); - static QPointF getDownUnitVector(const QPolygonF& poly_rect); + static QPointF getDownUnitVector(const QPolygonF& poly_rect); }; } // namespace page_layout #endif // ifndef PAGE_LAYOUT_UTILS_H_ diff --git a/filters/page_layout/ui/PageLayoutOptionsWidget.ui b/filters/page_layout/ui/PageLayoutOptionsWidget.ui index 301bd6b2e..b12ba7634 100644 --- a/filters/page_layout/ui/PageLayoutOptionsWidget.ui +++ b/filters/page_layout/ui/PageLayoutOptionsWidget.ui @@ -6,804 +6,870 @@ 0 0 - 217 - 553 + 232 + 804 Form - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - QFrame::NoFrame + + + Margins - - Qt::ScrollBarAlwaysOff + + Qt::AlignCenter - - true + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Auto Margins + + + false + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + 24 + 48 + + + + ... + + + + :/icons/stock-vchain-broken-24.png + :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png + + + + 9 + 24 + + + + true + + + + + + + Top + + + + + + + Right + + + + + + + Left + + + + + + + + 24 + 48 + + + + ... + + + + :/icons/stock-vchain-broken-24.png + :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png + + + + 9 + 24 + + + + false + + + true + + + + + + + 1 + + + 9999.000000000000000 + + + + + + + 1 + + + 9999.000000000000000 + + + + + + + 1 + + + 9999.000000000000000 + + + + + + + Bottom + + + + + + + 1 + + + 9999.000000000000000 + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + Alignment - - - - 0 - 0 - 217 - 553 - + + Qt::AlignCenter + + + + 9 - - - - - Margins - - + + 9 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + 1 + - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Auto Margins - - - false - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + Auto + - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - 24 - 48 - - - - ... - - - - :/icons/stock-vchain-broken-24.png - :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png - - - - 9 - 24 - - - - true - - - - - - - Top - - - - - - - Right - - - - - - - Left - - - - - - - - 24 - 48 - - - - ... - - - - :/icons/stock-vchain-broken-24.png - :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png - - - - 9 - 24 - - - - false - - - true - - - - - - - 1 - - - 9999.000000000000000 - - - - - - - 1 - - - 9999.000000000000000 - - - - - - - 1 - - - 9999.000000000000000 - - - - - - - Bottom - - - - - - - 1 - - - 9999.000000000000000 - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + Manual + - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Apply To ... - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + Original + - - - - - - - Alignment - - - - 9 + + + + + + Qt::Horizontal - - 9 + + + 1 + 1 + - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - 1 - - - - Auto - - - - - Manual - - - - - Original - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - Auto aligning - - - - 6 + + + + + + + + Auto aligning + + + Qt::AlignCenter + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + Qt::Horizontal - - 6 + + + 1 + 1 + - - 6 + + + + + + Enable horizontal - - 6 + + true - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Enable horizontal - - - true - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Enable vertical - - - true - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + +
+ + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Enable vertical + + + true + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + +
+ + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Match size with other pages + + + true + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + 16 + + + + + ... + + + + :/icons/stock-gravity-north-west-24.png:/icons/stock-gravity-north-west-24.png + + + + 24 + 24 + + + + true + + + true + - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Match size with other pages - - - true - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + + + ... + + + + :/icons/stock-gravity-north-24.png:/icons/stock-gravity-north-24.png + + + + 24 + 24 + + + + true + + + true + + - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - 16 - - - - - ... - - - - :/icons/stock-gravity-north-west-24.png:/icons/stock-gravity-north-west-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-north-24.png:/icons/stock-gravity-north-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-north-east-24.png:/icons/stock-gravity-north-east-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-west-24.png:/icons/stock-gravity-west-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-center-24.png:/icons/stock-center-24.png - - - - 24 - 24 - - - - true - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-east-24.png:/icons/stock-gravity-east-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-south-west-24.png:/icons/stock-gravity-south-west-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-south-24.png:/icons/stock-gravity-south-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - ... - - - - :/icons/stock-gravity-south-east-24.png:/icons/stock-gravity-south-east-24.png - - - - 24 - 24 - - - - true - - - true - - - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + + + ... + + + + :/icons/stock-gravity-north-east-24.png:/icons/stock-gravity-north-east-24.png + + + + 24 + 24 + + + + true + + + true + + - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - Apply To ... - - - - - - - Qt::Horizontal - - - - 1 - 1 - - - - - + + + + ... + + + + :/icons/stock-gravity-west-24.png:/icons/stock-gravity-west-24.png + + + + 24 + 24 + + + + true + + + true + + + + + + + ... + + + + :/icons/stock-center-24.png:/icons/stock-center-24.png + + + + 24 + 24 + + + + true + + + true + + + true + + + + + + + ... + + + + :/icons/stock-gravity-east-24.png:/icons/stock-gravity-east-24.png + + + + 24 + 24 + + + + true + + + true + + + + + + + ... + + + + :/icons/stock-gravity-south-west-24.png:/icons/stock-gravity-south-west-24.png + + + + 24 + 24 + + + + true + + + true + + + + + + + ... + + + + :/icons/stock-gravity-south-24.png:/icons/stock-gravity-south-24.png + + + + 24 + 24 + + + + true + + + true + + + + + + + ... + + + + :/icons/stock-gravity-south-east-24.png:/icons/stock-gravity-south-east-24.png + + + + 24 + 24 + + + + true + + + true + + - - - - - - Qt::Vertical - - - - 188 - 11 - - - - - - + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + Apply To ... + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + 0 + 0 + + + + Guides Help + + + Qt::AlignCenter + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 0 + 220 + + + + + 0 + 0 + + + + false + + + + + + -1 + + + + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + QTextEdit::AutoNone + + + QTextEdit::WidgetWidth + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> to create/remove guides from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> on a guide to delete that guide from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift+LMB</span><span style=" font-size:7pt;"> - drag the guide under the cursor.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift/Ctrl+LMB</span><span style=" font-size:7pt;"> on the content rectangle - drag the page content. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to restrict moving along the horizontal axis only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for the vertical one. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> for usual dragging.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Double-click</span><span style=" font-size:7pt;"> on content - automatically attach that content to the nearest guide. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to select vertical guides only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for horizontal ones. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> to attach that to both the nearest vertical and horizontal guides.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣</span><span style=" font-size:7pt;"> Use the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> to enable/disable showing the hard margins rectangle.</span></p></body></html> + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Qt::Vertical + + + + 188 + 11 + + + + diff --git a/filters/page_split/CMakeLists.txt b/filters/page_split/CMakeLists.txt index 2768205ba..b59dc4068 100644 --- a/filters/page_split/CMakeLists.txt +++ b/filters/page_split/CMakeLists.txt @@ -1,38 +1,38 @@ -PROJECT("Page Split Filter") +project("Page Split Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - SplitLineObject.h - ImageView.cpp ImageView.h - Thumbnail.cpp Thumbnail.h - Params.cpp Params.h - Dependencies.cpp Dependencies.h - PageLayout.cpp PageLayout.h - PageLayoutEstimator.cpp PageLayoutEstimator.h - VertLineFinder.cpp VertLineFinder.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - SplitModeDialog.cpp SplitModeDialog.h - Settings.cpp Settings.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h - LayoutType.cpp LayoutType.h - UnremoveButton.cpp UnremoveButton.h - OrderBySplitTypeProvider.cpp OrderBySplitTypeProvider.h - PageLayoutAdapter.cpp PageLayoutAdapter.h) - -SOURCE_GROUP("Sources" FILES ${sources}) +set( + sources + SplitLineObject.h + ImageView.cpp ImageView.h + Thumbnail.cpp Thumbnail.h + Params.cpp Params.h + Dependencies.cpp Dependencies.h + PageLayout.cpp PageLayout.h + PageLayoutEstimator.cpp PageLayoutEstimator.h + VertLineFinder.cpp VertLineFinder.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + SplitModeDialog.cpp SplitModeDialog.h + Settings.cpp Settings.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h + LayoutType.cpp LayoutType.h + UnremoveButton.cpp UnremoveButton.h + OrderBySplitTypeProvider.cpp OrderBySplitTypeProvider.h + PageLayoutAdapter.cpp PageLayoutAdapter.h) + +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(page_split STATIC ${sources} ${ui_sources}) +add_library(page_split STATIC ${sources} ${ui_sources}) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) \ No newline at end of file +translation_sources(scantailor ${sources} ${ui_files}) \ No newline at end of file diff --git a/filters/page_split/CacheDrivenTask.cpp b/filters/page_split/CacheDrivenTask.cpp index 6d56751c2..03217eab4 100644 --- a/filters/page_split/CacheDrivenTask.cpp +++ b/filters/page_split/CacheDrivenTask.cpp @@ -19,80 +19,77 @@ #include "CacheDrivenTask.h" #include -#include "Thumbnail.h" #include "IncompleteThumbnail.h" -#include "Settings.h" -#include "ProjectPages.h" #include "PageInfo.h" +#include "PageLayoutAdapter.h" +#include "ProjectPages.h" +#include "Settings.h" +#include "Thumbnail.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filters/deskew/CacheDrivenTask.h" -#include "PageLayoutAdapter.h" namespace page_split { CacheDrivenTask::CacheDrivenTask(intrusive_ptr settings, intrusive_ptr projectPages, intrusive_ptr next_task) - : m_ptrNextTask(std::move(next_task)), - m_ptrSettings(std::move(settings)), - m_projectPages(std::move(projectPages)) { -} + : m_nextTask(std::move(next_task)), m_settings(std::move(settings)), m_projectPages(std::move(projectPages)) {} CacheDrivenTask::~CacheDrivenTask() = default; static ProjectPages::LayoutType toPageLayoutType(const PageLayout& layout) { - switch (layout.type()) { - case PageLayout::SINGLE_PAGE_UNCUT: - case PageLayout::SINGLE_PAGE_CUT: - return ProjectPages::ONE_PAGE_LAYOUT; - case PageLayout::TWO_PAGES: - return ProjectPages::TWO_PAGE_LAYOUT; - } + switch (layout.type()) { + case PageLayout::SINGLE_PAGE_UNCUT: + case PageLayout::SINGLE_PAGE_CUT: + return ProjectPages::ONE_PAGE_LAYOUT; + case PageLayout::TWO_PAGES: + return ProjectPages::TWO_PAGE_LAYOUT; + } - assert(!"Unreachable"); + assert(!"Unreachable"); - return ProjectPages::ONE_PAGE_LAYOUT; + return ProjectPages::ONE_PAGE_LAYOUT; } void CacheDrivenTask::process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform) { - const Settings::Record record(m_ptrSettings->getPageRecord(page_info.imageId())); - - const OrthogonalRotation pre_rotation(xform.preRotation()); - const Dependencies deps(page_info.metadata().size(), pre_rotation, record.combinedLayoutType()); - - const Params* params = record.params(); + const Settings::Record record(m_settings->getPageRecord(page_info.imageId())); - if (!params || !deps.compatibleWith(*params)) { - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); - } - - return; - } + const OrthogonalRotation pre_rotation(xform.preRotation()); + const Dependencies deps(page_info.metadata().size(), pre_rotation, record.combinedLayoutType()); - PageLayout layout(params->pageLayout()); - PageLayoutAdapter::correctPageLayoutType(&layout); - // m_projectPages controls number of pages displayed in thumbnail list - // usually this is set in Task, but if user changed layout with Apply To.. - // and just jumped to next stage - the Task::process isn't invoked for all pages - // so we must additionally ensure here that we display right number of pages. - m_projectPages->setLayoutTypeFor(page_info.id().imageId(), toPageLayoutType(layout)); - - if (m_ptrNextTask) { - ImageTransformation new_xform(xform); - new_xform.setPreCropArea(layout.pageOutline(page_info.id().subPage()).toPolygon()); - m_ptrNextTask->process(page_info, collector, new_xform); - - return; - } + const Params* params = record.params(); + if (!params || !deps.compatibleWith(*params)) { if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr( - new Thumbnail(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, - layout, page_info.leftHalfRemoved(), page_info.rightHalfRemoved()))); + thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); } + + return; + } + + PageLayout layout(params->pageLayout()); + PageLayoutAdapter::correctPageLayoutType(&layout); + // m_projectPages controls number of pages displayed in thumbnail list + // usually this is set in Task, but if user changed layout with Apply To.. + // and just jumped to next stage - the Task::process isn't invoked for all pages + // so we must additionally ensure here that we display right number of pages. + m_projectPages->setLayoutTypeFor(page_info.id().imageId(), toPageLayoutType(layout)); + + if (m_nextTask) { + ImageTransformation new_xform(xform); + new_xform.setPreCropArea(layout.pageOutline(page_info.id().subPage()).toPolygon()); + m_nextTask->process(page_info, collector, new_xform); + + return; + } + + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr( + new Thumbnail(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, layout, + page_info.leftHalfRemoved(), page_info.rightHalfRemoved()))); + } } // CacheDrivenTask::process } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/CacheDrivenTask.h b/filters/page_split/CacheDrivenTask.h index 3b5bdccf5..e05468a0d 100644 --- a/filters/page_split/CacheDrivenTask.h +++ b/filters/page_split/CacheDrivenTask.h @@ -20,8 +20,8 @@ #define PAGE_SPLIT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" -#include "ref_countable.h" #include "intrusive_ptr.h" +#include "ref_countable.h" class QSizeF; class PageInfo; @@ -37,21 +37,21 @@ namespace page_split { class Settings; class CacheDrivenTask : public ref_countable { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr settings, - intrusive_ptr projectPages, - intrusive_ptr next_task); + public: + CacheDrivenTask(intrusive_ptr settings, + intrusive_ptr projectPages, + intrusive_ptr next_task); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); + void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); -private: - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; - intrusive_ptr m_projectPages; + private: + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; + intrusive_ptr m_projectPages; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_CACHEDRIVENTASK_H_ diff --git a/filters/page_split/Dependencies.cpp b/filters/page_split/Dependencies.cpp index a34cd6309..11ec7abf2 100644 --- a/filters/page_split/Dependencies.cpp +++ b/filters/page_split/Dependencies.cpp @@ -22,70 +22,67 @@ #include "XmlUnmarshaller.h" namespace page_split { -Dependencies::Dependencies() : m_layoutType(AUTO_LAYOUT_TYPE) { -} +Dependencies::Dependencies() : m_layoutType(AUTO_LAYOUT_TYPE) {} Dependencies::Dependencies(const QDomElement& el) - : m_imageSize(XmlUnmarshaller::size(el.namedItem("size").toElement())), - m_rotation(XmlUnmarshaller::rotation(el.namedItem("rotation").toElement())), - m_layoutType(layoutTypeFromString(XmlUnmarshaller::string(el.namedItem("layoutType").toElement()))) { -} + : m_imageSize(XmlUnmarshaller::size(el.namedItem("size").toElement())), + m_rotation(XmlUnmarshaller::rotation(el.namedItem("rotation").toElement())), + m_layoutType(layoutTypeFromString(XmlUnmarshaller::string(el.namedItem("layoutType").toElement()))) {} Dependencies::Dependencies(const QSize& image_size, const OrthogonalRotation rotation, const LayoutType layout_type) - : m_imageSize(image_size), m_rotation(rotation), m_layoutType(layout_type) { -} + : m_imageSize(image_size), m_rotation(rotation), m_layoutType(layout_type) {} bool Dependencies::compatibleWith(const Params& params) const { - const Dependencies& deps = params.dependencies(); - - if (m_imageSize != deps.m_imageSize) { - return false; - } - if (m_rotation != deps.m_rotation) { - return false; - } - if (m_layoutType == deps.m_layoutType) { - return true; - } - if (m_layoutType == SINGLE_PAGE_UNCUT) { - // The split line doesn't matter here. - return true; - } - if ((m_layoutType == TWO_PAGES) && (params.splitLineMode() == MODE_MANUAL)) { - // Two pages and a specified split line means we have all the data. - // Note that if layout type was PAGE_PLUS_OFFCUT, we would - // not know if that page is to the left or to the right of the - // split line. - return true; - } + const Dependencies& deps = params.dependencies(); + if (m_imageSize != deps.m_imageSize) { + return false; + } + if (m_rotation != deps.m_rotation) { return false; + } + if (m_layoutType == deps.m_layoutType) { + return true; + } + if (m_layoutType == SINGLE_PAGE_UNCUT) { + // The split line doesn't matter here. + return true; + } + if ((m_layoutType == TWO_PAGES) && (params.splitLineMode() == MODE_MANUAL)) { + // Two pages and a specified split line means we have all the data. + // Note that if layout type was PAGE_PLUS_OFFCUT, we would + // not know if that page is to the left or to the right of the + // split line. + return true; + } + + return false; } QDomElement Dependencies::toXml(QDomDocument& doc, const QString& tag_name) const { - if (isNull()) { - return QDomElement(); - } + if (isNull()) { + return QDomElement(); + } - XmlMarshaller marshaller(doc); + XmlMarshaller marshaller(doc); - QDomElement el(doc.createElement(tag_name)); - el.appendChild(marshaller.rotation(m_rotation, "rotation")); - el.appendChild(marshaller.size(m_imageSize, "size")); - el.appendChild(marshaller.string(layoutTypeToString(m_layoutType), "layoutType")); + QDomElement el(doc.createElement(tag_name)); + el.appendChild(marshaller.rotation(m_rotation, "rotation")); + el.appendChild(marshaller.size(m_imageSize, "size")); + el.appendChild(marshaller.string(layoutTypeToString(m_layoutType), "layoutType")); - return el; + return el; } bool Dependencies::isNull() const { - return m_imageSize.isNull(); + return m_imageSize.isNull(); } const OrthogonalRotation& Dependencies::orientation() const { - return m_rotation; + return m_rotation; } void Dependencies::setLayoutType(LayoutType type) { - m_layoutType = type; + m_layoutType = type; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/Dependencies.h b/filters/page_split/Dependencies.h index b9fb500b9..8f8acafee 100644 --- a/filters/page_split/Dependencies.h +++ b/filters/page_split/Dependencies.h @@ -19,9 +19,9 @@ #ifndef PAGE_SPLIT_DEPENDENCIES_H_ #define PAGE_SPLIT_DEPENDENCIES_H_ -#include "OrthogonalRotation.h" -#include "LayoutType.h" #include +#include "LayoutType.h" +#include "OrthogonalRotation.h" class QString; class QDomDocument; @@ -36,28 +36,28 @@ class Params; * Once dependencies change, the stored page parameters are no longer valid. */ class Dependencies { - // Member-wise copying is OK. -public: - Dependencies(); + // Member-wise copying is OK. + public: + Dependencies(); - explicit Dependencies(const QDomElement& el); + explicit Dependencies(const QDomElement& el); - Dependencies(const QSize& image_size, OrthogonalRotation rotation, LayoutType layout_type); + Dependencies(const QSize& image_size, OrthogonalRotation rotation, LayoutType layout_type); - void setLayoutType(LayoutType type); + void setLayoutType(LayoutType type); - const OrthogonalRotation& orientation() const; + const OrthogonalRotation& orientation() const; - bool compatibleWith(const Params& params) const; + bool compatibleWith(const Params& params) const; - bool isNull() const; + bool isNull() const; - QDomElement toXml(QDomDocument& doc, const QString& tag_name) const; + QDomElement toXml(QDomDocument& doc, const QString& tag_name) const; -private: - QSize m_imageSize; - OrthogonalRotation m_rotation; - LayoutType m_layoutType; + private: + QSize m_imageSize; + OrthogonalRotation m_rotation; + LayoutType m_layoutType; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_DEPENDENCIES_H_ diff --git a/filters/page_split/Filter.cpp b/filters/page_split/Filter.cpp index bf49604ef..c807e758e 100644 --- a/filters/page_split/Filter.cpp +++ b/filters/page_split/Filter.cpp @@ -17,187 +17,185 @@ */ #include "Filter.h" +#include +#include +#include +#include +#include +#include +#include +#include "CacheDrivenTask.h" +#include "CommandLine.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" -#include "Task.h" -#include "Settings.h" +#include "OrderBySplitTypeProvider.h" #include "ProjectPages.h" #include "ProjectReader.h" #include "ProjectWriter.h" -#include "CacheDrivenTask.h" -#include -#include -#include -#include -#include -#include "CommandLine.h" -#include "OrderBySplitTypeProvider.h" -#include -#include +#include "Settings.h" +#include "Task.h" namespace page_split { Filter::Filter(intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor) - : m_ptrPages(std::move(page_sequence)), m_ptrSettings(new Settings), m_selectedPageOrder(0) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, m_ptrPages, page_selection_accessor)); - } - - typedef PageOrderOption::ProviderPtr ProviderPtr; - - const ProviderPtr default_order; - const auto order_by_split_type = make_intrusive(m_ptrSettings); - m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); - m_pageOrderOptions.emplace_back(tr("Order by split type"), order_by_split_type); + : m_pages(std::move(page_sequence)), m_settings(new Settings), m_selectedPageOrder(0) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, m_pages, page_selection_accessor)); + } + + const PageOrderOption::ProviderPtr default_order; + const auto order_by_split_type = make_intrusive(m_settings); + m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); + m_pageOrderOptions.emplace_back(tr("Order by split type"), order_by_split_type); } Filter::~Filter() = default; QString Filter::getName() const { - return QCoreApplication::translate("page_split::Filter", "Split Pages"); + return QCoreApplication::translate("page_split::Filter", "Split Pages"); } PageView Filter::getView() const { - return IMAGE_VIEW; + return IMAGE_VIEW; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) { - m_ptrOptionsWidget->preUpdateUI(page_info.id()); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); + m_optionsWidget->preUpdateUI(page_info.id()); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("page-split")); - filter_el.setAttribute("defaultLayoutType", layoutTypeToString(m_ptrSettings->defaultLayoutType())); + QDomElement filter_el(doc.createElement("page-split")); + filter_el.setAttribute("defaultLayoutType", layoutTypeToString(m_settings->defaultLayoutType())); - writer.enumImages([&](const ImageId& image_id, const int numeric_id) { - this->writeImageSettings(doc, filter_el, image_id, numeric_id); - }); + writer.enumImages([&](const ImageId& image_id, const int numeric_id) { + this->writeImageSettings(doc, filter_el, image_id, numeric_id); + }); - return filter_el; + return filter_el; } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); - - const QDomElement filter_el(filters_el.namedItem("page-split").toElement()); - const QString default_layout_type(filter_el.attribute("defaultLayoutType")); - m_ptrSettings->setLayoutTypeForAllPages(layoutTypeFromString(default_layout_type)); - - const QString image_tag_name("image"); - QDomNode node(filter_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != image_tag_name) { - continue; - } - QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const ImageId image_id(reader.imageId(id)); - if (image_id.isNull()) { - continue; - } - - Settings::UpdateAction update; - - const QString layout_type(el.attribute("layoutType")); - if (!layout_type.isEmpty()) { - update.setLayoutType(layoutTypeFromString(layout_type)); - } - - QDomElement params_el(el.namedItem("params").toElement()); - if (!params_el.isNull()) { - update.setParams(Params(params_el)); - } - - m_ptrSettings->updatePage(image_id, update); + m_settings->clear(); + + const QDomElement filter_el(filters_el.namedItem("page-split").toElement()); + const QString default_layout_type(filter_el.attribute("defaultLayoutType")); + m_settings->setLayoutTypeForAllPages(layoutTypeFromString(default_layout_type)); + + const QString image_tag_name("image"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } -} // Filter::loadSettings + if (node.nodeName() != image_tag_name) { + continue; + } + QDomElement el(node.toElement()); -void Filter::pageOrientationUpdate(const ImageId& image_id, const OrthogonalRotation& orientation) { - const Settings::Record record(m_ptrSettings->getPageRecord(image_id)); + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const ImageId image_id(reader.imageId(id)); + if (image_id.isNull()) { + continue; + } - if (record.layoutType() && (*record.layoutType() != AUTO_LAYOUT_TYPE)) { - // The layout type was set manually, so we don't care about orientation. - return; + Settings::UpdateAction update; + + const QString layout_type(el.attribute("layoutType")); + if (!layout_type.isEmpty()) { + update.setLayoutType(layoutTypeFromString(layout_type)); } - if (record.params() && (record.params()->dependencies().orientation() == orientation)) { - // We've already estimated the number of pages for this orientation. - return; + QDomElement params_el(el.namedItem("params").toElement()); + if (!params_el.isNull()) { + update.setParams(Params(params_el)); } - // Use orientation to update the number of logical pages in an image. - m_ptrPages->autoSetLayoutTypeFor(image_id, orientation); + m_settings->updatePage(image_id, update); + } +} // Filter::loadSettings + +void Filter::pageOrientationUpdate(const ImageId& image_id, const OrthogonalRotation& orientation) { + const Settings::Record record(m_settings->getPageRecord(image_id)); + + if (record.layoutType() && (*record.layoutType() != AUTO_LAYOUT_TYPE)) { + // The layout type was set manually, so we don't care about orientation. + return; + } + + if (record.params() && (record.params()->dependencies().orientation() == orientation)) { + // We've already estimated the number of pages for this orientation. + return; + } + + // Use orientation to update the number of logical pages in an image. + m_pages->autoSetLayoutTypeFor(image_id, orientation); } void Filter::writeImageSettings(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, const int numeric_id) const { - const Settings::Record record(m_ptrSettings->getPageRecord(image_id)); - - QDomElement image_el(doc.createElement("image")); - image_el.setAttribute("id", numeric_id); - if (const LayoutType* layout_type = record.layoutType()) { - image_el.setAttribute("layoutType", layoutTypeToString(*layout_type)); - } - - if (const Params* params = record.params()) { - image_el.appendChild(params->toXml(doc, "params")); - filter_el.appendChild(image_el); - } + const Settings::Record record(m_settings->getPageRecord(image_id)); + + QDomElement image_el(doc.createElement("image")); + image_el.setAttribute("id", numeric_id); + if (const LayoutType* layout_type = record.layoutType()) { + image_el.setAttribute("layoutType", layoutTypeToString(*layout_type)); + } + + if (const Params* params = record.params()) { + image_el.appendChild(params->toXml(doc, "params")); + filter_el.appendChild(image_el); + } } intrusive_ptr Filter::createTask(const PageInfo& page_info, intrusive_ptr next_task, const bool batch_processing, const bool debug) { - return make_intrusive(intrusive_ptr(this), m_ptrSettings, m_ptrPages, std::move(next_task), page_info, - batch_processing, debug); + return make_intrusive(intrusive_ptr(this), m_settings, m_pages, std::move(next_task), page_info, + batch_processing, debug); } intrusive_ptr Filter::createCacheDrivenTask(intrusive_ptr next_task) { - return make_intrusive(m_ptrSettings, m_ptrPages, std::move(next_task)); + return make_intrusive(m_settings, m_pages, std::move(next_task)); } std::vector Filter::pageOrderOptions() const { - return m_pageOrderOptions; + return m_pageOrderOptions; } int Filter::selectedPageOrder() const { - return m_selectedPageOrder; + return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { - assert((unsigned) option < m_pageOrderOptions.size()); - m_selectedPageOrder = option; + assert((unsigned) option < m_pageOrderOptions.size()); + m_selectedPageOrder = option; } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->getPageRecord(page_info.id().imageId()).isNull()) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::PageSplitParams& pageSplitParams = defaultParams.getPageSplitParams(); - - Settings::UpdateAction update; - update.setLayoutType(pageSplitParams.getLayoutType()); - m_ptrSettings->updatePage(page_info.id().imageId(), update); + if (!m_settings->getPageRecord(page_info.id().imageId()).isNull()) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::PageSplitParams& pageSplitParams = defaultParams.getPageSplitParams(); + + Settings::UpdateAction update; + update.setLayoutType(pageSplitParams.getLayoutType()); + m_settings->updatePage(page_info.id().imageId(), update); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); } } // namespace page_split diff --git a/filters/page_split/Filter.h b/filters/page_split/Filter.h index e3fa367a3..762773d21 100644 --- a/filters/page_split/Filter.h +++ b/filters/page_split/Filter.h @@ -19,15 +19,15 @@ #ifndef PAGE_SPLIT_FILTER_H_ #define PAGE_SPLIT_FILTER_H_ -#include "NonCopyable.h" +#include +#include #include "AbstractFilter.h" -#include "PageView.h" -#include "intrusive_ptr.h" #include "FilterResult.h" -#include "SafeDeletingQObjectPtr.h" -#include +#include "NonCopyable.h" #include "PageOrderOption.h" -#include +#include "PageView.h" +#include "SafeDeletingQObjectPtr.h" +#include "intrusive_ptr.h" class ImageId; class PageInfo; @@ -49,53 +49,53 @@ class Settings; class Params; class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) - Q_DECLARE_TR_FUNCTIONS(page_split::Filter) -public: - Filter(intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor); + Q_DECLARE_TR_FUNCTIONS(page_split::Filter) + public: + Filter(intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor); - ~Filter() override; + ~Filter() override; - QString getName() const override; + QString getName() const override; - PageView getView() const override; + PageView getView() const override; - void performRelinking(const AbstractRelinker& relinker) override; + void performRelinking(const AbstractRelinker& relinker) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& wirter, QDomDocument& doc) const override; + QDomElement saveSettings(const ProjectWriter& wirter, QDomDocument& doc) const override; - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void loadDefaultSettings(const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - intrusive_ptr createTask(const PageInfo& page_info, - intrusive_ptr next_task, - bool batch_processing, - bool debug); + intrusive_ptr createTask(const PageInfo& page_info, + intrusive_ptr next_task, + bool batch_processing, + bool debug); - intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); + intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); - OptionsWidget* optionsWidget(); + OptionsWidget* optionsWidget(); - void pageOrientationUpdate(const ImageId& image_id, const OrthogonalRotation& orientation); + void pageOrientationUpdate(const ImageId& image_id, const OrthogonalRotation& orientation); - std::vector pageOrderOptions() const override; + std::vector pageOrderOptions() const override; - int selectedPageOrder() const override; + int selectedPageOrder() const override; - void selectPageOrder(int option) override; + void selectPageOrder(int option) override; -private: - void writeImageSettings(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, int numeric_id) const; + private: + void writeImageSettings(QDomDocument& doc, QDomElement& filter_el, const ImageId& image_id, int numeric_id) const; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; - std::vector m_pageOrderOptions; - int m_selectedPageOrder; + intrusive_ptr m_pages; + intrusive_ptr m_settings; + SafeDeletingQObjectPtr m_optionsWidget; + std::vector m_pageOrderOptions; + int m_selectedPageOrder; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_FILTER_H_ diff --git a/filters/page_split/ImageView.cpp b/filters/page_split/ImageView.cpp index dfdc35a57..30e91f2cf 100644 --- a/filters/page_split/ImageView.cpp +++ b/filters/page_split/ImageView.cpp @@ -17,14 +17,14 @@ */ #include "ImageView.h" -#include "ImageTransformation.h" -#include "ImagePresentation.h" -#include "ProjectPages.h" -#include #include +#include #include #include #include +#include "ImagePresentation.h" +#include "ImageTransformation.h" +#include "ProjectPages.h" namespace page_split { ImageView::ImageView(const QImage& image, @@ -35,174 +35,174 @@ ImageView::ImageView(const QImage& image, const ImageId& image_id, bool left_half_removed, bool right_half_removed) - : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), - m_ptrPages(std::move(pages)), - m_imageId(image_id), - m_leftUnremoveButton(boost::bind(&ImageView::leftPageCenter, this)), - m_rightUnremoveButton(boost::bind(&ImageView::rightPageCenter, this)), - m_dragHandler(*this), - m_zoomHandler(*this), - m_handlePixmap(":/icons/aqua-sphere.png"), - m_virtLayout(layout), - m_leftPageRemoved(left_half_removed), - m_rightPageRemoved(right_half_removed) { - setMouseTracking(true); - - m_leftUnremoveButton.setClickCallback(boost::bind(&ImageView::unremoveLeftPage, this)); - m_rightUnremoveButton.setClickCallback(boost::bind(&ImageView::unremoveRightPage, this)); - - if (m_leftPageRemoved) { - makeLastFollower(m_leftUnremoveButton); - } - if (m_rightPageRemoved) { - makeLastFollower(m_rightUnremoveButton); - } - - setupCuttersInteraction(); - - rootInteractionHandler().makeLastFollower(*this); - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), + m_pages(std::move(pages)), + m_imageId(image_id), + m_leftUnremoveButton(boost::bind(&ImageView::leftPageCenter, this)), + m_rightUnremoveButton(boost::bind(&ImageView::rightPageCenter, this)), + m_dragHandler(*this), + m_zoomHandler(*this), + m_handlePixmap(":/icons/aqua-sphere.png"), + m_virtLayout(layout), + m_leftPageRemoved(left_half_removed), + m_rightPageRemoved(right_half_removed) { + setMouseTracking(true); + + m_leftUnremoveButton.setClickCallback(boost::bind(&ImageView::unremoveLeftPage, this)); + m_rightUnremoveButton.setClickCallback(boost::bind(&ImageView::unremoveRightPage, this)); + + if (m_leftPageRemoved) { + makeLastFollower(m_leftUnremoveButton); + } + if (m_rightPageRemoved) { + makeLastFollower(m_rightUnremoveButton); + } + + setupCuttersInteraction(); + + rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); } ImageView::~ImageView() = default; void ImageView::setupCuttersInteraction() { - const QString tip(tr("Drag the line or the handles.")); - const double hit_radius = std::max(0.5 * m_handlePixmap.width(), 15.0); - const int num_cutters = m_virtLayout.numCutters(); - for (int i = 0; i < num_cutters; ++i) { // Loop over lines. - m_lineInteractors[i].setObject(&m_lineSegments[0]); - - for (int j = 0; j < 2; ++j) { // Loop over handles. - m_handles[i][j].setHitRadius(hit_radius); - m_handles[i][j].setPositionCallback(boost::bind(&ImageView::handlePosition, this, i, j)); - m_handles[i][j].setMoveRequestCallback(boost::bind(&ImageView::handleMoveRequest, this, i, j, _1)); - m_handles[i][j].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - - m_handleInteractors[i][j].setObject(&m_handles[i][j]); - m_handleInteractors[i][j].setProximityStatusTip(tip); - makeLastFollower(m_handleInteractors[i][j]); - } - - m_lineSegments[i].setPositionCallback(boost::bind(&ImageView::linePosition, this, i)); - m_lineSegments[i].setMoveRequestCallback(boost::bind(&ImageView::lineMoveRequest, this, i, _1)); - m_lineSegments[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); - - m_lineInteractors[i].setObject(&m_lineSegments[i]); - m_lineInteractors[i].setProximityCursor(Qt::SplitHCursor); - m_lineInteractors[i].setInteractionCursor(Qt::SplitHCursor); - m_lineInteractors[i].setProximityStatusTip(tip); - makeLastFollower(m_lineInteractors[i]); + const QString tip(tr("Drag the line or the handles.")); + const double hit_radius = std::max(0.5 * m_handlePixmap.width(), 15.0); + const int num_cutters = m_virtLayout.numCutters(); + for (int i = 0; i < num_cutters; ++i) { // Loop over lines. + m_lineInteractors[i].setObject(&m_lineSegments[0]); + + for (int j = 0; j < 2; ++j) { // Loop over handles. + m_handles[i][j].setHitRadius(hit_radius); + m_handles[i][j].setPositionCallback(boost::bind(&ImageView::handlePosition, this, i, j)); + m_handles[i][j].setMoveRequestCallback(boost::bind(&ImageView::handleMoveRequest, this, i, j, _1)); + m_handles[i][j].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + + m_handleInteractors[i][j].setObject(&m_handles[i][j]); + m_handleInteractors[i][j].setProximityStatusTip(tip); + makeLastFollower(m_handleInteractors[i][j]); } - // Turn off cutters we don't need anymore. - for (int i = num_cutters; i < 2; ++i) { - for (int j = 0; j < 2; ++j) { - m_handleInteractors[i][j].unlink(); - } - m_lineInteractors[i].unlink(); + m_lineSegments[i].setPositionCallback(boost::bind(&ImageView::linePosition, this, i)); + m_lineSegments[i].setMoveRequestCallback(boost::bind(&ImageView::lineMoveRequest, this, i, _1)); + m_lineSegments[i].setDragFinishedCallback(boost::bind(&ImageView::dragFinished, this)); + + m_lineInteractors[i].setObject(&m_lineSegments[i]); + m_lineInteractors[i].setProximityCursor(Qt::SplitHCursor); + m_lineInteractors[i].setInteractionCursor(Qt::SplitHCursor); + m_lineInteractors[i].setProximityStatusTip(tip); + makeLastFollower(m_lineInteractors[i]); + } + + // Turn off cutters we don't need anymore. + for (int i = num_cutters; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + m_handleInteractors[i][j].unlink(); } + m_lineInteractors[i].unlink(); + } } // ImageView::setupCuttersInteraction void ImageView::pageLayoutSetExternally(const PageLayout& layout) { - m_virtLayout = layout; - setupCuttersInteraction(); - update(); + m_virtLayout = layout; + setupCuttersInteraction(); + update(); } void ImageView::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setRenderHint(QPainter::Antialiasing, false); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - - painter.setPen(Qt::NoPen); - const QRectF virt_rect(virtualDisplayRect()); - - switch (m_virtLayout.type()) { - case PageLayout::SINGLE_PAGE_UNCUT: - painter.setBrush(QColor(0, 0, 255, 50)); - painter.drawRect(virt_rect); - - return; // No Split Line will be drawn. - case PageLayout::SINGLE_PAGE_CUT: - painter.setBrush(QColor(0, 0, 255, 50)); - painter.drawPolygon(m_virtLayout.singlePageOutline()); - break; - case PageLayout::TWO_PAGES: - painter.setBrush(m_leftPageRemoved ? QColor(0, 0, 0, 80) : QColor(0, 0, 255, 50)); - painter.drawPolygon(m_virtLayout.leftPageOutline()); - painter.setBrush(m_rightPageRemoved ? QColor(0, 0, 0, 80) : QColor(255, 0, 0, 50)); - painter.drawPolygon(m_virtLayout.rightPageOutline()); - break; - } - - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setWorldTransform(QTransform()); - - QPen pen(QColor(0, 0, 255)); - pen.setCosmetic(true); - pen.setWidth(2); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - - const int num_cutters = m_virtLayout.numCutters(); - for (int i = 0; i < num_cutters; ++i) { - const QLineF cutter(widgetCutterLine(i)); - painter.drawLine(cutter); - - QRectF rect(m_handlePixmap.rect()); - - rect.moveCenter(cutter.p1()); - painter.drawPixmap(rect.topLeft(), m_handlePixmap); - - rect.moveCenter(cutter.p2()); - painter.drawPixmap(rect.topLeft(), m_handlePixmap); - } + painter.setRenderHint(QPainter::Antialiasing, false); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + painter.setPen(Qt::NoPen); + const QRectF virt_rect(virtualDisplayRect()); + + switch (m_virtLayout.type()) { + case PageLayout::SINGLE_PAGE_UNCUT: + painter.setBrush(QColor(0, 0, 255, 50)); + painter.drawRect(virt_rect); + + return; // No Split Line will be drawn. + case PageLayout::SINGLE_PAGE_CUT: + painter.setBrush(QColor(0, 0, 255, 50)); + painter.drawPolygon(m_virtLayout.singlePageOutline()); + break; + case PageLayout::TWO_PAGES: + painter.setBrush(m_leftPageRemoved ? QColor(0, 0, 0, 80) : QColor(0, 0, 255, 50)); + painter.drawPolygon(m_virtLayout.leftPageOutline()); + painter.setBrush(m_rightPageRemoved ? QColor(0, 0, 0, 80) : QColor(255, 0, 0, 50)); + painter.drawPolygon(m_virtLayout.rightPageOutline()); + break; + } + + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setWorldTransform(QTransform()); + + QPen pen(QColor(0, 0, 255)); + pen.setCosmetic(true); + pen.setWidth(2); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + const int num_cutters = m_virtLayout.numCutters(); + for (int i = 0; i < num_cutters; ++i) { + const QLineF cutter(widgetCutterLine(i)); + painter.drawLine(cutter); + + QRectF rect(m_handlePixmap.rect()); + + rect.moveCenter(cutter.p1()); + painter.drawPixmap(rect.topLeft(), m_handlePixmap); + + rect.moveCenter(cutter.p2()); + painter.drawPixmap(rect.topLeft(), m_handlePixmap); + } } // ImageView::onPaint PageLayout ImageView::widgetLayout() const { - return m_virtLayout.transformed(virtualToWidget()); + return m_virtLayout.transformed(virtualToWidget()); } QLineF ImageView::widgetCutterLine(const int line_idx) const { - const QRectF widget_rect(virtualToWidget().mapRect(virtualDisplayRect())); - QRectF reduced_widget_rect(reducedWidgetArea()); - reduced_widget_rect.setLeft(widget_rect.left()); - reduced_widget_rect.setRight(widget_rect.right()); - // The reason we restore original left and right boundaries is that - // we want to allow cutter handles to go off-screen horizontally - // but not vertically. - QLineF line(customInscribedCutterLine(widgetLayout().cutterLine(line_idx), reduced_widget_rect)); - - if (m_handleInteractors[line_idx][1].interactionInProgress(interactionState())) { - line.setP1(virtualToWidget().map(virtualCutterLine(line_idx).p1())); - } else if (m_handleInteractors[line_idx][0].interactionInProgress(interactionState())) { - line.setP2(virtualToWidget().map(virtualCutterLine(line_idx).p2())); - } - - return line; + const QRectF widget_rect(virtualToWidget().mapRect(virtualDisplayRect())); + QRectF reduced_widget_rect(reducedWidgetArea()); + reduced_widget_rect.setLeft(widget_rect.left()); + reduced_widget_rect.setRight(widget_rect.right()); + // The reason we restore original left and right boundaries is that + // we want to allow cutter handles to go off-screen horizontally + // but not vertically. + QLineF line(customInscribedCutterLine(widgetLayout().cutterLine(line_idx), reduced_widget_rect)); + + if (m_handleInteractors[line_idx][1].interactionInProgress(interactionState())) { + line.setP1(virtualToWidget().map(virtualCutterLine(line_idx).p1())); + } else if (m_handleInteractors[line_idx][0].interactionInProgress(interactionState())) { + line.setP2(virtualToWidget().map(virtualCutterLine(line_idx).p2())); + } + + return line; } QLineF ImageView::virtualCutterLine(int line_idx) const { - const QRectF virt_rect(virtualDisplayRect()); - - QRectF widget_rect(virtualToWidget().mapRect(virt_rect)); - const double delta = 0.5 * m_handlePixmap.width(); - widget_rect.adjust(0.0, delta, 0.0, -delta); - - QRectF reduced_virt_rect(widgetToVirtual().mapRect(widget_rect)); - reduced_virt_rect.setLeft(virt_rect.left()); - reduced_virt_rect.setRight(virt_rect.right()); - // The reason we restore original left and right boundaries is that - // we want to allow cutter handles to go off-screen horizontally - // but not vertically. - return customInscribedCutterLine(m_virtLayout.cutterLine(line_idx), reduced_virt_rect); + const QRectF virt_rect(virtualDisplayRect()); + + QRectF widget_rect(virtualToWidget().mapRect(virt_rect)); + const double delta = 0.5 * m_handlePixmap.width(); + widget_rect.adjust(0.0, delta, 0.0, -delta); + + QRectF reduced_virt_rect(widgetToVirtual().mapRect(widget_rect)); + reduced_virt_rect.setLeft(virt_rect.left()); + reduced_virt_rect.setRight(virt_rect.right()); + // The reason we restore original left and right boundaries is that + // we want to allow cutter handles to go off-screen horizontally + // but not vertically. + return customInscribedCutterLine(m_virtLayout.cutterLine(line_idx), reduced_virt_rect); } QRectF ImageView::reducedWidgetArea() const { - const qreal delta = 0.5 * m_handlePixmap.width(); + const qreal delta = 0.5 * m_handlePixmap.width(); - return getOccupiedWidgetRect().adjusted(0.0, delta, 0.0, -delta); + return getOccupiedWidgetRect().adjusted(0.0, delta, 0.0, -delta); } /** @@ -211,186 +211,186 @@ QRectF ImageView::reducedWidgetArea() const { * Line's angle may change as a result. */ QLineF ImageView::customInscribedCutterLine(const QLineF& line, const QRectF& rect) { - if (line.p1().y() == line.p2().y()) { - // This should not happen, but if it does, we need to handle it gracefully. - qreal middle_x = 0.5 * (line.p1().x() + line.p2().x()); - middle_x = qBound(rect.left(), middle_x, rect.right()); + if (line.p1().y() == line.p2().y()) { + // This should not happen, but if it does, we need to handle it gracefully. + qreal middle_x = 0.5 * (line.p1().x() + line.p2().x()); + middle_x = qBound(rect.left(), middle_x, rect.right()); - return QLineF(QPointF(middle_x, rect.top()), QPointF(middle_x, rect.bottom())); - } + return QLineF(QPointF(middle_x, rect.top()), QPointF(middle_x, rect.bottom())); + } - QPointF top_pt; - QPointF bottom_pt; + QPointF top_pt; + QPointF bottom_pt; - line.intersect(QLineF(rect.topLeft(), rect.topRight()), &top_pt); - line.intersect(QLineF(rect.bottomLeft(), rect.bottomRight()), &bottom_pt); + line.intersect(QLineF(rect.topLeft(), rect.topRight()), &top_pt); + line.intersect(QLineF(rect.bottomLeft(), rect.bottomRight()), &bottom_pt); - const double top_x = qBound(rect.left(), top_pt.x(), rect.right()); - const double bottom_x = qBound(rect.left(), bottom_pt.x(), rect.right()); + const double top_x = qBound(rect.left(), top_pt.x(), rect.right()); + const double bottom_x = qBound(rect.left(), bottom_pt.x(), rect.right()); - return QLineF(QPointF(top_x, rect.top()), QPointF(bottom_x, rect.bottom())); + return QLineF(QPointF(top_x, rect.top()), QPointF(bottom_x, rect.bottom())); } QPointF ImageView::handlePosition(int line_idx, int handle_idx) const { - const QLineF line(widgetCutterLine(line_idx)); + const QLineF line(widgetCutterLine(line_idx)); - return handle_idx == 0 ? line.p1() : line.p2(); + return handle_idx == 0 ? line.p1() : line.p2(); } void ImageView::handleMoveRequest(int line_idx, int handle_idx, const QPointF& pos) { - const QRectF valid_area(getOccupiedWidgetRect()); - - qreal min_x_top = valid_area.left(); - qreal max_x_top = valid_area.right(); - qreal min_x_bottom = valid_area.left(); - qreal max_x_bottom = valid_area.right(); - - // preventing intersection with other cutters - for (int i = 0; i < m_virtLayout.numCutters(); i++) { - if (i != line_idx) { - QPointF p_top_i; - QPointF p_bottom_i; - QLineF anotherLine = virtualToWidget().map(m_virtLayout.cutterLine(i)); - anotherLine.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top_i); - anotherLine.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom_i); - - if ((p_top_i.x() < min_x_top) || (p_top_i.x() > max_x_top) || (p_bottom_i.x() < min_x_bottom) - || (p_bottom_i.x() > max_x_bottom)) { - continue; - } - - if (line_idx > i) { - min_x_top = p_top_i.x(); - min_x_bottom = p_bottom_i.x(); - } else { - max_x_top = p_top_i.x(); - max_x_bottom = p_bottom_i.x(); - } - } - } - - QLineF virt_line(virtualCutterLine(line_idx)); - if (handle_idx == 0) { - const QPointF wpt(qBound(min_x_top, pos.x(), max_x_top), valid_area.top()); - virt_line.setP1(widgetToVirtual().map(wpt)); - } else { - const QPointF wpt(qBound(min_x_bottom, pos.x(), max_x_bottom), valid_area.bottom()); - virt_line.setP2(widgetToVirtual().map(wpt)); + const QRectF valid_area(getOccupiedWidgetRect()); + + qreal min_x_top = valid_area.left(); + qreal max_x_top = valid_area.right(); + qreal min_x_bottom = valid_area.left(); + qreal max_x_bottom = valid_area.right(); + + // preventing intersection with other cutters + for (int i = 0; i < m_virtLayout.numCutters(); i++) { + if (i != line_idx) { + QPointF p_top_i; + QPointF p_bottom_i; + QLineF anotherLine = virtualToWidget().map(m_virtLayout.cutterLine(i)); + anotherLine.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top_i); + anotherLine.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom_i); + + if ((p_top_i.x() < min_x_top) || (p_top_i.x() > max_x_top) || (p_bottom_i.x() < min_x_bottom) + || (p_bottom_i.x() > max_x_bottom)) { + continue; + } + + if (line_idx > i) { + min_x_top = p_top_i.x(); + min_x_bottom = p_bottom_i.x(); + } else { + max_x_top = p_top_i.x(); + max_x_bottom = p_bottom_i.x(); + } } - - m_virtLayout.setCutterLine(line_idx, virt_line); - update(); + } + + QLineF virt_line(virtualCutterLine(line_idx)); + if (handle_idx == 0) { + const QPointF wpt(qBound(min_x_top, pos.x(), max_x_top), valid_area.top()); + virt_line.setP1(widgetToVirtual().map(wpt)); + } else { + const QPointF wpt(qBound(min_x_bottom, pos.x(), max_x_bottom), valid_area.bottom()); + virt_line.setP2(widgetToVirtual().map(wpt)); + } + + m_virtLayout.setCutterLine(line_idx, virt_line); + update(); } QLineF ImageView::linePosition(int line_idx) const { - return widgetCutterLine(line_idx); + return widgetCutterLine(line_idx); } void ImageView::lineMoveRequest(int line_idx, QLineF line) { - // Intersect with top and bottom. - const QRectF valid_area(getOccupiedWidgetRect()); - QPointF p_top; - QPointF p_bottom; - line.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top); - line.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom); - - qreal min_x_top = valid_area.left(); - qreal max_x_top = valid_area.right(); - qreal min_x_bottom = valid_area.left(); - qreal max_x_bottom = valid_area.right(); - - // preventing intersection with other cutters - for (int i = 0; i < m_virtLayout.numCutters(); i++) { - if (i != line_idx) { - QPointF p_top_i; - QPointF p_bottom_i; - QLineF anotherLine = virtualToWidget().map(m_virtLayout.cutterLine(i)); - anotherLine.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top_i); - anotherLine.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom_i); - - if ((p_top_i.x() < min_x_top) || (p_top_i.x() > max_x_top) || (p_bottom_i.x() < min_x_bottom) - || (p_bottom_i.x() > max_x_bottom)) { - continue; - } - - if (line_idx > i) { - min_x_top = p_top_i.x(); - min_x_bottom = p_bottom_i.x(); - } else { - max_x_top = p_top_i.x(); - max_x_bottom = p_bottom_i.x(); - } - } + // Intersect with top and bottom. + const QRectF valid_area(getOccupiedWidgetRect()); + QPointF p_top; + QPointF p_bottom; + line.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top); + line.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom); + + qreal min_x_top = valid_area.left(); + qreal max_x_top = valid_area.right(); + qreal min_x_bottom = valid_area.left(); + qreal max_x_bottom = valid_area.right(); + + // preventing intersection with other cutters + for (int i = 0; i < m_virtLayout.numCutters(); i++) { + if (i != line_idx) { + QPointF p_top_i; + QPointF p_bottom_i; + QLineF anotherLine = virtualToWidget().map(m_virtLayout.cutterLine(i)); + anotherLine.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top_i); + anotherLine.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom_i); + + if ((p_top_i.x() < min_x_top) || (p_top_i.x() > max_x_top) || (p_bottom_i.x() < min_x_bottom) + || (p_bottom_i.x() > max_x_bottom)) { + continue; + } + + if (line_idx > i) { + min_x_top = p_top_i.x(); + min_x_bottom = p_bottom_i.x(); + } else { + max_x_top = p_top_i.x(); + max_x_bottom = p_bottom_i.x(); + } } - - // Limit movement. - const double left = qMax(min_x_top - p_top.x(), min_x_bottom - p_bottom.x()); - const double right = qMax(p_top.x() - max_x_top, p_bottom.x() - max_x_bottom); - if ((left > right) && (left > 0.0)) { - line.translate(left, 0.0); - } else if (right > 0.0) { - line.translate(-right, 0.0); - } - - m_virtLayout.setCutterLine(line_idx, widgetToVirtual().map(line)); - update(); + } + + // Limit movement. + const double left = qMax(min_x_top - p_top.x(), min_x_bottom - p_bottom.x()); + const double right = qMax(p_top.x() - max_x_top, p_bottom.x() - max_x_bottom); + if ((left > right) && (left > 0.0)) { + line.translate(left, 0.0); + } else if (right > 0.0) { + line.translate(-right, 0.0); + } + + m_virtLayout.setCutterLine(line_idx, widgetToVirtual().map(line)); + update(); } void ImageView::dragFinished() { - // When a handle is being dragged, the other handle is displayed not - // at the edge of the widget widget but at the edge of the image. - // That means we have to redraw once dragging is over. - // BTW, the only reason for displaying handles at widget's edges - // is to make them visible and accessible for dragging. - update(); - emit pageLayoutSetLocally(m_virtLayout); + // When a handle is being dragged, the other handle is displayed not + // at the edge of the widget widget but at the edge of the image. + // That means we have to redraw once dragging is over. + // BTW, the only reason for displaying handles at widget's edges + // is to make them visible and accessible for dragging. + update(); + emit pageLayoutSetLocally(m_virtLayout); } QPointF ImageView::leftPageCenter() const { - QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); - QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); + QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); + QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); - const double x_mid = 0.5 * (left_rect.right() + right_rect.left()); - left_rect.setRight(x_mid); - right_rect.setLeft(x_mid); + const double x_mid = 0.5 * (left_rect.right() + right_rect.left()); + left_rect.setRight(x_mid); + right_rect.setLeft(x_mid); - return virtualToWidget().map(left_rect.center()); + return virtualToWidget().map(left_rect.center()); } QPointF ImageView::rightPageCenter() const { - QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); - QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); + QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); + QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); - const double x_mid = 0.5 * (left_rect.right() + right_rect.left()); - left_rect.setRight(x_mid); - right_rect.setLeft(x_mid); + const double x_mid = 0.5 * (left_rect.right() + right_rect.left()); + left_rect.setRight(x_mid); + right_rect.setLeft(x_mid); - return virtualToWidget().map(right_rect.center()); + return virtualToWidget().map(right_rect.center()); } void ImageView::unremoveLeftPage() { - PageInfo page_info(m_ptrPages->unremovePage(PageId(m_imageId, PageId::LEFT_PAGE))); - m_leftUnremoveButton.unlink(); - m_leftPageRemoved = false; + PageInfo page_info(m_pages->unremovePage(PageId(m_imageId, PageId::LEFT_PAGE))); + m_leftUnremoveButton.unlink(); + m_leftPageRemoved = false; - update(); + update(); - // We need invalidateThumbnail(PageInfo) rather than (PageId), - // as we are updating page removal status. - page_info.setId(PageId(m_imageId, PageId::SINGLE_PAGE)); - emit invalidateThumbnail(page_info); + // We need invalidateThumbnail(PageInfo) rather than (PageId), + // as we are updating page removal status. + page_info.setId(PageId(m_imageId, PageId::SINGLE_PAGE)); + emit invalidateThumbnail(page_info); } void ImageView::unremoveRightPage() { - PageInfo page_info(m_ptrPages->unremovePage(PageId(m_imageId, PageId::RIGHT_PAGE))); - m_rightUnremoveButton.unlink(); - m_rightPageRemoved = false; + PageInfo page_info(m_pages->unremovePage(PageId(m_imageId, PageId::RIGHT_PAGE))); + m_rightUnremoveButton.unlink(); + m_rightPageRemoved = false; - update(); + update(); - // We need invalidateThumbnail(PageInfo) rather than (PageId), - // as we are updating page removal status. - page_info.setId(PageId(m_imageId, PageId::SINGLE_PAGE)); - emit invalidateThumbnail(page_info); + // We need invalidateThumbnail(PageInfo) rather than (PageId), + // as we are updating page removal status. + page_info.setId(PageId(m_imageId, PageId::SINGLE_PAGE)); + emit invalidateThumbnail(page_info); } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/ImageView.h b/filters/page_split/ImageView.h index b73db8804..5bb9f6b0a 100644 --- a/filters/page_split/ImageView.h +++ b/filters/page_split/ImageView.h @@ -19,17 +19,17 @@ #ifndef PAGE_SPLIT_IMAGEVIEW_H_ #define PAGE_SPLIT_IMAGEVIEW_H_ -#include "ImageViewBase.h" +#include #include "DragHandler.h" -#include "ZoomHandler.h" -#include "ObjectDragHandler.h" -#include "DraggablePoint.h" #include "DraggableLineSegment.h" +#include "DraggablePoint.h" +#include "ImageId.h" +#include "ImageViewBase.h" +#include "ObjectDragHandler.h" #include "PageLayout.h" #include "UnremoveButton.h" -#include "ImageId.h" +#include "ZoomHandler.h" #include "intrusive_ptr.h" -#include class ImageTransformation; class ProjectPages; @@ -40,103 +40,103 @@ class QLineF; namespace page_split { class ImageView : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - ImageView(const QImage& image, - const QImage& downscaled_image, - const ImageTransformation& xform, - const PageLayout& layout, - intrusive_ptr pages, - const ImageId& image_id, - bool left_half_removed, - bool right_half_removed); + Q_OBJECT + public: + ImageView(const QImage& image, + const QImage& downscaled_image, + const ImageTransformation& xform, + const PageLayout& layout, + intrusive_ptr pages, + const ImageId& image_id, + bool left_half_removed, + bool right_half_removed); - ~ImageView() override; + ~ImageView() override; -signals: + signals: - void invalidateThumbnail(const PageInfo& page_info); + void invalidateThumbnail(const PageInfo& page_info); - void pageLayoutSetLocally(const PageLayout& layout); + void pageLayoutSetLocally(const PageLayout& layout); -public slots: + public slots: - void pageLayoutSetExternally(const PageLayout& layout); + void pageLayoutSetExternally(const PageLayout& layout); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; -private: - void setupCuttersInteraction(); + private: + void setupCuttersInteraction(); - QPointF handlePosition(int line_idx, int handle_idx) const; + QPointF handlePosition(int line_idx, int handle_idx) const; - void handleMoveRequest(int line_idx, int handle_idx, const QPointF& pos); + void handleMoveRequest(int line_idx, int handle_idx, const QPointF& pos); - QLineF linePosition(int line_idx) const; + QLineF linePosition(int line_idx) const; - void lineMoveRequest(int line_idx, QLineF line); + void lineMoveRequest(int line_idx, QLineF line); - void dragFinished(); + void dragFinished(); - QPointF leftPageCenter() const; + QPointF leftPageCenter() const; - QPointF rightPageCenter() const; + QPointF rightPageCenter() const; - void unremoveLeftPage(); + void unremoveLeftPage(); - void unremoveRightPage(); + void unremoveRightPage(); - /** - * \return Page layout in widget coordinates. - */ - PageLayout widgetLayout() const; + /** + * \return Page layout in widget coordinates. + */ + PageLayout widgetLayout() const; - /** - * \return A Cutter line in widget coordinates. - * - * Depending on the current interaction state, the line segment - * may end either shortly before the widget boundaries, or shortly - * before the image boundaries. - */ - QLineF widgetCutterLine(int line_idx) const; + /** + * \return A Cutter line in widget coordinates. + * + * Depending on the current interaction state, the line segment + * may end either shortly before the widget boundaries, or shortly + * before the image boundaries. + */ + QLineF widgetCutterLine(int line_idx) const; - /** - * \return A Cutter line in virtual image coordinates. - * - * Unlike widgetCutterLine(), this one always ends shortly before - * the image boundaries. - */ - QLineF virtualCutterLine(int line_idx) const; + /** + * \return A Cutter line in virtual image coordinates. + * + * Unlike widgetCutterLine(), this one always ends shortly before + * the image boundaries. + */ + QLineF virtualCutterLine(int line_idx) const; - /** - * Same as ImageViewBase::getVisibleWidgetRect(), except reduced - * vertically to accomodate the height of line endpoint handles. - */ - QRectF reducedWidgetArea() const; + /** + * Same as ImageViewBase::getVisibleWidgetRect(), except reduced + * vertically to accomodate the height of line endpoint handles. + */ + QRectF reducedWidgetArea() const; - static QLineF customInscribedCutterLine(const QLineF& line, const QRectF& rect); + static QLineF customInscribedCutterLine(const QLineF& line, const QRectF& rect); - intrusive_ptr m_ptrPages; - ImageId m_imageId; - DraggablePoint m_handles[2][2]; - ObjectDragHandler m_handleInteractors[2][2]; - DraggableLineSegment m_lineSegments[2]; - ObjectDragHandler m_lineInteractors[2]; - UnremoveButton m_leftUnremoveButton; - UnremoveButton m_rightUnremoveButton; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + intrusive_ptr m_pages; + ImageId m_imageId; + DraggablePoint m_handles[2][2]; + ObjectDragHandler m_handleInteractors[2][2]; + DraggableLineSegment m_lineSegments[2]; + ObjectDragHandler m_lineInteractors[2]; + UnremoveButton m_leftUnremoveButton; + UnremoveButton m_rightUnremoveButton; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; - QPixmap m_handlePixmap; + QPixmap m_handlePixmap; - /** - * Page layout in virtual image coordinates. - */ - PageLayout m_virtLayout; + /** + * Page layout in virtual image coordinates. + */ + PageLayout m_virtLayout; - bool m_leftPageRemoved; - bool m_rightPageRemoved; + bool m_leftPageRemoved; + bool m_rightPageRemoved; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_IMAGEVIEW_H_ diff --git a/filters/page_split/LayoutType.cpp b/filters/page_split/LayoutType.cpp index 10f9f3cc3..70af63c80 100644 --- a/filters/page_split/LayoutType.cpp +++ b/filters/page_split/LayoutType.cpp @@ -21,30 +21,30 @@ namespace page_split { QString layoutTypeToString(const LayoutType layout_type) { - switch (layout_type) { - case AUTO_LAYOUT_TYPE: - return "auto-detect"; - case SINGLE_PAGE_UNCUT: - return "single-uncut"; - case PAGE_PLUS_OFFCUT: - return "single-cut"; - case TWO_PAGES: - return "two-pages"; - } - assert(!"unreachable"); + switch (layout_type) { + case AUTO_LAYOUT_TYPE: + return "auto-detect"; + case SINGLE_PAGE_UNCUT: + return "single-uncut"; + case PAGE_PLUS_OFFCUT: + return "single-cut"; + case TWO_PAGES: + return "two-pages"; + } + assert(!"unreachable"); - return QString(); + return QString(); } LayoutType layoutTypeFromString(const QString& layout_type) { - if (layout_type == "single-uncut") { - return SINGLE_PAGE_UNCUT; - } else if (layout_type == "single-cut") { - return PAGE_PLUS_OFFCUT; - } else if (layout_type == "two-pages") { - return TWO_PAGES; - } else { - return AUTO_LAYOUT_TYPE; - } + if (layout_type == "single-uncut") { + return SINGLE_PAGE_UNCUT; + } else if (layout_type == "single-cut") { + return PAGE_PLUS_OFFCUT; + } else if (layout_type == "two-pages") { + return TWO_PAGES; + } else { + return AUTO_LAYOUT_TYPE; + } } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/OptionsWidget.cpp b/filters/page_split/OptionsWidget.cpp index e56c8f8bd..589be52e5 100644 --- a/filters/page_split/OptionsWidget.cpp +++ b/filters/page_split/OptionsWidget.cpp @@ -17,347 +17,339 @@ */ #include "OptionsWidget.h" +#include +#include #include "Filter.h" -#include "SplitModeDialog.h" -#include "Settings.h" +#include "PageLayoutAdapter.h" #include "ProjectPages.h" #include "ScopedIncDec.h" -#include "PageLayoutAdapter.h" -#include -#include +#include "Settings.h" +#include "SplitModeDialog.h" namespace page_split { OptionsWidget::OptionsWidget(intrusive_ptr settings, intrusive_ptr page_sequence, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), - m_ptrPages(std::move(page_sequence)), - m_pageSelectionAccessor(page_selection_accessor), - m_ignoreAutoManualToggle(0), - m_ignoreLayoutTypeToggle(0) { - setupUi(this); - // Workaround for QTBUG-182 - auto* grp = new QButtonGroup(this); - grp->addButton(autoBtn); - grp->addButton(manualBtn); - - setupUiConnections(); + : m_settings(std::move(settings)), + m_pages(std::move(page_sequence)), + m_pageSelectionAccessor(page_selection_accessor), + m_ignoreAutoManualToggle(0), + m_ignoreLayoutTypeToggle(0) { + setupUi(this); + // Workaround for QTBUG-182 + auto* grp = new QButtonGroup(this); + grp->addButton(autoBtn); + grp->addButton(manualBtn); + + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::preUpdateUI(const PageId& page_id) { - removeUiConnections(); - - ScopedIncDec guard1(m_ignoreAutoManualToggle); - ScopedIncDec guard2(m_ignoreLayoutTypeToggle); - - m_pageId = page_id; - const Settings::Record record(m_ptrSettings->getPageRecord(page_id.imageId())); - const LayoutType layout_type(record.combinedLayoutType()); - - switch (layout_type) { - case AUTO_LAYOUT_TYPE: - // Uncheck all buttons. Can only be done - // by playing with exclusiveness. - twoPagesBtn->setChecked(true); - twoPagesBtn->setAutoExclusive(false); - twoPagesBtn->setChecked(false); - twoPagesBtn->setAutoExclusive(true); - break; - case SINGLE_PAGE_UNCUT: - singlePageUncutBtn->setChecked(true); - break; - case PAGE_PLUS_OFFCUT: - pagePlusOffcutBtn->setChecked(true); - break; - case TWO_PAGES: - twoPagesBtn->setChecked(true); - break; - } - - splitLineGroup->setVisible(layout_type != SINGLE_PAGE_UNCUT); - - if (layout_type == AUTO_LAYOUT_TYPE) { - changeBtn->setEnabled(false); - scopeLabel->setText("?"); - } else { - changeBtn->setEnabled(true); - scopeLabel->setText(tr("Set manually")); - } - - // Uncheck both the Auto and Manual buttons. - autoBtn->setChecked(true); - autoBtn->setAutoExclusive(false); - autoBtn->setChecked(false); - autoBtn->setAutoExclusive(true); - // And disable both of them. - autoBtn->setEnabled(false); - manualBtn->setEnabled(false); - - setupUiConnections(); + removeUiConnections(); + + ScopedIncDec guard1(m_ignoreAutoManualToggle); + ScopedIncDec guard2(m_ignoreLayoutTypeToggle); + + m_pageId = page_id; + const Settings::Record record(m_settings->getPageRecord(page_id.imageId())); + const LayoutType layout_type(record.combinedLayoutType()); + + switch (layout_type) { + case AUTO_LAYOUT_TYPE: + // Uncheck all buttons. Can only be done + // by playing with exclusiveness. + twoPagesBtn->setChecked(true); + twoPagesBtn->setAutoExclusive(false); + twoPagesBtn->setChecked(false); + twoPagesBtn->setAutoExclusive(true); + break; + case SINGLE_PAGE_UNCUT: + singlePageUncutBtn->setChecked(true); + break; + case PAGE_PLUS_OFFCUT: + pagePlusOffcutBtn->setChecked(true); + break; + case TWO_PAGES: + twoPagesBtn->setChecked(true); + break; + } + + splitLineGroup->setVisible(layout_type != SINGLE_PAGE_UNCUT); + + if (layout_type == AUTO_LAYOUT_TYPE) { + changeBtn->setEnabled(false); + scopeLabel->setText("?"); + } else { + changeBtn->setEnabled(true); + scopeLabel->setText(tr("Set manually")); + } + + // Uncheck both the Auto and Manual buttons. + autoBtn->setChecked(true); + autoBtn->setAutoExclusive(false); + autoBtn->setChecked(false); + autoBtn->setAutoExclusive(true); + // And disable both of them. + autoBtn->setEnabled(false); + manualBtn->setEnabled(false); + + setupUiConnections(); } // OptionsWidget::preUpdateUI void OptionsWidget::postUpdateUI(const UiData& ui_data) { - removeUiConnections(); + removeUiConnections(); - ScopedIncDec guard1(m_ignoreAutoManualToggle); - ScopedIncDec guard2(m_ignoreLayoutTypeToggle); + ScopedIncDec guard1(m_ignoreAutoManualToggle); + ScopedIncDec guard2(m_ignoreLayoutTypeToggle); - m_uiData = ui_data; + m_uiData = ui_data; - changeBtn->setEnabled(true); - autoBtn->setEnabled(true); - manualBtn->setEnabled(true); + changeBtn->setEnabled(true); + autoBtn->setEnabled(true); + manualBtn->setEnabled(true); - if (ui_data.splitLineMode() == MODE_AUTO) { - autoBtn->setChecked(true); - } else { - manualBtn->setChecked(true); - } + if (ui_data.splitLineMode() == MODE_AUTO) { + autoBtn->setChecked(true); + } else { + manualBtn->setChecked(true); + } - const PageLayout::Type layout_type = ui_data.pageLayout().type(); - - switch (layout_type) { - case PageLayout::SINGLE_PAGE_UNCUT: - singlePageUncutBtn->setChecked(true); - break; - case PageLayout::SINGLE_PAGE_CUT: - pagePlusOffcutBtn->setChecked(true); - break; - case PageLayout::TWO_PAGES: - twoPagesBtn->setChecked(true); - break; - } + const PageLayout::Type layout_type = ui_data.pageLayout().type(); - splitLineGroup->setVisible(layout_type != PageLayout::SINGLE_PAGE_UNCUT); + switch (layout_type) { + case PageLayout::SINGLE_PAGE_UNCUT: + singlePageUncutBtn->setChecked(true); + break; + case PageLayout::SINGLE_PAGE_CUT: + pagePlusOffcutBtn->setChecked(true); + break; + case PageLayout::TWO_PAGES: + twoPagesBtn->setChecked(true); + break; + } - if (ui_data.layoutTypeAutoDetected()) { - scopeLabel->setText(tr("Auto detected")); - } + splitLineGroup->setVisible(layout_type != PageLayout::SINGLE_PAGE_UNCUT); + + if (ui_data.layoutTypeAutoDetected()) { + scopeLabel->setText(tr("Auto detected")); + } - setupUiConnections(); + setupUiConnections(); } // OptionsWidget::postUpdateUI void OptionsWidget::pageLayoutSetExternally(const PageLayout& page_layout) { - ScopedIncDec guard(m_ignoreAutoManualToggle); + ScopedIncDec guard(m_ignoreAutoManualToggle); - m_uiData.setPageLayout(page_layout); - m_uiData.setSplitLineMode(MODE_MANUAL); - commitCurrentParams(); + m_uiData.setPageLayout(page_layout); + m_uiData.setSplitLineMode(MODE_MANUAL); + commitCurrentParams(); - manualBtn->setChecked(true); + manualBtn->setChecked(true); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::layoutTypeButtonToggled(const bool checked) { - if (!checked || m_ignoreLayoutTypeToggle) { - return; - } - - LayoutType lt; - ProjectPages::LayoutType plt = ProjectPages::ONE_PAGE_LAYOUT; - - QObject* button = sender(); - if (button == singlePageUncutBtn) { - lt = SINGLE_PAGE_UNCUT; - } else if (button == pagePlusOffcutBtn) { - lt = PAGE_PLUS_OFFCUT; + if (!checked || m_ignoreLayoutTypeToggle) { + return; + } + + LayoutType lt; + ProjectPages::LayoutType plt = ProjectPages::ONE_PAGE_LAYOUT; + + QObject* button = sender(); + if (button == singlePageUncutBtn) { + lt = SINGLE_PAGE_UNCUT; + } else if (button == pagePlusOffcutBtn) { + lt = PAGE_PLUS_OFFCUT; + } else { + assert(button == twoPagesBtn); + lt = TWO_PAGES; + plt = ProjectPages::TWO_PAGE_LAYOUT; + } + + Settings::UpdateAction update; + update.setLayoutType(lt); + + splitLineGroup->setVisible(lt != SINGLE_PAGE_UNCUT); + scopeLabel->setText(tr("Set manually")); + + m_pages->setLayoutTypeFor(m_pageId.imageId(), plt); + + if ((lt == PAGE_PLUS_OFFCUT) || ((lt != SINGLE_PAGE_UNCUT) && (m_uiData.splitLineMode() == MODE_AUTO))) { + m_settings->updatePage(m_pageId.imageId(), update); + emit reloadRequested(); + } else { + PageLayout::Type plt; + if (lt == SINGLE_PAGE_UNCUT) { + plt = PageLayout::SINGLE_PAGE_UNCUT; } else { - assert(button == twoPagesBtn); - lt = TWO_PAGES; - plt = ProjectPages::TWO_PAGE_LAYOUT; + assert(lt == TWO_PAGES); + plt = PageLayout::TWO_PAGES; } - Settings::UpdateAction update; - update.setLayoutType(lt); - - splitLineGroup->setVisible(lt != SINGLE_PAGE_UNCUT); - scopeLabel->setText(tr("Set manually")); - - m_ptrPages->setLayoutTypeFor(m_pageId.imageId(), plt); - - if ((lt == PAGE_PLUS_OFFCUT) || ((lt != SINGLE_PAGE_UNCUT) && (m_uiData.splitLineMode() == MODE_AUTO))) { - m_ptrSettings->updatePage(m_pageId.imageId(), update); - emit reloadRequested(); - } else { - PageLayout::Type plt; - if (lt == SINGLE_PAGE_UNCUT) { - plt = PageLayout::SINGLE_PAGE_UNCUT; - } else { - assert(lt == TWO_PAGES); - plt = PageLayout::TWO_PAGES; - } + PageLayout new_layout(m_uiData.pageLayout()); + new_layout.setType(plt); + const Params new_params(new_layout, m_uiData.dependencies(), m_uiData.splitLineMode()); - PageLayout new_layout(m_uiData.pageLayout()); - new_layout.setType(plt); - const Params new_params(new_layout, m_uiData.dependencies(), m_uiData.splitLineMode()); + update.setParams(new_params); + m_settings->updatePage(m_pageId.imageId(), update); - update.setParams(new_params); - m_ptrSettings->updatePage(m_pageId.imageId(), update); + m_uiData.setPageLayout(new_layout); - m_uiData.setPageLayout(new_layout); - - emit pageLayoutSetLocally(new_layout); - emit invalidateThumbnail(m_pageId); - } + emit pageLayoutSetLocally(new_layout); + emit invalidateThumbnail(m_pageId); + } } // OptionsWidget::layoutTypeButtonToggled void OptionsWidget::showChangeDialog() { - const Settings::Record record(m_ptrSettings->getPageRecord(m_pageId.imageId())); - const Params* params = record.params(); - if (!params) { - return; - } - - auto* dialog = new SplitModeDialog(this, m_pageId, m_pageSelectionAccessor, record.combinedLayoutType(), - params->pageLayout().type(), params->splitLineMode() == MODE_AUTO); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(accepted(const std::set&, LayoutType, bool)), this, - SLOT(layoutTypeSet(const std::set&, LayoutType, bool))); - dialog->show(); + const Settings::Record record(m_settings->getPageRecord(m_pageId.imageId())); + const Params* params = record.params(); + if (!params) { + return; + } + + auto* dialog = new SplitModeDialog(this, m_pageId, m_pageSelectionAccessor, record.combinedLayoutType(), + params->pageLayout().type(), params->splitLineMode() == MODE_AUTO); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(accepted(const std::set&, LayoutType, bool)), this, + SLOT(layoutTypeSet(const std::set&, LayoutType, bool))); + dialog->show(); } void OptionsWidget::layoutTypeSet(const std::set& pages, const LayoutType layout_type, bool apply_cut) { - if (pages.empty()) { - return; - } - - const Params params = *(m_ptrSettings->getPageRecord(m_pageId.imageId()).params()); - - if (layout_type != AUTO_LAYOUT_TYPE) { - for (const PageId& page_id : pages) { - if (m_pageId == page_id) { - continue; - } - - Settings::UpdateAction update_params; - update_params.setLayoutType(layout_type); - if (apply_cut && (layout_type != SINGLE_PAGE_UNCUT)) { - Params new_params(params); - - const Params* old_params = m_ptrSettings->getPageRecord(page_id.imageId()).params(); - if (old_params != nullptr) { - std::unique_ptr newPageLayout = PageLayoutAdapter::adaptPageLayout( - params.pageLayout(), old_params->pageLayout().uncutOutline().boundingRect()); - - update_params.setLayoutType(newPageLayout->toLayoutType()); - new_params.setPageLayout(*newPageLayout); - - Dependencies oldDeps = old_params->dependencies(); - oldDeps.setLayoutType(newPageLayout->toLayoutType()); - new_params.setDependencies(oldDeps); - } - - update_params.setParams(new_params); - } - m_ptrSettings->updatePage(page_id.imageId(), update_params); - } - } else { - for (const PageId& page_id : pages) { - if (m_pageId == page_id) { - continue; - } - - Settings::UpdateAction update_params; - update_params.setLayoutType(layout_type); - m_ptrSettings->updatePage(page_id.imageId(), update_params); + if (pages.empty()) { + return; + } + + const Params params = *(m_settings->getPageRecord(m_pageId.imageId()).params()); + + if (layout_type != AUTO_LAYOUT_TYPE) { + for (const PageId& page_id : pages) { + Settings::UpdateAction update_action; + update_action.setLayoutType(layout_type); + if (apply_cut && (layout_type != SINGLE_PAGE_UNCUT)) { + Params new_page_params(params); + const Params* old_page_params = m_settings->getPageRecord(page_id.imageId()).params(); + if (old_page_params) { + PageLayout new_page_layout = PageLayoutAdapter::adaptPageLayout( + params.pageLayout(), old_page_params->pageLayout().uncutOutline().boundingRect()); + + update_action.setLayoutType(new_page_layout.toLayoutType()); + new_page_params.setPageLayout(new_page_layout); + + Dependencies deps = old_page_params->dependencies(); + deps.setLayoutType(new_page_layout.toLayoutType()); + new_page_params.setDependencies(deps); } + update_action.setParams(new_page_params); + } + m_settings->updatePage(page_id.imageId(), update_action); } - - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + } else { + for (const PageId& page_id : pages) { + Settings::UpdateAction update_params; + update_params.setLayoutType(layout_type); + m_settings->updatePage(page_id.imageId(), update_params); } + } - if (layout_type == AUTO_LAYOUT_TYPE) { - scopeLabel->setText(tr("Auto detected")); - emit reloadRequested(); - } else { - scopeLabel->setText(tr("Set manually")); + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } + + if (layout_type == AUTO_LAYOUT_TYPE) { + scopeLabel->setText(tr("Auto detected")); + emit reloadRequested(); + } else { + scopeLabel->setText(tr("Set manually")); + } } // OptionsWidget::layoutTypeSet void OptionsWidget::splitLineModeChanged(const bool auto_mode) { - if (m_ignoreAutoManualToggle) { - return; - } + if (m_ignoreAutoManualToggle) { + return; + } - if (auto_mode) { - Settings::UpdateAction update; - update.clearParams(); - m_ptrSettings->updatePage(m_pageId.imageId(), update); - m_uiData.setSplitLineMode(MODE_AUTO); - emit reloadRequested(); - } else { - m_uiData.setSplitLineMode(MODE_MANUAL); - commitCurrentParams(); - } + if (auto_mode) { + Settings::UpdateAction update; + update.clearParams(); + m_settings->updatePage(m_pageId.imageId(), update); + m_uiData.setSplitLineMode(MODE_AUTO); + emit reloadRequested(); + } else { + m_uiData.setSplitLineMode(MODE_MANUAL); + commitCurrentParams(); + } } void OptionsWidget::commitCurrentParams() { - const Params params(m_uiData.pageLayout(), m_uiData.dependencies(), m_uiData.splitLineMode()); - Settings::UpdateAction update; - update.setParams(params); - m_ptrSettings->updatePage(m_pageId.imageId(), update); + const Params params(m_uiData.pageLayout(), m_uiData.dependencies(), m_uiData.splitLineMode()); + Settings::UpdateAction update; + update.setParams(params); + m_settings->updatePage(m_pageId.imageId(), update); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(singlePageUncutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - connect(pagePlusOffcutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - connect(twoPagesBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - connect(changeBtn, SIGNAL(clicked()), this, SLOT(showChangeDialog())); - connect(autoBtn, SIGNAL(toggled(bool)), this, SLOT(splitLineModeChanged(bool))); + CONNECT(singlePageUncutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); + CONNECT(pagePlusOffcutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); + CONNECT(twoPagesBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); + CONNECT(changeBtn, SIGNAL(clicked()), this, SLOT(showChangeDialog())); + CONNECT(autoBtn, SIGNAL(toggled(bool)), this, SLOT(splitLineModeChanged(bool))); } +#undef CONNECT + void OptionsWidget::removeUiConnections() { - disconnect(singlePageUncutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - disconnect(pagePlusOffcutBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - disconnect(twoPagesBtn, SIGNAL(toggled(bool)), this, SLOT(layoutTypeButtonToggled(bool))); - disconnect(changeBtn, SIGNAL(clicked()), this, SLOT(showChangeDialog())); - disconnect(autoBtn, SIGNAL(toggled(bool)), this, SLOT(splitLineModeChanged(bool))); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } /*============================= Widget::UiData ==========================*/ -OptionsWidget::UiData::UiData() : m_splitLineMode(MODE_AUTO), m_layoutTypeAutoDetected(false) { -} +OptionsWidget::UiData::UiData() : m_splitLineMode(MODE_AUTO), m_layoutTypeAutoDetected(false) {} OptionsWidget::UiData::~UiData() = default; void OptionsWidget::UiData::setPageLayout(const PageLayout& layout) { - m_pageLayout = layout; + m_pageLayout = layout; } const PageLayout& OptionsWidget::UiData::pageLayout() const { - return m_pageLayout; + return m_pageLayout; } void OptionsWidget::UiData::setDependencies(const Dependencies& deps) { - m_deps = deps; + m_deps = deps; } const Dependencies& OptionsWidget::UiData::dependencies() const { - return m_deps; + return m_deps; } void OptionsWidget::UiData::setSplitLineMode(const AutoManualMode mode) { - m_splitLineMode = mode; + m_splitLineMode = mode; } AutoManualMode OptionsWidget::UiData::splitLineMode() const { - return m_splitLineMode; + return m_splitLineMode; } bool OptionsWidget::UiData::layoutTypeAutoDetected() const { - return m_layoutTypeAutoDetected; + return m_layoutTypeAutoDetected; } void OptionsWidget::UiData::setLayoutTypeAutoDetected(const bool val) { - m_layoutTypeAutoDetected = val; + m_layoutTypeAutoDetected = val; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/OptionsWidget.h b/filters/page_split/OptionsWidget.h index 1e0af3ae6..c3323bdb4 100644 --- a/filters/page_split/OptionsWidget.h +++ b/filters/page_split/OptionsWidget.h @@ -19,17 +19,18 @@ #ifndef PAGE_SPLIT_OPTIONSWIDGET_H_ #define PAGE_SPLIT_OPTIONSWIDGET_H_ -#include "ui_PageSplitOptionsWidget.h" +#include +#include +#include "AutoManualMode.h" +#include "Dependencies.h" #include "FilterOptionsWidget.h" -#include "intrusive_ptr.h" -#include "LayoutType.h" -#include "PageLayout.h" #include "ImageId.h" +#include "LayoutType.h" #include "PageId.h" +#include "PageLayout.h" #include "PageSelectionAccessor.h" -#include "Dependencies.h" -#include "AutoManualMode.h" -#include +#include "intrusive_ptr.h" +#include "ui_PageSplitOptionsWidget.h" class ProjectPages; @@ -37,81 +38,83 @@ namespace page_split { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::PageSplitOptionsWidget { - Q_OBJECT -public: - class UiData { - // Member-wise copying is OK. - public: - UiData(); + Q_OBJECT + public: + class UiData { + // Member-wise copying is OK. + public: + UiData(); + + ~UiData(); - ~UiData(); + void setPageLayout(const PageLayout& layout); - void setPageLayout(const PageLayout& layout); + const PageLayout& pageLayout() const; - const PageLayout& pageLayout() const; + void setDependencies(const Dependencies& deps); - void setDependencies(const Dependencies& deps); + const Dependencies& dependencies() const; - const Dependencies& dependencies() const; + void setSplitLineMode(AutoManualMode mode); - void setSplitLineMode(AutoManualMode mode); + AutoManualMode splitLineMode() const; - AutoManualMode splitLineMode() const; + bool layoutTypeAutoDetected() const; - bool layoutTypeAutoDetected() const; + void setLayoutTypeAutoDetected(bool val); - void setLayoutTypeAutoDetected(bool val); + private: + PageLayout m_pageLayout; + Dependencies m_deps; + AutoManualMode m_splitLineMode; + bool m_layoutTypeAutoDetected; + }; - private: - PageLayout m_pageLayout; - Dependencies m_deps; - AutoManualMode m_splitLineMode; - bool m_layoutTypeAutoDetected; - }; + OptionsWidget(intrusive_ptr settings, + intrusive_ptr page_sequence, + const PageSelectionAccessor& page_selection_accessor); - OptionsWidget(intrusive_ptr settings, - intrusive_ptr page_sequence, - const PageSelectionAccessor& page_selection_accessor); + ~OptionsWidget() override; - ~OptionsWidget() override; + void preUpdateUI(const PageId& page_id); - void preUpdateUI(const PageId& page_id); + void postUpdateUI(const UiData& ui_data); - void postUpdateUI(const UiData& ui_data); + signals: -signals: + void pageLayoutSetLocally(const PageLayout& page_layout); - void pageLayoutSetLocally(const PageLayout& page_layout); + public slots: -public slots: + void pageLayoutSetExternally(const PageLayout& page_layout); - void pageLayoutSetExternally(const PageLayout& page_layout); + private slots: -private slots: + void layoutTypeButtonToggled(bool checked); - void layoutTypeButtonToggled(bool checked); + void showChangeDialog(); - void showChangeDialog(); + void layoutTypeSet(const std::set& pages, LayoutType layout_type, bool apply_cut); - void layoutTypeSet(const std::set& pages, LayoutType layout_type, bool apply_cut); + void splitLineModeChanged(bool auto_mode); - void splitLineModeChanged(bool auto_mode); + private: + void commitCurrentParams(); -private: - void commitCurrentParams(); + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + intrusive_ptr m_settings; + intrusive_ptr m_pages; + PageSelectionAccessor m_pageSelectionAccessor; + PageId m_pageId; + UiData m_uiData; + int m_ignoreAutoManualToggle; + int m_ignoreLayoutTypeToggle; - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrPages; - PageSelectionAccessor m_pageSelectionAccessor; - PageId m_pageId; - UiData m_uiData; - int m_ignoreAutoManualToggle; - int m_ignoreLayoutTypeToggle; + std::list m_connectionList; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_OPTIONSWIDGET_H_ diff --git a/filters/page_split/OrderBySplitTypeProvider.cpp b/filters/page_split/OrderBySplitTypeProvider.cpp index 7af1d6301..b8a488b3e 100644 --- a/filters/page_split/OrderBySplitTypeProvider.cpp +++ b/filters/page_split/OrderBySplitTypeProvider.cpp @@ -23,51 +23,50 @@ namespace page_split { OrderBySplitTypeProvider::OrderBySplitTypeProvider(intrusive_ptr settings) - : m_ptrSettings(std::move(settings)) { -} + : m_settings(std::move(settings)) {} bool OrderBySplitTypeProvider::precedes(const PageId& lhs_page, const bool lhs_incomplete, const PageId& rhs_page, const bool rhs_incomplete) const { - if (lhs_incomplete != rhs_incomplete) { - // Pages with question mark go to the bottom. - return rhs_incomplete; - } else if (lhs_incomplete) { - assert(rhs_incomplete); - // Two pages with question marks are ordered naturally. - return lhs_page < rhs_page; - } + if (lhs_incomplete != rhs_incomplete) { + // Pages with question mark go to the bottom. + return rhs_incomplete; + } else if (lhs_incomplete) { + assert(rhs_incomplete); + // Two pages with question marks are ordered naturally. + return lhs_page < rhs_page; + } - assert(!lhs_incomplete); - assert(!rhs_incomplete); + assert(!lhs_incomplete); + assert(!rhs_incomplete); - const Settings::Record lhs_record(m_ptrSettings->getPageRecord(lhs_page.imageId())); - const Settings::Record rhs_record(m_ptrSettings->getPageRecord(rhs_page.imageId())); + const Settings::Record lhs_record(m_settings->getPageRecord(lhs_page.imageId())); + const Settings::Record rhs_record(m_settings->getPageRecord(rhs_page.imageId())); - const Params* lhs_params = lhs_record.params(); - const Params* rhs_params = rhs_record.params(); + const Params* lhs_params = lhs_record.params(); + const Params* rhs_params = rhs_record.params(); - int lhs_layout_type = lhs_record.combinedLayoutType(); - if (lhs_params) { - lhs_layout_type = lhs_params->pageLayout().toLayoutType(); - } - if (lhs_layout_type == AUTO_LAYOUT_TYPE) { - lhs_layout_type = 100; // To force it below pages with known layout. - } + int lhs_layout_type = lhs_record.combinedLayoutType(); + if (lhs_params) { + lhs_layout_type = lhs_params->pageLayout().toLayoutType(); + } + if (lhs_layout_type == AUTO_LAYOUT_TYPE) { + lhs_layout_type = 100; // To force it below pages with known layout. + } - int rhs_layout_type = rhs_record.combinedLayoutType(); - if (rhs_params) { - rhs_layout_type = rhs_params->pageLayout().toLayoutType(); - } - if (rhs_layout_type == AUTO_LAYOUT_TYPE) { - rhs_layout_type = 100; // To force it below pages with known layout. - } + int rhs_layout_type = rhs_record.combinedLayoutType(); + if (rhs_params) { + rhs_layout_type = rhs_params->pageLayout().toLayoutType(); + } + if (rhs_layout_type == AUTO_LAYOUT_TYPE) { + rhs_layout_type = 100; // To force it below pages with known layout. + } - if (lhs_layout_type == rhs_layout_type) { - return lhs_page < rhs_page; - } else { - return lhs_layout_type < rhs_layout_type; - } + if (lhs_layout_type == rhs_layout_type) { + return lhs_page < rhs_page; + } else { + return lhs_layout_type < rhs_layout_type; + } } // OrderBySplitTypeProvider::precedes } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/OrderBySplitTypeProvider.h b/filters/page_split/OrderBySplitTypeProvider.h index df354fa4b..e8351250d 100644 --- a/filters/page_split/OrderBySplitTypeProvider.h +++ b/filters/page_split/OrderBySplitTypeProvider.h @@ -20,22 +20,22 @@ #ifndef PAGE_SPLIT_ORDER_BY_SPLIT_TYPE_PROVIDER_H_ #define PAGE_SPLIT_ORDER_BY_SPLIT_TYPE_PROVIDER_H_ +#include "PageOrderProvider.h" #include "Settings.h" #include "intrusive_ptr.h" -#include "PageOrderProvider.h" namespace page_split { class OrderBySplitTypeProvider : public PageOrderProvider { -public: - explicit OrderBySplitTypeProvider(intrusive_ptr settings); + public: + explicit OrderBySplitTypeProvider(intrusive_ptr settings); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_settings; }; } // namespace page_split diff --git a/filters/page_split/PageLayout.cpp b/filters/page_split/PageLayout.cpp index a1946bbd0..10c38cd0b 100644 --- a/filters/page_split/PageLayout.cpp +++ b/filters/page_split/PageLayout.cpp @@ -17,311 +17,305 @@ */ #include "PageLayout.h" +#include +#include #include "NumericTraits.h" +#include "ToLineProjector.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" -#include "ToLineProjector.h" #include "imageproc/PolygonUtils.h" -#include -#include using namespace imageproc; namespace page_split { -PageLayout::PageLayout() : m_type(SINGLE_PAGE_UNCUT) { -} +PageLayout::PageLayout() : m_type(SINGLE_PAGE_UNCUT) {} PageLayout::PageLayout(const QRectF& full_rect) - : m_uncutOutline(full_rect), - m_cutter1(full_rect.topLeft(), full_rect.bottomLeft()), - m_cutter2(full_rect.topRight(), full_rect.bottomRight()), - m_type(SINGLE_PAGE_UNCUT) { -} + : m_uncutOutline(full_rect), + m_cutter1(full_rect.topLeft(), full_rect.bottomLeft()), + m_cutter2(full_rect.topRight(), full_rect.bottomRight()), + m_type(SINGLE_PAGE_UNCUT) {} PageLayout::PageLayout(const QRectF& full_rect, const QLineF& cutter1, const QLineF& cutter2) - : m_uncutOutline(full_rect), m_cutter1(cutter1), m_cutter2(cutter2), m_type(SINGLE_PAGE_CUT) { -} + : m_uncutOutline(full_rect), m_cutter1(cutter1), m_cutter2(cutter2), m_type(SINGLE_PAGE_CUT) {} PageLayout::PageLayout(const QRectF full_rect, const QLineF& split_line) - : m_uncutOutline(full_rect), m_cutter1(split_line), m_type(TWO_PAGES) { -} + : m_uncutOutline(full_rect), m_cutter1(split_line), m_type(TWO_PAGES) {} PageLayout::PageLayout(const QPolygonF& outline, const QLineF& cutter1, const QLineF& cutter2, Type type) - : m_uncutOutline(outline), m_cutter1(cutter1), m_cutter2(cutter2), m_type(type) { -} + : m_uncutOutline(outline), m_cutter1(cutter1), m_cutter2(cutter2), m_type(type) {} PageLayout::PageLayout(const QDomElement& layout_el) - : m_uncutOutline(XmlUnmarshaller::polygonF(layout_el.namedItem("outline").toElement())), - m_type(typeFromString(layout_el.attribute("type"))), - m_cutter1(XmlUnmarshaller::lineF(layout_el.namedItem("cutter1").toElement())), - m_cutter2(XmlUnmarshaller::lineF(layout_el.namedItem("cutter2").toElement())) { -} + : m_uncutOutline(XmlUnmarshaller::polygonF(layout_el.namedItem("outline").toElement())), + m_type(typeFromString(layout_el.attribute("type"))), + m_cutter1(XmlUnmarshaller::lineF(layout_el.namedItem("cutter1").toElement())), + m_cutter2(XmlUnmarshaller::lineF(layout_el.namedItem("cutter2").toElement())) {} void PageLayout::setType(Type type) { - m_type = type; - if (type == TWO_PAGES) { - m_cutter2 = m_cutter1; - } + m_type = type; + if (type == TWO_PAGES) { + m_cutter2 = m_cutter1; + } } void PageLayout::setUncutOutline(const QRectF& outline) { - m_uncutOutline = outline; - if (m_uncutOutline.size() < 4) { - // Empty rect? - return; - } + m_uncutOutline = outline; + if (m_uncutOutline.size() < 4) { + // Empty rect? + return; + } } const QLineF& PageLayout::cutterLine(int idx) const { - assert(idx >= 0 && idx < numCutters()); + assert(idx >= 0 && idx < numCutters()); - return idx == 0 ? m_cutter1 : m_cutter2; + return idx == 0 ? m_cutter1 : m_cutter2; } void PageLayout::setCutterLine(int idx, const QLineF& cutter) { - assert(idx >= 0 && idx < numCutters()); - (idx == 0 ? m_cutter1 : m_cutter2) = cutter; + assert(idx >= 0 && idx < numCutters()); + (idx == 0 ? m_cutter1 : m_cutter2) = cutter; } LayoutType PageLayout::toLayoutType() const { - switch (m_type) { - case SINGLE_PAGE_UNCUT: - return page_split::SINGLE_PAGE_UNCUT; - case SINGLE_PAGE_CUT: - return page_split::PAGE_PLUS_OFFCUT; - case TWO_PAGES: - return page_split::TWO_PAGES; - } - - assert(!"Unreachable"); - - return page_split::SINGLE_PAGE_UNCUT; + switch (m_type) { + case SINGLE_PAGE_UNCUT: + return page_split::SINGLE_PAGE_UNCUT; + case SINGLE_PAGE_CUT: + return page_split::PAGE_PLUS_OFFCUT; + case TWO_PAGES: + return page_split::TWO_PAGES; + } + + assert(!"Unreachable"); + + return page_split::SINGLE_PAGE_UNCUT; } int PageLayout::numCutters() const { - switch (m_type) { - case SINGLE_PAGE_UNCUT: - return 0; - case SINGLE_PAGE_CUT: - return 2; - case TWO_PAGES: - return 1; - } - - assert(!"Unreachable"); - - return 0; + switch (m_type) { + case SINGLE_PAGE_UNCUT: + return 0; + case SINGLE_PAGE_CUT: + return 2; + case TWO_PAGES: + return 1; + } + + assert(!"Unreachable"); + + return 0; } int PageLayout::numSubPages() const { - return m_type == TWO_PAGES ? 2 : 1; + return m_type == TWO_PAGES ? 2 : 1; } QLineF PageLayout::inscribedCutterLine(int idx) const { - assert(idx >= 0 && idx < numCutters()); + assert(idx >= 0 && idx < numCutters()); - if (m_uncutOutline.size() < 4) { - return QLineF(); - } + if (m_uncutOutline.size() < 4) { + return QLineF(); + } - const QLineF raw_line(cutterLine(idx)); - const QPointF origin(raw_line.p1()); - const QPointF line_vec(raw_line.p2() - origin); - - double min_proj = NumericTraits::max(); - double max_proj = NumericTraits::min(); - QPointF min_pt; - QPointF max_pt; - - QPointF p1, p2; - double projection; - - for (int i = 0; i < 4; ++i) { - const QLineF poly_segment(m_uncutOutline[i], m_uncutOutline[(i + 1) & 3]); - QPointF intersection; - if (poly_segment.intersect(raw_line, &intersection) == QLineF::NoIntersection) { - continue; - } - - // Project the intersection point on poly_segment to check - // if it's between its endpoints. - p1 = poly_segment.p2() - poly_segment.p1(); - p2 = intersection - poly_segment.p1(); - projection = p1.x() * p2.x() + p1.y() * p2.y(); - if ((projection < 0) || (projection > p1.x() * p1.x() + p1.y() * p1.y())) { - // Intersection point not on the polygon segment. - continue; - } - // Now project it on raw_line and update min/max projections. - p1 = intersection - origin; - p2 = line_vec; - projection = p1.x() * p2.x() + p1.y() * p2.y(); - if (projection <= min_proj) { - min_proj = projection; - min_pt = intersection; - } - if (projection >= max_proj) { - max_proj = projection; - max_pt = intersection; - } - } + const QLineF raw_line(cutterLine(idx)); + const QPointF origin(raw_line.p1()); + const QPointF line_vec(raw_line.p2() - origin); - QLineF res(min_pt, max_pt); - ensureSameDirection(raw_line, res); + double min_proj = NumericTraits::max(); + double max_proj = NumericTraits::min(); + QPointF min_pt; + QPointF max_pt; - return res; -} // PageLayout::inscribedCutterLine + QPointF p1, p2; + double projection; -QPolygonF PageLayout::singlePageOutline() const { - if (m_uncutOutline.size() < 4) { - return QPolygonF(); + for (int i = 0; i < 4; ++i) { + const QLineF poly_segment(m_uncutOutline[i], m_uncutOutline[(i + 1) & 3]); + QPointF intersection; + if (poly_segment.intersect(raw_line, &intersection) == QLineF::NoIntersection) { + continue; } - switch (m_type) { - case SINGLE_PAGE_UNCUT: - return m_uncutOutline; - case SINGLE_PAGE_CUT: - break; - case TWO_PAGES: - return QPolygonF(); + // Project the intersection point on poly_segment to check + // if it's between its endpoints. + p1 = poly_segment.p2() - poly_segment.p1(); + p2 = intersection - poly_segment.p1(); + projection = p1.x() * p2.x() + p1.y() * p2.y(); + if ((projection < 0) || (projection > p1.x() * p1.x() + p1.y() * p1.y())) { + // Intersection point not on the polygon segment. + continue; + } + // Now project it on raw_line and update min/max projections. + p1 = intersection - origin; + p2 = line_vec; + projection = p1.x() * p2.x() + p1.y() * p2.y(); + if (projection <= min_proj) { + min_proj = projection; + min_pt = intersection; + } + if (projection >= max_proj) { + max_proj = projection; + max_pt = intersection; } + } - const QLineF line1(extendToCover(m_cutter1, m_uncutOutline)); - QLineF line2(extendToCover(m_cutter2, m_uncutOutline)); - ensureSameDirection(line1, line2); - const QLineF reverse_line1(line1.p2(), line1.p1()); - const QLineF reverse_line2(line2.p2(), line2.p1()); + QLineF res(min_pt, max_pt); + ensureSameDirection(raw_line, res); - QPolygonF poly; - poly << line1.p1(); - maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); - poly << line2.p1() << line2.p2(); - maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); - poly << line1.p2(); + return res; +} // PageLayout::inscribedCutterLine - return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); +QPolygonF PageLayout::singlePageOutline() const { + if (m_uncutOutline.size() < 4) { + return QPolygonF(); + } + + switch (m_type) { + case SINGLE_PAGE_UNCUT: + return m_uncutOutline; + case SINGLE_PAGE_CUT: + break; + case TWO_PAGES: + return QPolygonF(); + } + + const QLineF line1(extendToCover(m_cutter1, m_uncutOutline)); + QLineF line2(extendToCover(m_cutter2, m_uncutOutline)); + ensureSameDirection(line1, line2); + const QLineF reverse_line1(line1.p2(), line1.p1()); + const QLineF reverse_line2(line2.p2(), line2.p1()); + + QPolygonF poly; + poly << line1.p1(); + maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); + poly << line2.p1() << line2.p2(); + maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); + poly << line1.p2(); + + return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); } QPolygonF PageLayout::leftPageOutline() const { - if (m_uncutOutline.size() < 4) { - return QPolygonF(); - } - - switch (m_type) { - case SINGLE_PAGE_UNCUT: - case SINGLE_PAGE_CUT: - return QPolygonF(); - case TWO_PAGES: - break; - } - - const QLineF line1(m_uncutOutline[0], m_uncutOutline[3]); - QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); - ensureSameDirection(line1, line2); - const QLineF reverse_line1(line1.p2(), line1.p1()); - const QLineF reverse_line2(line2.p2(), line2.p1()); - - QPolygonF poly; - poly << line1.p1(); - maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); - poly << line2.p1() << line2.p2(); - maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); - poly << line1.p2(); - - return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); + if (m_uncutOutline.size() < 4) { + return QPolygonF(); + } + + switch (m_type) { + case SINGLE_PAGE_UNCUT: + case SINGLE_PAGE_CUT: + return QPolygonF(); + case TWO_PAGES: + break; + } + + const QLineF line1(m_uncutOutline[0], m_uncutOutline[3]); + QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); + ensureSameDirection(line1, line2); + const QLineF reverse_line1(line1.p2(), line1.p1()); + const QLineF reverse_line2(line2.p2(), line2.p1()); + + QPolygonF poly; + poly << line1.p1(); + maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); + poly << line2.p1() << line2.p2(); + maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); + poly << line1.p2(); + + return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); } QPolygonF PageLayout::rightPageOutline() const { - if (m_uncutOutline.size() < 4) { - return QPolygonF(); - } - - switch (m_type) { - case SINGLE_PAGE_UNCUT: - case SINGLE_PAGE_CUT: - return QPolygonF(); - case TWO_PAGES: - break; - } - - const QLineF line1(m_uncutOutline[1], m_uncutOutline[2]); - QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); - ensureSameDirection(line1, line2); - const QLineF reverse_line1(line1.p2(), line1.p1()); - const QLineF reverse_line2(line2.p2(), line2.p1()); - - QPolygonF poly; - poly << line1.p1(); - maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); - poly << line2.p1() << line2.p2(); - maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); - poly << line1.p2(); - - return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); + if (m_uncutOutline.size() < 4) { + return QPolygonF(); + } + + switch (m_type) { + case SINGLE_PAGE_UNCUT: + case SINGLE_PAGE_CUT: + return QPolygonF(); + case TWO_PAGES: + break; + } + + const QLineF line1(m_uncutOutline[1], m_uncutOutline[2]); + QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); + ensureSameDirection(line1, line2); + const QLineF reverse_line1(line1.p2(), line1.p1()); + const QLineF reverse_line2(line2.p2(), line2.p1()); + + QPolygonF poly; + poly << line1.p1(); + maybeAddIntersectionPoint(poly, line1.normalVector(), line2.normalVector()); + poly << line2.p1() << line2.p2(); + maybeAddIntersectionPoint(poly, reverse_line1.normalVector(), reverse_line2.normalVector()); + poly << line1.p2(); + + return PolygonUtils::round(m_uncutOutline).intersected(PolygonUtils::round(poly)); } QPolygonF PageLayout::pageOutline(const PageId::SubPage page) const { - switch (page) { - case PageId::SINGLE_PAGE: - return singlePageOutline(); - case PageId::LEFT_PAGE: - return leftPageOutline(); - case PageId::RIGHT_PAGE: - return rightPageOutline(); - } - - assert(!"Unreachable"); - - return QPolygonF(); + switch (page) { + case PageId::SINGLE_PAGE: + return singlePageOutline(); + case PageId::LEFT_PAGE: + return leftPageOutline(); + case PageId::RIGHT_PAGE: + return rightPageOutline(); + } + + assert(!"Unreachable"); + + return QPolygonF(); } PageLayout PageLayout::transformed(const QTransform& xform) const { - return PageLayout(xform.map(m_uncutOutline), xform.map(m_cutter1), xform.map(m_cutter2), m_type); + return PageLayout(xform.map(m_uncutOutline), xform.map(m_cutter1), xform.map(m_cutter2), m_type); } QDomElement PageLayout::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); - - QDomElement el(doc.createElement(name)); - el.setAttribute("type", typeToString(m_type)); - el.appendChild(marshaller.polygonF(m_uncutOutline, "outline")); - - const int num_cutters = numCutters(); - if (num_cutters > 0) { - el.appendChild(marshaller.lineF(m_cutter1, "cutter1")); - if (num_cutters > 1) { - el.appendChild(marshaller.lineF(m_cutter2, "cutter2")); - } + XmlMarshaller marshaller(doc); + + QDomElement el(doc.createElement(name)); + el.setAttribute("type", typeToString(m_type)); + el.appendChild(marshaller.polygonF(m_uncutOutline, "outline")); + + const int num_cutters = numCutters(); + if (num_cutters > 0) { + el.appendChild(marshaller.lineF(m_cutter1, "cutter1")); + if (num_cutters > 1) { + el.appendChild(marshaller.lineF(m_cutter2, "cutter2")); } + } - return el; + return el; } PageLayout::Type PageLayout::typeFromString(const QString& str) { - if (str == "two-pages") { - return TWO_PAGES; - } else if (str == "single-cut") { - return SINGLE_PAGE_CUT; - } else { // "single-uncut" - return SINGLE_PAGE_UNCUT; - } + if (str == "two-pages") { + return TWO_PAGES; + } else if (str == "single-cut") { + return SINGLE_PAGE_CUT; + } else { // "single-uncut" + return SINGLE_PAGE_UNCUT; + } } QString PageLayout::typeToString(const Type type) { - const char* str = nullptr; - switch (type) { - case SINGLE_PAGE_UNCUT: - str = "single-uncut"; - break; - case SINGLE_PAGE_CUT: - str = "single-cut"; - break; - case TWO_PAGES: - str = "two-pages"; - break; - } - - return QString::fromLatin1(str); + const char* str = nullptr; + switch (type) { + case SINGLE_PAGE_UNCUT: + str = "single-uncut"; + break; + case SINGLE_PAGE_CUT: + str = "single-cut"; + break; + case TWO_PAGES: + str = "two-pages"; + break; + } + + return QString::fromLatin1(str); } /** @@ -331,27 +325,27 @@ QString PageLayout::typeToString(const Type type) { * all the polygon edges it can possibly intersect. */ QLineF PageLayout::extendToCover(const QLineF& line, const QPolygonF& poly) { - if (poly.isEmpty()) { - return line; - } + if (poly.isEmpty()) { + return line; + } - // Project every vertex of the polygon onto the line and take extremas. + // Project every vertex of the polygon onto the line and take extremas. - double min = NumericTraits::max(); - double max = NumericTraits::min(); - const ToLineProjector projector(line); + double min = NumericTraits::max(); + double max = NumericTraits::min(); + const ToLineProjector projector(line); - for (const QPointF& pt : poly) { - const double scalar = projector.projectionScalar(pt); - if (scalar < min) { - min = scalar; - } - if (scalar > max) { - max = scalar; - } + for (const QPointF& pt : poly) { + const double scalar = projector.projectionScalar(pt); + if (scalar < min) { + min = scalar; + } + if (scalar > max) { + max = scalar; } + } - return QLineF(line.pointAt(min), line.pointAt(max)); + return QLineF(line.pointAt(min), line.pointAt(max)); } /** @@ -360,12 +354,12 @@ QLineF PageLayout::extendToCover(const QLineF& line, const QPolygonF& poly) { * (line1.p2() - line1.p1()) and (line2.p2() - line2.p1()). */ void PageLayout::ensureSameDirection(const QLineF& line1, QLineF& line2) { - const QPointF v1(line1.p2() - line1.p1()); - const QPointF v2(line2.p2() - line2.p1()); - const double dot = v1.x() * v2.x() + v1.y() * v2.y(); - if (dot < 0.0) { - line2 = QLineF(line2.p2(), line2.p1()); - } + const QPointF v1(line1.p2() - line1.p1()); + const QPointF v2(line2.p2() - line2.p1()); + const double dot = v1.x() * v2.x() + v1.y() * v2.y(); + if (dot < 0.0) { + line2 = QLineF(line2.p2(), line2.p1()); + } } /** @@ -378,27 +372,27 @@ void PageLayout::ensureSameDirection(const QLineF& line1, QLineF& line2) { * as lines, not line segments. */ void PageLayout::maybeAddIntersectionPoint(QPolygonF& poly, const QLineF& line1, const QLineF& line2) { - QPointF intersection; - if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { - return; - } - - const ToLineProjector projector(QLineF(line1.p1(), line2.p1())); - const double p = projector.projectionScalar(intersection); - if ((p > 0.0) && (p < 1.0)) { - poly << intersection; - } + QPointF intersection; + if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { + return; + } + + const ToLineProjector projector(QLineF(line1.p1(), line2.p1())); + const double p = projector.projectionScalar(intersection); + if ((p > 0.0) && (p < 1.0)) { + poly << intersection; + } } bool PageLayout::isNull() const { - return m_uncutOutline.isEmpty(); + return m_uncutOutline.isEmpty(); } PageLayout::Type PageLayout::type() const { - return m_type; + return m_type; } const QPolygonF& PageLayout::uncutOutline() const { - return m_uncutOutline; + return m_uncutOutline; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/PageLayout.h b/filters/page_split/PageLayout.h index a4ab56155..26f20cf3d 100644 --- a/filters/page_split/PageLayout.h +++ b/filters/page_split/PageLayout.h @@ -19,11 +19,11 @@ #ifndef PAGELAYOUT_H_ #define PAGELAYOUT_H_ -#include "PageId.h" -#include "LayoutType.h" -#include #include +#include #include +#include "LayoutType.h" +#include "PageId.h" class QRectF; class QTransform; @@ -46,150 +46,150 @@ namespace page_split { * area is the only thing we care about. */ class PageLayout { -public: - enum Type { SINGLE_PAGE_UNCUT, SINGLE_PAGE_CUT, TWO_PAGES }; - - /** - * \brief Constructs a null layout. - */ - PageLayout(); - - /** - * \brief Constructs a SINGLE_PAGE_UNCUT layout. - */ - explicit PageLayout(const QRectF& full_rect); - - /** - * \brief Constructs a SINGLE_PAGE_CUT layout. - */ - PageLayout(const QRectF& full_rect, const QLineF& cutter1, const QLineF& cutter2); - - /** - * \brief Constructs a TWO_PAGES layout. - */ - PageLayout(QRectF full_rect, const QLineF& split_line); - - /** - * \brief Construct a page layout based on XML data. - */ - explicit PageLayout(const QDomElement& layout_el); - - bool isNull() const; - - Type type() const; - - /** - * \brief Sets layout type and ensures the internal state - * is consistent with the new type. - */ - void setType(Type type); - - LayoutType toLayoutType() const; - - const QPolygonF& uncutOutline() const; - - /** - * We don't provide a method to set a polygon, but only a rectangle - * because we want to make sure the polygon stored internally corresponds - * to a rectangle. For example, we want to be sure vertices 0 and 3 - * comprise the line corresponding to a left edge of a rectangle. - */ - void setUncutOutline(const QRectF& outline); - - /** - * \brief Get a cutter line. - * - * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. - * \return The cutter line with *arbitrary* endpoints. - */ - const QLineF& cutterLine(int idx) const; - - /** - * \brief Set a cutter line. - * - * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. - * \param cutter The new cutter line with *arbitrary* endpoints. - */ - void setCutterLine(int idx, const QLineF& cutter); - - /** - * Unlike cutterLine(), which returns a line segment with arbitrary - * endpoints, inscribedCutterLine() returns a line segment with endpoints - * touching the edges of the outline polygon. - * - * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. - * \return The cutter line segment with endpoints touching the outline polygon. - */ - QLineF inscribedCutterLine(int idx) const; - - /** - * \brief Return the number of cutters (0, 1 or 2) for the current layout type. - */ - int numCutters() const; - - /** - * \brief Get the number of pages (1 or 2) for this layout. - */ - int numSubPages() const; - - /** - * \brief For single page layouts, return the outline of that page, - * otherwise return QPolygonF(). - */ - QPolygonF singlePageOutline() const; - - /** - * \brief For two page layouts, return the outline of the left page, - * otherwise return QPolygonF(). - */ - QPolygonF leftPageOutline() const; - - /** - * \brief For two pages layouts, return the outline of the right page, - * otherwise return QPolygonF(). - */ - QPolygonF rightPageOutline() const; - - /** - * \brief Determines and calls the appropriate *PageOutline() method. - */ - QPolygonF pageOutline(PageId::SubPage page) const; - - /** - * Returns an affine-transformed version of this layout. - */ - PageLayout transformed(const QTransform& xform) const; - - QDomElement toXml(QDomDocument& doc, const QString& name) const; - -private: - PageLayout(const QPolygonF& outline, const QLineF& cutter1, const QLineF& cutter2, Type type); - - static Type typeFromString(const QString& str); - - static QString typeToString(Type type); - - static QLineF extendToCover(const QLineF& line, const QPolygonF& poly); - - static void ensureSameDirection(const QLineF& line1, QLineF& line2); - - static void maybeAddIntersectionPoint(QPolygonF& poly, const QLineF& line1, const QLineF& line2); - - /** - * This polygon always corresponds to a rectangle, unless it's empty. - * It's vertex 0 corresponds to top-left vertex of a rectangle, and - * it goes clockwise from there, ending at vertex 3. - */ - QPolygonF m_uncutOutline; - - /** - * In case of two page layouts, both cutters refer to the split line, - * and both are supposed to be equal. - */ - QLineF m_cutter1; - QLineF m_cutter2; - - Type m_type; + public: + enum Type { SINGLE_PAGE_UNCUT, SINGLE_PAGE_CUT, TWO_PAGES }; + + /** + * \brief Constructs a null layout. + */ + PageLayout(); + + /** + * \brief Constructs a SINGLE_PAGE_UNCUT layout. + */ + explicit PageLayout(const QRectF& full_rect); + + /** + * \brief Constructs a SINGLE_PAGE_CUT layout. + */ + PageLayout(const QRectF& full_rect, const QLineF& cutter1, const QLineF& cutter2); + + /** + * \brief Constructs a TWO_PAGES layout. + */ + PageLayout(QRectF full_rect, const QLineF& split_line); + + /** + * \brief Construct a page layout based on XML data. + */ + explicit PageLayout(const QDomElement& layout_el); + + bool isNull() const; + + Type type() const; + + /** + * \brief Sets layout type and ensures the internal state + * is consistent with the new type. + */ + void setType(Type type); + + LayoutType toLayoutType() const; + + const QPolygonF& uncutOutline() const; + + /** + * We don't provide a method to set a polygon, but only a rectangle + * because we want to make sure the polygon stored internally corresponds + * to a rectangle. For example, we want to be sure vertices 0 and 3 + * comprise the line corresponding to a left edge of a rectangle. + */ + void setUncutOutline(const QRectF& outline); + + /** + * \brief Get a cutter line. + * + * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. + * \return The cutter line with *arbitrary* endpoints. + */ + const QLineF& cutterLine(int idx) const; + + /** + * \brief Set a cutter line. + * + * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. + * \param cutter The new cutter line with *arbitrary* endpoints. + */ + void setCutterLine(int idx, const QLineF& cutter); + + /** + * Unlike cutterLine(), which returns a line segment with arbitrary + * endpoints, inscribedCutterLine() returns a line segment with endpoints + * touching the edges of the outline polygon. + * + * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. + * \return The cutter line segment with endpoints touching the outline polygon. + */ + QLineF inscribedCutterLine(int idx) const; + + /** + * \brief Return the number of cutters (0, 1 or 2) for the current layout type. + */ + int numCutters() const; + + /** + * \brief Get the number of pages (1 or 2) for this layout. + */ + int numSubPages() const; + + /** + * \brief For single page layouts, return the outline of that page, + * otherwise return QPolygonF(). + */ + QPolygonF singlePageOutline() const; + + /** + * \brief For two page layouts, return the outline of the left page, + * otherwise return QPolygonF(). + */ + QPolygonF leftPageOutline() const; + + /** + * \brief For two pages layouts, return the outline of the right page, + * otherwise return QPolygonF(). + */ + QPolygonF rightPageOutline() const; + + /** + * \brief Determines and calls the appropriate *PageOutline() method. + */ + QPolygonF pageOutline(PageId::SubPage page) const; + + /** + * Returns an affine-transformed version of this layout. + */ + PageLayout transformed(const QTransform& xform) const; + + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + private: + PageLayout(const QPolygonF& outline, const QLineF& cutter1, const QLineF& cutter2, Type type); + + static Type typeFromString(const QString& str); + + static QString typeToString(Type type); + + static QLineF extendToCover(const QLineF& line, const QPolygonF& poly); + + static void ensureSameDirection(const QLineF& line1, QLineF& line2); + + static void maybeAddIntersectionPoint(QPolygonF& poly, const QLineF& line1, const QLineF& line2); + + /** + * This polygon always corresponds to a rectangle, unless it's empty. + * It's vertex 0 corresponds to top-left vertex of a rectangle, and + * it goes clockwise from there, ending at vertex 3. + */ + QPolygonF m_uncutOutline; + + /** + * In case of two page layouts, both cutters refer to the split line, + * and both are supposed to be equal. + */ + QLineF m_cutter1; + QLineF m_cutter2; + + Type m_type; }; } // namespace page_split #endif // ifndef PAGELAYOUT_H_ diff --git a/filters/page_split/PageLayoutAdapter.cpp b/filters/page_split/PageLayoutAdapter.cpp index 75ca75004..08063f10a 100644 --- a/filters/page_split/PageLayoutAdapter.cpp +++ b/filters/page_split/PageLayoutAdapter.cpp @@ -4,136 +4,134 @@ namespace page_split { QLineF PageLayoutAdapter::adaptCutter(const QLineF& cutterLine, const QRectF& newRect) { - if (!newRect.isValid() || cutterLine.isNull()) { - return cutterLine; - } + if (!newRect.isValid() || cutterLine.isNull()) { + return cutterLine; + } + + QLineF upperBorder(newRect.topLeft(), newRect.topRight()); + QPointF upperIntersection; + if (upperBorder.intersect(cutterLine, &upperIntersection) == QLineF::NoIntersection) { + return cutterLine; + } + // if intersection is outside the rect + if (upperIntersection.x() < newRect.topLeft().x()) { + upperIntersection.setX(newRect.topLeft().x()); + } else if (upperIntersection.x() > newRect.topRight().x()) { + upperIntersection.setX(newRect.topRight().x()); + } + + QLineF lowerBorder(newRect.bottomLeft(), newRect.bottomRight()); + QPointF lowerIntersection; + if (lowerBorder.intersect(cutterLine, &lowerIntersection) == QLineF::NoIntersection) { + return cutterLine; + } + // if intersection is outside the rect + if (lowerIntersection.x() < newRect.bottomLeft().x()) { + lowerIntersection.setX(newRect.bottomLeft().x()); + } else if (lowerIntersection.x() > newRect.bottomRight().x()) { + lowerIntersection.setX(newRect.bottomRight().x()); + } + + return QLineF(upperIntersection, lowerIntersection); +} - QLineF upperBorder(newRect.topLeft(), newRect.topRight()); - QPointF upperIntersection; - if (upperBorder.intersect(cutterLine, &upperIntersection) == QLineF::NoIntersection) { - return cutterLine; - } - // if intersection is outside the rect - if (upperIntersection.x() < newRect.topLeft().x()) { - upperIntersection.setX(newRect.topLeft().x()); - } else if (upperIntersection.x() > newRect.topRight().x()) { - upperIntersection.setX(newRect.topRight().x()); - } +QVector PageLayoutAdapter::adaptCutters(const QVector& cuttersList, const QRectF& newRect) { + QVector adaptedCutters; - QLineF lowerBorder(newRect.bottomLeft(), newRect.bottomRight()); - QPointF lowerIntersection; - if (lowerBorder.intersect(cutterLine, &lowerIntersection) == QLineF::NoIntersection) { - return cutterLine; - } - // if intersection is outside the rect - if (lowerIntersection.x() < newRect.bottomLeft().x()) { - lowerIntersection.setX(newRect.bottomLeft().x()); - } else if (lowerIntersection.x() > newRect.bottomRight().x()) { - lowerIntersection.setX(newRect.bottomRight().x()); - } + for (QLineF cutter : cuttersList) { + QLineF adaptedCutter = adaptCutter(cutter, newRect); + adaptedCutters.append(adaptedCutter); + } - return QLineF(upperIntersection, lowerIntersection); -} + std::sort(adaptedCutters.begin(), adaptedCutters.end(), + [](const QLineF& line1, const QLineF& line2) -> bool { return line1.x1() < line2.x1(); }); -std::unique_ptr> PageLayoutAdapter::adaptCutters(const QVector& cuttersList, - const QRectF& newRect) { - auto adaptedCutters = std::make_unique>(); + // checking whether the cutters intersect each other inside the new rect, and if so fixing that + const qreal upperBound = newRect.top(); + const qreal lowerBound = newRect.bottom(); + for (int i = 1; i < adaptedCutters.size(); i++) { + QPointF intersection; + QLineF cutterLeft = adaptedCutters.at(i - 1); + QLineF cutterRight = adaptedCutters.at(i); - for (QLineF cutter : cuttersList) { - QLineF adaptedCutter = adaptCutter(cutter, newRect); - adaptedCutters->append(adaptedCutter); + if (cutterLeft.intersect(cutterRight, &intersection) == QLineF::NoIntersection) { + continue; } - std::sort(adaptedCutters->begin(), adaptedCutters->end(), - [](const QLineF& line1, const QLineF& line2) -> bool { return line1.x1() < line2.x1(); }); - - // checking whether the cutters intersect each other inside the new rect, and if so fixing that - const qreal upperBound = newRect.top(); - const qreal lowerBound = newRect.bottom(); - for (int i = 1; i < adaptedCutters->size(); i++) { - QPointF intersection; - QLineF cutterLeft = adaptedCutters->at(i - 1); - QLineF cutterRight = adaptedCutters->at(i); - - if (cutterLeft.intersect(cutterRight, &intersection) == QLineF::NoIntersection) { - continue; - } - - if ((intersection.y() < lowerBound) && (intersection.y() > upperBound)) { - if ((lowerBound - intersection.y()) <= (lowerBound - upperBound) / 2) { - qreal newY = lowerBound; - cutterLeft.setP2(QPointF(intersection.x(), newY)); - cutterRight.setP2(QPointF(intersection.x(), newY)); - } else { - qreal newY = upperBound; - cutterLeft.setP1(QPointF(intersection.x(), newY)); - cutterRight.setP1(QPointF(intersection.x(), newY)); - } - adaptedCutters->replace(i - 1, cutterLeft); - adaptedCutters->replace(i, cutterRight); - } + if ((intersection.y() < lowerBound) && (intersection.y() > upperBound)) { + if ((lowerBound - intersection.y()) <= (lowerBound - upperBound) / 2) { + qreal newY = lowerBound; + cutterLeft.setP2(QPointF(intersection.x(), newY)); + cutterRight.setP2(QPointF(intersection.x(), newY)); + } else { + qreal newY = upperBound; + cutterLeft.setP1(QPointF(intersection.x(), newY)); + cutterRight.setP1(QPointF(intersection.x(), newY)); + } + adaptedCutters.replace(i - 1, cutterLeft); + adaptedCutters.replace(i, cutterRight); } + } - return adaptedCutters; + return adaptedCutters; } void PageLayoutAdapter::correctPageLayoutType(PageLayout* layout) { - const QRectF outline = layout->uncutOutline().boundingRect().toRect(); - - if (layout->type() == PageLayout::SINGLE_PAGE_CUT) { - QLineF cutterLine1 = layout->cutterLine(0).toLine(); - QLineF cutterLine2 = layout->cutterLine(1).toLine(); - - // if both cutter lines match left or right bound - if (((cutterLine1.x1() == cutterLine1.x2()) - && ((cutterLine1.x1() == outline.left()) || (cutterLine1.x1() == outline.right()))) - && ((cutterLine2.x1() == cutterLine2.x2()) - && ((cutterLine2.x1() == outline.left()) || (cutterLine2.x1() == outline.right())))) { - layout->setType(PageLayout::SINGLE_PAGE_UNCUT); - } - - // if cutter lines match or intersect inside outline (not valid) - QPointF intersection; - QLineF::IntersectType intersectType = cutterLine1.intersect(cutterLine2, &intersection); - if (((intersectType != QLineF::NoIntersection) - && (((intersection.y() > outline.top()) && (intersection.y() < outline.bottom())))) - || ((intersectType == QLineF::NoIntersection) && (cutterLine1.pointAt(0) == cutterLine2.pointAt(0)))) { - layout->setType(PageLayout::SINGLE_PAGE_UNCUT); - } + const QRectF outline = layout->uncutOutline().boundingRect().toRect(); + + if (layout->type() == PageLayout::SINGLE_PAGE_CUT) { + QLineF cutterLine1 = layout->cutterLine(0).toLine(); + QLineF cutterLine2 = layout->cutterLine(1).toLine(); + + // if both cutter lines match left or right bound + if (((cutterLine1.x1() == cutterLine1.x2()) + && ((cutterLine1.x1() == outline.left()) || (cutterLine1.x1() == outline.right()))) + && ((cutterLine2.x1() == cutterLine2.x2()) + && ((cutterLine2.x1() == outline.left()) || (cutterLine2.x1() == outline.right())))) { + layout->setType(PageLayout::SINGLE_PAGE_UNCUT); } - if (layout->type() == PageLayout::TWO_PAGES) { - QLineF cutterLine1 = layout->cutterLine(0).toLine(); - - // if the cutter line matches left or right bound - if ((cutterLine1.x1() == cutterLine1.x2()) - && ((cutterLine1.x1() == outline.left()) || (cutterLine1.x1() == outline.right()))) { - layout->setType(PageLayout::SINGLE_PAGE_UNCUT); - } + // if cutter lines match or intersect inside outline (not valid) + QPointF intersection; + QLineF::IntersectType intersectType = cutterLine1.intersect(cutterLine2, &intersection); + if (((intersectType != QLineF::NoIntersection) + && (((intersection.y() > outline.top()) && (intersection.y() < outline.bottom())))) + || ((intersectType == QLineF::NoIntersection) && (cutterLine1.pointAt(0) == cutterLine2.pointAt(0)))) { + layout->setType(PageLayout::SINGLE_PAGE_UNCUT); } -} + } -std::unique_ptr PageLayoutAdapter::adaptPageLayout(const PageLayout& pageLayout, const QRectF& outline) { - if (pageLayout.uncutOutline().boundingRect() == outline) { - return std::make_unique(pageLayout); - } + if (layout->type() == PageLayout::TWO_PAGES) { + QLineF cutterLine1 = layout->cutterLine(0).toLine(); - std::unique_ptr newPageLayout; - - if (pageLayout.type() == PageLayout::SINGLE_PAGE_CUT) { - std::unique_ptr> adaptedCutters = PageLayoutAdapter::adaptCutters( - QVector{pageLayout.cutterLine(0), pageLayout.cutterLine(1)}, outline); - newPageLayout = std::make_unique(outline, adaptedCutters->at(0), adaptedCutters->at(1)); - correctPageLayoutType(newPageLayout.get()); - } else if (pageLayout.type() == PageLayout::TWO_PAGES) { - QLineF adaptedCutter = PageLayoutAdapter::adaptCutter(pageLayout.cutterLine(0), outline); - newPageLayout = std::make_unique(outline, adaptedCutter); - correctPageLayoutType(newPageLayout.get()); - } else { - newPageLayout = std::make_unique(outline); + // if the cutter line matches left or right bound + if ((cutterLine1.x1() == cutterLine1.x2()) + && ((cutterLine1.x1() == outline.left()) || (cutterLine1.x1() == outline.right()))) { + layout->setType(PageLayout::SINGLE_PAGE_UNCUT); } - - return newPageLayout; + } } +PageLayout PageLayoutAdapter::adaptPageLayout(const PageLayout& pageLayout, const QRectF& outline) { + if (pageLayout.uncutOutline().boundingRect() == outline) { + return pageLayout; + } + + PageLayout newPageLayout; + + if (pageLayout.type() == PageLayout::SINGLE_PAGE_CUT) { + const QVector adaptedCutters + = PageLayoutAdapter::adaptCutters({pageLayout.cutterLine(0), pageLayout.cutterLine(1)}, outline); + newPageLayout = PageLayout(outline, adaptedCutters.at(0), adaptedCutters.at(1)); + correctPageLayoutType(&newPageLayout); + } else if (pageLayout.type() == PageLayout::TWO_PAGES) { + QLineF adaptedCutter = PageLayoutAdapter::adaptCutter(pageLayout.cutterLine(0), outline); + newPageLayout = PageLayout(outline, adaptedCutter); + correctPageLayoutType(&newPageLayout); + } else { + newPageLayout = PageLayout(outline); + } + + return newPageLayout; +} } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/PageLayoutAdapter.h b/filters/page_split/PageLayoutAdapter.h index 67e538c1b..0556c616a 100644 --- a/filters/page_split/PageLayoutAdapter.h +++ b/filters/page_split/PageLayoutAdapter.h @@ -10,39 +10,37 @@ namespace page_split { class PageLayoutAdapter { -public: - /** - * Creates the new page layout from the given with another outline adapting the cutters - */ - static std::unique_ptr adaptPageLayout(const PageLayout& pageLayout, const QRectF& outline); - - /** - * Correct page layout type in place, depending on cutters. - */ - static void correctPageLayoutType(PageLayout* layout); - -private: - /** - * Adapt the cutter line for the new outline. - * - * @param cutterLine the cutter line to be adapted. - * @param newRect the new outline. - * - * @return the adapted cutter line, where QLineF::p1() belongs the upper bound of the new outline, - * and QLineF::p2() does the lower one. - */ - static QLineF adaptCutter(const QLineF& cutterLine, const QRectF& newRect); - - - /** - * Adapt the cutter lines for the new outline. - * - * @param cutterLine the list of the cutter lines to be adapted. - * @param newRect the new outline. - * - * @return the list of the adapted cutter lines. - */ - static std::unique_ptr> adaptCutters(const QVector& cuttersList, const QRectF& newRect); + public: + /** + * Creates the new page layout from the given with another outline adapting the cutters + */ + static PageLayout adaptPageLayout(const PageLayout& pageLayout, const QRectF& outline); + + /** + * Corrects page layout type in place, depending on cutters. + */ + static void correctPageLayoutType(PageLayout* layout); + + private: + /** + * Adapts the cutter line for the new outline. + * + * @param cutterLine the cutter line to be adapted. + * @param newRect the new outline. + * @return the adapted cutter line, where QLineF::p1() belongs the upper bound of the new outline, + * and QLineF::p2() does the lower one. + */ + static QLineF adaptCutter(const QLineF& cutterLine, const QRectF& newRect); + + + /** + * Adapts the cutter lines for the new outline. + * + * @param cutterLine the list of the cutter lines to be adapted. + * @param newRect the new outline. + * @return the list of the adapted cutter lines. + */ + static QVector adaptCutters(const QVector& cuttersList, const QRectF& newRect); }; } // namespace page_split diff --git a/filters/page_split/PageLayoutEstimator.cpp b/filters/page_split/PageLayoutEstimator.cpp index ce916c1ff..7bb7dab0b 100644 --- a/filters/page_split/PageLayoutEstimator.cpp +++ b/filters/page_split/PageLayoutEstimator.cpp @@ -17,51 +17,49 @@ */ #include "PageLayoutEstimator.h" -#include "PageLayout.h" -#include "OrthogonalRotation.h" -#include "VertLineFinder.h" +#include +#include +#include +#include +#include #include "ContentSpanFinder.h" -#include "ImageMetadata.h" -#include "ProjectPages.h" #include "DebugImages.h" +#include "ImageMetadata.h" #include "ImageTransformation.h" +#include "OrthogonalRotation.h" +#include "PageLayout.h" +#include "ProjectPages.h" +#include "VertLineFinder.h" #include "imageproc/Binarize.h" #include "imageproc/BinaryThreshold.h" -#include "imageproc/Morphology.h" -#include "imageproc/Connectivity.h" -#include "imageproc/SeedFill.h" -#include "imageproc/ReduceThreshold.h" #include "imageproc/ConnComp.h" #include "imageproc/ConnCompEraserExt.h" -#include "imageproc/SkewFinder.h" +#include "imageproc/Connectivity.h" #include "imageproc/Constants.h" -#include "imageproc/RasterOp.h" -#include "imageproc/Shear.h" +#include "imageproc/GrayRasterOp.h" +#include "imageproc/Grayscale.h" +#include "imageproc/Morphology.h" #include "imageproc/OrthogonalRotation.h" +#include "imageproc/PolygonRasterizer.h" +#include "imageproc/RasterOp.h" +#include "imageproc/ReduceThreshold.h" #include "imageproc/Scale.h" +#include "imageproc/SeedFill.h" +#include "imageproc/Shear.h" +#include "imageproc/SkewFinder.h" #include "imageproc/SlicedHistogram.h" #include "imageproc/Transform.h" -#include "imageproc/Grayscale.h" -#include "imageproc/GrayRasterOp.h" -#include "imageproc/PolygonRasterizer.h" -#include -#include -#include -#include -#include namespace page_split { using namespace imageproc; namespace { double lineCenterX(const QLineF& line) { - return 0.5 * (line.p1().x() + line.p2().x()); + return 0.5 * (line.p1().x() + line.p2().x()); } struct CenterComparator { - bool operator()(const QLineF& line1, const QLineF& line2) const { - return lineCenterX(line1) < lineCenterX(line2); - } + bool operator()(const QLineF& line1, const QLineF& line2) const { return lineCenterX(line1) < lineCenterX(line2); } }; /** @@ -82,57 +80,57 @@ std::unique_ptr autoDetectSinglePageLayout(const LayoutType layout_t const GrayImage& gray_downscaled, const QTransform& out_to_downscaled, DebugImages* dbg) { - const double image_center = virtual_image_rect.center().x(); - - // A loop just to be able to break from it. - do { - // This whole branch (loop) leads to SINGLE_PAGE_UNCUT, - // which conflicts with PAGE_PLUS_OFFCUT. - if (layout_type == PAGE_PLUS_OFFCUT) { - break; - } - // If we have a single line close to an edge, - // Or more than one line, with the first and the last - // ones close to an edge, that looks more like - // SINGLE_PAGE_CUT layout. - if (!ltr_lines.empty()) { - const QLineF& first_line = ltr_lines.front(); - const double line_center = lineCenterX(first_line); - if (std::fabs(image_center - line_center) > 0.65 * image_center) { - break; - } - } - if (ltr_lines.size() > 1) { - const QLineF& last_line = ltr_lines.back(); - const double line_center = lineCenterX(last_line); - if (std::fabs(image_center - line_center) > 0.65 * image_center) { - break; - } - } + const double image_center = virtual_image_rect.center().x(); - // Return a SINGLE_PAGE_UNCUT layout. - return std::make_unique(virtual_image_rect); - } while (false); + // A loop just to be able to break from it. + do { + // This whole branch (loop) leads to SINGLE_PAGE_UNCUT, + // which conflicts with PAGE_PLUS_OFFCUT. + if (layout_type == PAGE_PLUS_OFFCUT) { + break; + } + // If we have a single line close to an edge, + // Or more than one line, with the first and the last + // ones close to an edge, that looks more like + // SINGLE_PAGE_CUT layout. + if (!ltr_lines.empty()) { + const QLineF& first_line = ltr_lines.front(); + const double line_center = lineCenterX(first_line); + if (std::fabs(image_center - line_center) > 0.65 * image_center) { + break; + } + } + if (ltr_lines.size() > 1) { + const QLineF& last_line = ltr_lines.back(); + const double line_center = lineCenterX(last_line); + if (std::fabs(image_center - line_center) > 0.65 * image_center) { + break; + } + } - if (ltr_lines.empty()) { - // Impossible to detect the layout type. - return nullptr; - } else if (ltr_lines.size() > 1) { - return std::make_unique(virtual_image_rect, ltr_lines.front(), ltr_lines.back()); + // Return a SINGLE_PAGE_UNCUT layout. + return std::make_unique(virtual_image_rect); + } while (false); + + if (ltr_lines.empty()) { + // Impossible to detect the layout type. + return nullptr; + } else if (ltr_lines.size() > 1) { + return std::make_unique(virtual_image_rect, ltr_lines.front(), ltr_lines.back()); + } else { + assert(ltr_lines.size() == 1); + const QLineF& line = ltr_lines.front(); + const double line_center = lineCenterX(line); + if (line_center < image_center) { + const QLineF right_line(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); + + return std::make_unique(virtual_image_rect, line, right_line); } else { - assert(ltr_lines.size() == 1); - const QLineF& line = ltr_lines.front(); - const double line_center = lineCenterX(line); - if (line_center < image_center) { - const QLineF right_line(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); - - return std::make_unique(virtual_image_rect, line, right_line); - } else { - const QLineF left_line(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); - - return std::make_unique(virtual_image_rect, left_line, line); - } + const QLineF left_line(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); + + return std::make_unique(virtual_image_rect, left_line, line); } + } } // autoDetectSinglePageLayout /** @@ -144,49 +142,49 @@ std::unique_ptr autoDetectSinglePageLayout(const LayoutType layout_t */ std::unique_ptr autoDetectTwoPageLayout(const std::vector& ltr_lines, const QRectF& virtual_image_rect) { - if (ltr_lines.empty()) { - // Impossible to detect the page layout. - return nullptr; - } else if (ltr_lines.size() == 1) { - return std::make_unique(virtual_image_rect, ltr_lines.front()); + if (ltr_lines.empty()) { + // Impossible to detect the page layout. + return nullptr; + } else if (ltr_lines.size() == 1) { + return std::make_unique(virtual_image_rect, ltr_lines.front()); + } + + // Find the line closest to the center. + const double image_center = virtual_image_rect.center().x(); + double min_distance = std::numeric_limits::max(); + const QLineF* best_line = nullptr; + for (const QLineF& line : ltr_lines) { + const double line_center = lineCenterX(line); + const double distance = std::fabs(line_center - image_center); + if (distance < min_distance) { + min_distance = distance; + best_line = &line; } + } - // Find the line closest to the center. - const double image_center = virtual_image_rect.center().x(); - double min_distance = std::numeric_limits::max(); - const QLineF* best_line = nullptr; - for (const QLineF& line : ltr_lines) { - const double line_center = lineCenterX(line); - const double distance = std::fabs(line_center - image_center); - if (distance < min_distance) { - min_distance = distance; - best_line = &line; - } - } - - return std::make_unique(virtual_image_rect, *best_line); + return std::make_unique(virtual_image_rect, *best_line); } int numPages(const LayoutType layout_type, const ImageTransformation& pre_xform) { - int num_pages = 0; - - switch (layout_type) { - case AUTO_LAYOUT_TYPE: { - const QSize image_size(pre_xform.origRect().size().toSize()); - num_pages = ProjectPages::adviseNumberOfLogicalPages(ImageMetadata(image_size, pre_xform.origDpi()), - pre_xform.preRotation()); - break; - } - case SINGLE_PAGE_UNCUT: - case PAGE_PLUS_OFFCUT: - num_pages = 1; - break; - case TWO_PAGES: - num_pages = 2; - break; + int num_pages = 0; + + switch (layout_type) { + case AUTO_LAYOUT_TYPE: { + const QSize image_size(pre_xform.origRect().size().toSize()); + num_pages = ProjectPages::adviseNumberOfLogicalPages(ImageMetadata(image_size, pre_xform.origDpi()), + pre_xform.preRotation()); + break; } - - return num_pages; + case SINGLE_PAGE_UNCUT: + case PAGE_PLUS_OFFCUT: + num_pages = 1; + break; + case TWO_PAGES: + num_pages = 2; + break; + } + + return num_pages; } } // anonymous namespace @@ -195,38 +193,37 @@ PageLayout PageLayoutEstimator::estimatePageLayout(const LayoutType layout_type, const ImageTransformation& pre_xform, const BinaryThreshold bw_threshold, DebugImages* const dbg) { - if (layout_type == SINGLE_PAGE_UNCUT) { - return PageLayout(pre_xform.resultingRect()); - } + if (layout_type == SINGLE_PAGE_UNCUT) { + return PageLayout(pre_xform.resultingRect()); + } - std::unique_ptr layout(tryCutAtFoldingLine(layout_type, input, pre_xform, dbg)); - if (layout) { - return *layout; - } + std::unique_ptr layout(tryCutAtFoldingLine(layout_type, input, pre_xform, dbg)); + if (layout) { + return *layout; + } - return cutAtWhitespace(layout_type, input, pre_xform, bw_threshold, dbg); + return cutAtWhitespace(layout_type, input, pre_xform, bw_threshold, dbg); } namespace { class BadTwoPageSplitter { -public: - explicit BadTwoPageSplitter(double image_width) - : m_imageCenter(0.5 * image_width), m_distFromCenterThreshold(0.6 * m_imageCenter) { - } - - /** - * Returns true if the line is too close to an edge - * to be the line splitting two pages - */ - bool operator()(const QLineF& line) { - const double dist = std::fabs(lineCenterX(line) - m_imageCenter); - - return dist > m_distFromCenterThreshold; - } - -private: - double m_imageCenter; - double m_distFromCenterThreshold; + public: + explicit BadTwoPageSplitter(double image_width) + : m_imageCenter(0.5 * image_width), m_distFromCenterThreshold(0.6 * m_imageCenter) {} + + /** + * Returns true if the line is too close to an edge + * to be the line splitting two pages + */ + bool operator()(const QLineF& line) { + const double dist = std::fabs(lineCenterX(line) - m_imageCenter); + + return dist > m_distFromCenterThreshold; + } + + private: + double m_imageCenter; + double m_distFromCenterThreshold; }; } // anonymous namespace @@ -249,62 +246,61 @@ std::unique_ptr PageLayoutEstimator::tryCutAtFoldingLine(const Layou const QImage& input, const ImageTransformation& pre_xform, DebugImages* const dbg) { - const int num_pages = numPages(layout_type, pre_xform); - - GrayImage gray_downscaled; - QTransform out_to_downscaled; - - const int max_lines = 8; - std::vector lines(VertLineFinder::findLines(input, pre_xform, max_lines, dbg, - num_pages == 1 ? &gray_downscaled : nullptr, - num_pages == 1 ? &out_to_downscaled : nullptr)); - - std::sort(lines.begin(), lines.end(), CenterComparator()); - - const QRectF virtual_image_rect(pre_xform.transform().mapRect(input.rect())); - const QPointF center(virtual_image_rect.center()); - - if (num_pages == 1) { - // If all of the lines are close to one of the edges, - // that means they can't be the edges of a pages, - // so we take only one of them, the one closest to - // the center. - while (lines.size() > 1) { // just to be able to break from it. - const QLineF left_line(lines.front()); - const QLineF right_line(lines.back()); - const double threshold = 0.3 * center.x(); - double left_dist = center.x() - lineCenterX(left_line); - double right_dist = center.x() - lineCenterX(right_line); - if ((left_dist < 0) != (right_dist < 0)) { - // They are from the opposite sides - // from the center line. - break; - } - - left_dist = std::fabs(left_dist); - right_dist = std::fabs(right_dist); - if ((left_dist < threshold) || (right_dist < threshold)) { - // At least one of them is relatively close - // to the center. - break; - } - - lines.clear(); - lines.push_back(left_dist < right_dist ? left_line : right_line); - break; - } + const int num_pages = numPages(layout_type, pre_xform); + + GrayImage gray_downscaled; + QTransform out_to_downscaled; + + const int max_lines = 8; + std::vector lines(VertLineFinder::findLines(input, pre_xform, max_lines, dbg, + num_pages == 1 ? &gray_downscaled : nullptr, + num_pages == 1 ? &out_to_downscaled : nullptr)); + + std::sort(lines.begin(), lines.end(), CenterComparator()); + + const QRectF virtual_image_rect(pre_xform.transform().mapRect(input.rect())); + const QPointF center(virtual_image_rect.center()); + + if (num_pages == 1) { + // If all of the lines are close to one of the edges, + // that means they can't be the edges of a pages, + // so we take only one of them, the one closest to + // the center. + while (lines.size() > 1) { // just to be able to break from it. + const QLineF left_line(lines.front()); + const QLineF right_line(lines.back()); + const double threshold = 0.3 * center.x(); + double left_dist = center.x() - lineCenterX(left_line); + double right_dist = center.x() - lineCenterX(right_line); + if ((left_dist < 0) != (right_dist < 0)) { + // They are from the opposite sides + // from the center line. + break; + } + + left_dist = std::fabs(left_dist); + right_dist = std::fabs(right_dist); + if ((left_dist < threshold) || (right_dist < threshold)) { + // At least one of them is relatively close + // to the center. + break; + } + + lines.clear(); + lines.push_back(left_dist < right_dist ? left_line : right_line); + break; + } - return autoDetectSinglePageLayout(layout_type, lines, virtual_image_rect, gray_downscaled, out_to_downscaled, - dbg); - } else { - assert(num_pages == 2); - // In two page mode we ignore the lines that are too close - // to the edge. - lines.erase(std::remove_if(lines.begin(), lines.end(), BadTwoPageSplitter(virtual_image_rect.width())), - lines.end()); + return autoDetectSinglePageLayout(layout_type, lines, virtual_image_rect, gray_downscaled, out_to_downscaled, dbg); + } else { + assert(num_pages == 2); + // In two page mode we ignore the lines that are too close + // to the edge. + lines.erase(std::remove_if(lines.begin(), lines.end(), BadTwoPageSplitter(virtual_image_rect.width())), + lines.end()); - return autoDetectTwoPageLayout(lines, virtual_image_rect); - } + return autoDetectTwoPageLayout(lines, virtual_image_rect); + } } // PageLayoutEstimator::tryCutAtFoldingLine /** @@ -327,69 +323,69 @@ PageLayout PageLayoutEstimator::cutAtWhitespace(const LayoutType layout_type, const ImageTransformation& pre_xform, const BinaryThreshold bw_threshold, DebugImages* const dbg) { - QTransform xform; - - // Convert to B/W and rotate. - BinaryImage img(to300DpiBinary(input, xform, bw_threshold)); - // Note: here we assume the only transformation applied - // to the input image is orthogonal rotation. - img = orthogonalRotation(img, pre_xform.preRotation().toDegrees()); - if (dbg) { - dbg->add(img, "bw300"); + QTransform xform; + + // Convert to B/W and rotate. + BinaryImage img(to300DpiBinary(input, xform, bw_threshold)); + // Note: here we assume the only transformation applied + // to the input image is orthogonal rotation. + img = orthogonalRotation(img, pre_xform.preRotation().toDegrees()); + if (dbg) { + dbg->add(img, "bw300"); + } + + img = removeGarbageAnd2xDownscale(img, dbg); + xform.scale(0.5, 0.5); + if (dbg) { + dbg->add(img, "no_garbage"); + } + + // From now on we work with 150 dpi images. + + const bool left_offcut = checkForLeftOffcut(img); + const bool right_offcut = checkForRightOffcut(img); + + SkewFinder skew_finder; + // We work with 150dpi image, so no further reduction. + skew_finder.setCoarseReduction(0); + skew_finder.setFineReduction(0); + skew_finder.setDesiredAccuracy(0.5); // fine accuracy is not required. + const Skew skew(skew_finder.findSkew(img)); + if ((skew.angle() != 0.0) && (skew.confidence() >= Skew::GOOD_CONFIDENCE)) { + const int w = img.width(); + const int h = img.height(); + const double angle_deg = skew.angle(); + const double tg = std::tan(angle_deg * constants::DEG2RAD); + + const auto margin = (int) std::ceil(std::fabs(0.5 * h * tg)); + const int new_width = w - margin * 2; + if (new_width > 0) { + hShearInPlace(img, tg, 0.5 * h, WHITE); + BinaryImage new_img(new_width, h); + rasterOp(new_img, new_img.rect(), img, QPoint(margin, 0)); + img.swap(new_img); + if (dbg) { + dbg->add(img, "shear_applied"); + } + + QTransform t1; + t1.translate(-0.5 * w, -0.5 * h); + QTransform t2; + t2.shear(tg, 0.0); + QTransform t3; + t3.translate(0.5 * w - margin, 0.5 * h); + xform = xform * t1 * t2 * t3; } + } - img = removeGarbageAnd2xDownscale(img, dbg); - xform.scale(0.5, 0.5); - if (dbg) { - dbg->add(img, "no_garbage"); - } + const int num_pages = numPages(layout_type, pre_xform); + const PageLayout layout(cutAtWhitespaceDeskewed150(layout_type, num_pages, img, left_offcut, right_offcut, dbg)); - // From now on we work with 150 dpi images. - - const bool left_offcut = checkForLeftOffcut(img); - const bool right_offcut = checkForRightOffcut(img); - - SkewFinder skew_finder; - // We work with 150dpi image, so no further reduction. - skew_finder.setCoarseReduction(0); - skew_finder.setFineReduction(0); - skew_finder.setDesiredAccuracy(0.5); // fine accuracy is not required. - const Skew skew(skew_finder.findSkew(img)); - if ((skew.angle() != 0.0) && (skew.confidence() >= Skew::GOOD_CONFIDENCE)) { - const int w = img.width(); - const int h = img.height(); - const double angle_deg = skew.angle(); - const double tg = std::tan(angle_deg * constants::DEG2RAD); - - const auto margin = (int) std::ceil(std::fabs(0.5 * h * tg)); - const int new_width = w - margin * 2; - if (new_width > 0) { - hShearInPlace(img, tg, 0.5 * h, WHITE); - BinaryImage new_img(new_width, h); - rasterOp(new_img, new_img.rect(), img, QPoint(margin, 0)); - img.swap(new_img); - if (dbg) { - dbg->add(img, "shear_applied"); - } - - QTransform t1; - t1.translate(-0.5 * w, -0.5 * h); - QTransform t2; - t2.shear(tg, 0.0); - QTransform t3; - t3.translate(0.5 * w - margin, 0.5 * h); - xform = xform * t1 * t2 * t3; - } - } + PageLayout transformed_layout(layout.transformed(xform.inverted())); + // We don't want a skewed outline! + transformed_layout.setUncutOutline(pre_xform.resultingRect()); - const int num_pages = numPages(layout_type, pre_xform); - const PageLayout layout(cutAtWhitespaceDeskewed150(layout_type, num_pages, img, left_offcut, right_offcut, dbg)); - - PageLayout transformed_layout(layout.transformed(xform.inverted())); - // We don't want a skewed outline! - transformed_layout.setUncutOutline(pre_xform.resultingRect()); - - return transformed_layout; + return transformed_layout; } // PageLayoutEstimator::cutAtWhitespace /** @@ -411,196 +407,196 @@ PageLayout PageLayoutEstimator::cutAtWhitespaceDeskewed150(const LayoutType layo const bool left_offcut, const bool right_offcut, DebugImages* dbg) { - const int width = input.width(); - const int height = input.height(); - - BinaryImage cc_img(input.size(), WHITE); - - { - ConnCompEraser cc_eraser(input, CONN8); - ConnComp cc; - while (!(cc = cc_eraser.nextConnComp()).isNull()) { - if ((cc.width() < 5) || (cc.height() < 5)) { - continue; - } - if ((double) cc.height() / cc.width() > 6) { - continue; - } - cc_img.fill(cc.rect(), BLACK); - } - } - - if (dbg) { - dbg->add(cc_img, "cc_img"); + const int width = input.width(); + const int height = input.height(); + + BinaryImage cc_img(input.size(), WHITE); + + { + ConnCompEraser cc_eraser(input, CONN8); + ConnComp cc; + while (!(cc = cc_eraser.nextConnComp()).isNull()) { + if ((cc.width() < 5) || (cc.height() < 5)) { + continue; + } + if ((double) cc.height() / cc.width() > 6) { + continue; + } + cc_img.fill(cc.rect(), BLACK); } - - ContentSpanFinder span_finder; - span_finder.setMinContentWidth(2); - span_finder.setMinWhitespaceWidth(8); - - std::deque spans; - SlicedHistogram hist(cc_img, SlicedHistogram::COLS); - span_finder.find(hist, [&](Span s) { spans.push_back(s); }); - + } + + if (dbg) { + dbg->add(cc_img, "cc_img"); + } + + ContentSpanFinder span_finder; + span_finder.setMinContentWidth(2); + span_finder.setMinWhitespaceWidth(8); + + std::deque spans; + SlicedHistogram hist(cc_img, SlicedHistogram::COLS); + span_finder.find(hist, [&](Span s) { spans.push_back(s); }); + + if (dbg) { + visualizeSpans(*dbg, spans, input, "spans"); + } + + if (num_pages == 1) { + return processContentSpansSinglePage(layout_type, spans, width, height, left_offcut, right_offcut); + } else { + // This helps if we have 2 pages with one page containing nothing + // but a small amount of garbage. + removeInsignificantEdgeSpans(spans); if (dbg) { - visualizeSpans(*dbg, spans, input, "spans"); + visualizeSpans(*dbg, spans, input, "spans_refined"); } - if (num_pages == 1) { - return processContentSpansSinglePage(layout_type, spans, width, height, left_offcut, right_offcut); - } else { - // This helps if we have 2 pages with one page containing nothing - // but a small amount of garbage. - removeInsignificantEdgeSpans(spans); - if (dbg) { - visualizeSpans(*dbg, spans, input, "spans_refined"); - } - - return processContentSpansTwoPages(layout_type, spans, width, height); - } + return processContentSpansTwoPages(layout_type, spans, width, height); + } } // PageLayoutEstimator::cutAtWhitespaceDeskewed150 imageproc::BinaryImage PageLayoutEstimator::to300DpiBinary(const QImage& img, QTransform& xform, const BinaryThreshold binary_threshold) { - const double xfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterX(); - const double yfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterY(); - if ((std::fabs(xfactor - 1.0) < 0.1) && (std::fabs(yfactor - 1.0) < 0.1)) { - return BinaryImage(img, binary_threshold); - } + const double xfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterX(); + const double yfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterY(); + if ((std::fabs(xfactor - 1.0) < 0.1) && (std::fabs(yfactor - 1.0) < 0.1)) { + return BinaryImage(img, binary_threshold); + } - QTransform scale_xform; - scale_xform.scale(xfactor, yfactor); - xform *= scale_xform; - const QSize new_size(std::max(1, (int) std::ceil(xfactor * img.width())), - std::max(1, (int) std::ceil(yfactor * img.height()))); + QTransform scale_xform; + scale_xform.scale(xfactor, yfactor); + xform *= scale_xform; + const QSize new_size(std::max(1, (int) std::ceil(xfactor * img.width())), + std::max(1, (int) std::ceil(yfactor * img.height()))); - const GrayImage new_image(scaleToGray(GrayImage(img), new_size)); + const GrayImage new_image(scaleToGray(GrayImage(img), new_size)); - return BinaryImage(new_image, binary_threshold); + return BinaryImage(new_image, binary_threshold); } BinaryImage PageLayoutEstimator::removeGarbageAnd2xDownscale(const BinaryImage& image, DebugImages* dbg) { - BinaryImage reduced(ReduceThreshold(image)(2)); - if (dbg) { - dbg->add(reduced, "reduced"); - } - // Remove anything not connected to a bar of at least 4 pixels long. - BinaryImage non_garbage_seed(openBrick(reduced, QSize(4, 1))); - BinaryImage non_garbage_seed2(openBrick(reduced, QSize(1, 4))); - rasterOp>(non_garbage_seed, non_garbage_seed2); - non_garbage_seed2.release(); - reduced = seedFill(non_garbage_seed, reduced, CONN8); - non_garbage_seed.release(); - - if (dbg) { - dbg->add(reduced, "garbage_removed"); - } - - BinaryImage hor_seed(openBrick(reduced, QSize(200, 14), BLACK)); - BinaryImage ver_seed(openBrick(reduced, QSize(14, 300), BLACK)); - - rasterOp>(hor_seed, ver_seed); - BinaryImage seed(hor_seed.release()); - ver_seed.release(); - if (dbg) { - dbg->add(seed, "shadows_seed"); - } - - BinaryImage dilated(dilateBrick(reduced, QSize(3, 3))); - - BinaryImage shadows_dilated(seedFill(seed, dilated, CONN8)); - dilated.release(); - if (dbg) { - dbg->add(shadows_dilated, "shadows_dilated"); - } - - rasterOp>(reduced, shadows_dilated); - - return reduced; + BinaryImage reduced(ReduceThreshold(image)(2)); + if (dbg) { + dbg->add(reduced, "reduced"); + } + // Remove anything not connected to a bar of at least 4 pixels long. + BinaryImage non_garbage_seed(openBrick(reduced, QSize(4, 1))); + BinaryImage non_garbage_seed2(openBrick(reduced, QSize(1, 4))); + rasterOp>(non_garbage_seed, non_garbage_seed2); + non_garbage_seed2.release(); + reduced = seedFill(non_garbage_seed, reduced, CONN8); + non_garbage_seed.release(); + + if (dbg) { + dbg->add(reduced, "garbage_removed"); + } + + BinaryImage hor_seed(openBrick(reduced, QSize(200, 14), BLACK)); + BinaryImage ver_seed(openBrick(reduced, QSize(14, 300), BLACK)); + + rasterOp>(hor_seed, ver_seed); + BinaryImage seed(hor_seed.release()); + ver_seed.release(); + if (dbg) { + dbg->add(seed, "shadows_seed"); + } + + BinaryImage dilated(dilateBrick(reduced, QSize(3, 3))); + + BinaryImage shadows_dilated(seedFill(seed, dilated, CONN8)); + dilated.release(); + if (dbg) { + dbg->add(shadows_dilated, "shadows_dilated"); + } + + rasterOp>(reduced, shadows_dilated); + + return reduced; } // PageLayoutEstimator::removeGarbageAnd2xDownscale bool PageLayoutEstimator::checkForLeftOffcut(const BinaryImage& image) { - const int margin = 2; // Some scanners leave garbage near page borders. - const int width = 3; - QRect rect(margin, 0, width, image.height()); - rect.adjust(0, margin, 0, -margin); + const int margin = 2; // Some scanners leave garbage near page borders. + const int width = 3; + QRect rect(margin, 0, width, image.height()); + rect.adjust(0, margin, 0, -margin); - return image.countBlackPixels(rect) != 0; + return image.countBlackPixels(rect) != 0; } bool PageLayoutEstimator::checkForRightOffcut(const BinaryImage& image) { - const int margin = 2; // Some scanners leave garbage near page borders. - const int width = 3; - QRect rect(image.width() - margin - width, 0, width, image.height()); - rect.adjust(0, margin, 0, -margin); + const int margin = 2; // Some scanners leave garbage near page borders. + const int width = 3; + QRect rect(image.width() - margin - width, 0, width, image.height()); + rect.adjust(0, margin, 0, -margin); - return image.countBlackPixels(rect) != 0; + return image.countBlackPixels(rect) != 0; } void PageLayoutEstimator::visualizeSpans(DebugImages& dbg, const std::deque& spans, const BinaryImage& image, const char* label) { - const int height = image.height(); + const int height = image.height(); - QImage spans_img(image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QImage spans_img(image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); - { - QPainter painter(&spans_img); - const QBrush brush(QColor(0xff, 0x00, 0x00, 0x50)); - for (const Span& span : spans) { - const QRect rect(span.begin(), 0, span.width(), height); - painter.fillRect(rect, brush); - } + { + QPainter painter(&spans_img); + const QBrush brush(QColor(0xff, 0x00, 0x00, 0x50)); + for (const Span& span : spans) { + const QRect rect(span.begin(), 0, span.width(), height); + painter.fillRect(rect, brush); } - dbg.add(spans_img, label); + } + dbg.add(spans_img, label); } void PageLayoutEstimator::removeInsignificantEdgeSpans(std::deque& spans) { - if (spans.empty()) { - return; + if (spans.empty()) { + return; + } + // GapInfo.first: the amount of content preceding this gap. + // GapInfo.second: the amount of content following this gap. + typedef std::pair GapInfo; + + std::vector gaps(spans.size() - 1); + + int sum = 0; + for (unsigned i = 0; i < gaps.size(); ++i) { + sum += spans[i].width(); + gaps[i].first = sum; + } + sum = 0; + for (auto i = static_cast(gaps.size() - 1); i >= 0; --i) { + sum += spans[i + 1].width(); + gaps[i].second = sum; + } + const int total = sum + spans[0].width(); + + int may_be_removed = total / 15; + + do { + const Span& first = spans.front(); + const Span& last = spans.back(); + if (&first == &last) { + break; } - // GapInfo.first: the amount of content preceding this gap. - // GapInfo.second: the amount of content following this gap. - typedef std::pair GapInfo; - - std::vector gaps(spans.size() - 1); - - int sum = 0; - for (unsigned i = 0; i < gaps.size(); ++i) { - sum += spans[i].width(); - gaps[i].first = sum; - } - sum = 0; - for (auto i = static_cast(gaps.size() - 1); i >= 0; --i) { - sum += spans[i + 1].width(); - gaps[i].second = sum; + if (first.width() < last.width()) { + if (first.width() > may_be_removed) { + break; + } + may_be_removed -= first.width(); + spans.pop_front(); + } else { + if (last.width() > may_be_removed) { + break; + } + may_be_removed -= last.width(); + spans.pop_back(); } - const int total = sum + spans[0].width(); - - int may_be_removed = total / 15; - - do { - const Span& first = spans.front(); - const Span& last = spans.back(); - if (&first == &last) { - break; - } - if (first.width() < last.width()) { - if (first.width() > may_be_removed) { - break; - } - may_be_removed -= first.width(); - spans.pop_front(); - } else { - if (last.width() > may_be_removed) { - break; - } - may_be_removed -= last.width(); - spans.pop_back(); - } - } while (!spans.empty()); + } while (!spans.empty()); } // PageLayoutEstimator::removeInsignificantEdgeSpans PageLayout PageLayoutEstimator::processContentSpansSinglePage(const LayoutType layout_type, @@ -609,88 +605,88 @@ PageLayout PageLayoutEstimator::processContentSpansSinglePage(const LayoutType l const int height, const bool left_offcut, const bool right_offcut) { - assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == PAGE_PLUS_OFFCUT); - - const QRectF virtual_image_rect(0, 0, width, height); - - // Just to be able to break from it. - while (left_offcut && !right_offcut && layout_type == AUTO_LAYOUT_TYPE) { - double x; - if (spans.empty()) { - x = 0.0; - } else if (spans.front().begin() > 0) { - x = 0.5 * spans.front().begin(); - } else { - if (spans.front().width() > width / 2) { - // Probably it's the content span. - // Maybe we should cut it from the other side. - break; - } else if (spans.size() > 1) { - x = Span(spans[0], spans[1]).center(); - } else { - x = std::min(spans[0].end() + 20, width); - } - } + assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == PAGE_PLUS_OFFCUT); - const QLineF right_line(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); + const QRectF virtual_image_rect(0, 0, width, height); - return PageLayout(virtual_image_rect, vertLine(x), right_line); + // Just to be able to break from it. + while (left_offcut && !right_offcut && layout_type == AUTO_LAYOUT_TYPE) { + double x; + if (spans.empty()) { + x = 0.0; + } else if (spans.front().begin() > 0) { + x = 0.5 * spans.front().begin(); + } else { + if (spans.front().width() > width / 2) { + // Probably it's the content span. + // Maybe we should cut it from the other side. + break; + } else if (spans.size() > 1) { + x = Span(spans[0], spans[1]).center(); + } else { + x = std::min(spans[0].end() + 20, width); + } } - // Just to be able to break from it. - while (right_offcut && !left_offcut && layout_type == AUTO_LAYOUT_TYPE) { - double x; - if (spans.empty()) { - x = width; - } else if (spans.back().end() < width) { - x = Span(spans.back(), width).center(); - } else { - if (spans.back().width() > width / 2) { - // Probably it's the content span. - // Maybe we should cut it from the other side. - break; - } else if (spans.size() > 1) { - x = Span(spans[spans.size() - 2], spans.back()).center(); - } else { - x = std::max(spans.back().begin() - 20, 0); - } - } + const QLineF right_line(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); - const QLineF left_line(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); + return PageLayout(virtual_image_rect, vertLine(x), right_line); + } - return PageLayout(virtual_image_rect, left_line, vertLine(x)); + // Just to be able to break from it. + while (right_offcut && !left_offcut && layout_type == AUTO_LAYOUT_TYPE) { + double x; + if (spans.empty()) { + x = width; + } else if (spans.back().end() < width) { + x = Span(spans.back(), width).center(); + } else { + if (spans.back().width() > width / 2) { + // Probably it's the content span. + // Maybe we should cut it from the other side. + break; + } else if (spans.size() > 1) { + x = Span(spans[spans.size() - 2], spans.back()).center(); + } else { + x = std::max(spans.back().begin() - 20, 0); + } } - if (layout_type == PAGE_PLUS_OFFCUT) { - const QLineF line1(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); - const QLineF line2(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); + const QLineF left_line(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); - return PageLayout(virtual_image_rect, line1, line2); - } else { - // Returning a SINGLE_PAGE_UNCUT layout. - return PageLayout(virtual_image_rect); - } + return PageLayout(virtual_image_rect, left_line, vertLine(x)); + } + + if (layout_type == PAGE_PLUS_OFFCUT) { + const QLineF line1(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); + const QLineF line2(virtual_image_rect.topRight(), virtual_image_rect.bottomRight()); + + return PageLayout(virtual_image_rect, line1, line2); + } else { + // Returning a SINGLE_PAGE_UNCUT layout. + return PageLayout(virtual_image_rect); + } } // PageLayoutEstimator::processContentSpansSinglePage PageLayout PageLayoutEstimator::processContentSpansTwoPages(const LayoutType layout_type, const std::deque& spans, const int width, const int height) { - assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == TWO_PAGES); + assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == TWO_PAGES); - const QRectF virtual_image_rect(0, 0, width, height); + const QRectF virtual_image_rect(0, 0, width, height); - double x; - if (spans.empty()) { - x = 0.5 * width; - } else if (spans.size() == 1) { - return processTwoPagesWithSingleSpan(spans.front(), width, height); - } else { - // GapInfo.first: the amount of content preceding this gap. - // GapInfo.second: the amount of content following this gap. - typedef std::pair GapInfo; + double x; + if (spans.empty()) { + x = 0.5 * width; + } else if (spans.size() == 1) { + return processTwoPagesWithSingleSpan(spans.front(), width, height); + } else { + // GapInfo.first: the amount of content preceding this gap. + // GapInfo.second: the amount of content following this gap. + typedef std::pair GapInfo; - std::vector gaps(spans.size() - 1); + std::vector gaps(spans.size() - 1); #if 0 int sum = 0; for (unsigned i = 0; i < gaps.size(); ++i) { @@ -703,95 +699,95 @@ PageLayout PageLayoutEstimator::processContentSpansTwoPages(const LayoutType lay gaps[i].second = sum; } #else - const int content_begin = spans.front().begin(); - const int content_end = spans.back().end(); - for (unsigned i = 0; i < gaps.size(); ++i) { - gaps[i].first = spans[i].end() - content_begin; - gaps[i].second = content_end - spans[i + 1].begin(); - } + const int content_begin = spans.front().begin(); + const int content_end = spans.back().end(); + for (unsigned i = 0; i < gaps.size(); ++i) { + gaps[i].first = spans[i].end() - content_begin; + gaps[i].second = content_end - spans[i + 1].begin(); + } #endif - int best_gap = 0; - double best_ratio = 0; - for (unsigned i = 0; i < gaps.size(); ++i) { - const double min = std::min(gaps[i].first, gaps[i].second); - const double max = std::max(gaps[i].first, gaps[i].second); - const double ratio = min / max; - if (ratio > best_ratio) { - best_ratio = ratio; - best_gap = i; - } - } - - if (best_ratio < 0.25) { - // Probably one of the pages is just empty. - return processTwoPagesWithSingleSpan(Span(content_begin, content_end), width, height); - } - - const double acceptable_ratio = best_ratio * 0.90; - - int widest_gap = best_gap; - int max_width = Span(spans[best_gap], spans[best_gap + 1]).width(); - for (int i = best_gap - 1; i >= 0; --i) { - const double min = std::min(gaps[i].first, gaps[i].second); - const double max = std::max(gaps[i].first, gaps[i].second); - const double ratio = min / max; - if (ratio < acceptable_ratio) { - break; - } - const int width = Span(spans[i], spans[i + 1]).width(); - if (width > max_width) { - max_width = width; - widest_gap = i; - } - } - for (auto i = static_cast(best_gap + 1); i < gaps.size(); ++i) { - const double min = std::min(gaps[i].first, gaps[i].second); - const double max = std::max(gaps[i].first, gaps[i].second); - const double ratio = min / max; - if (ratio < acceptable_ratio) { - break; - } - const int width = Span(spans[i], spans[i + 1]).width(); - if (width > max_width) { - max_width = width; - widest_gap = i; - } - } - - const Span gap(spans[widest_gap], spans[widest_gap + 1]); - x = gap.center(); + int best_gap = 0; + double best_ratio = 0; + for (unsigned i = 0; i < gaps.size(); ++i) { + const double min = std::min(gaps[i].first, gaps[i].second); + const double max = std::max(gaps[i].first, gaps[i].second); + const double ratio = min / max; + if (ratio > best_ratio) { + best_ratio = ratio; + best_gap = i; + } } - return PageLayout(virtual_image_rect, vertLine(x)); -} // PageLayoutEstimator::processContentSpansTwoPages + if (best_ratio < 0.25) { + // Probably one of the pages is just empty. + return processTwoPagesWithSingleSpan(Span(content_begin, content_end), width, height); + } -PageLayout PageLayoutEstimator::processTwoPagesWithSingleSpan(const Span& span, int width, int height) { - const QRectF virtual_image_rect(0, 0, width, height); + const double acceptable_ratio = best_ratio * 0.90; + + int widest_gap = best_gap; + int max_width = Span(spans[best_gap], spans[best_gap + 1]).width(); + for (int i = best_gap - 1; i >= 0; --i) { + const double min = std::min(gaps[i].first, gaps[i].second); + const double max = std::max(gaps[i].first, gaps[i].second); + const double ratio = min / max; + if (ratio < acceptable_ratio) { + break; + } + const int width = Span(spans[i], spans[i + 1]).width(); + if (width > max_width) { + max_width = width; + widest_gap = i; + } + } + for (auto i = static_cast(best_gap + 1); i < gaps.size(); ++i) { + const double min = std::min(gaps[i].first, gaps[i].second); + const double max = std::max(gaps[i].first, gaps[i].second); + const double ratio = min / max; + if (ratio < acceptable_ratio) { + break; + } + const int width = Span(spans[i], spans[i + 1]).width(); + if (width > max_width) { + max_width = width; + widest_gap = i; + } + } - const double page_center = 0.5 * width; - const double box_center = span.center(); - const double box_half_width = 0.5 * span.width(); - const double distance_to_page_center = std::fabs(page_center - box_center) - box_half_width; + const Span gap(spans[widest_gap], spans[widest_gap + 1]); + x = gap.center(); + } - double x; + return PageLayout(virtual_image_rect, vertLine(x)); +} // PageLayoutEstimator::processContentSpansTwoPages - if (distance_to_page_center > 15) { - x = page_center; +PageLayout PageLayoutEstimator::processTwoPagesWithSingleSpan(const Span& span, int width, int height) { + const QRectF virtual_image_rect(0, 0, width, height); + + const double page_center = 0.5 * width; + const double box_center = span.center(); + const double box_half_width = 0.5 * span.width(); + const double distance_to_page_center = std::fabs(page_center - box_center) - box_half_width; + + double x; + + if (distance_to_page_center > 15) { + x = page_center; + } else { + const Span left_ws(0, span); + const Span right_ws(span, width); + if (left_ws.width() > right_ws.width()) { + x = std::max(0, span.begin() - 15); } else { - const Span left_ws(0, span); - const Span right_ws(span, width); - if (left_ws.width() > right_ws.width()) { - x = std::max(0, span.begin() - 15); - } else { - x = std::min(width, span.end() + 15); - } + x = std::min(width, span.end() + 15); } + } - return PageLayout(virtual_image_rect, vertLine(x)); + return PageLayout(virtual_image_rect, vertLine(x)); } QLineF PageLayoutEstimator::vertLine(double x) { - return QLineF(x, 0.0, x, 1.0); + return QLineF(x, 0.0, x, 1.0); } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/PageLayoutEstimator.h b/filters/page_split/PageLayoutEstimator.h index 81c4fcc90..ea8ff0707 100644 --- a/filters/page_split/PageLayoutEstimator.h +++ b/filters/page_split/PageLayoutEstimator.h @@ -19,11 +19,11 @@ #ifndef PAGE_SPLIT_PAGELAYOUTESTIMATOR_H_ #define PAGE_SPLIT_PAGELAYOUTESTIMATOR_H_ -#include "foundation/VirtualFunction.h" -#include "LayoutType.h" #include #include #include +#include "LayoutType.h" +#include "foundation/VirtualFunction.h" class QRect; class QPoint; @@ -42,80 +42,80 @@ namespace page_split { class PageLayout; class PageLayoutEstimator { -public: - /** - * \brief Estimates the page layout according to the provided layout type. - * - * \param layout_type The type of a layout to detect. If set to - * something other than Rule::AUTO_DETECT, the returned - * layout will have the same type. - * \param input The input image. Will be converted to grayscale unless - * it's already grayscale. - * \param pre_xform The logical transformation applied to the input image. - * The resulting page layout will be in transformed coordinates. - * \param bw_threshold The global binarization threshold for the - * input image. - * \param dbg An optional sink for debugging images. - * \return The estimated PageLayout of type consistent with the - * requested layout type. - */ - static PageLayout estimatePageLayout(LayoutType layout_type, - const QImage& input, - const ImageTransformation& pre_xform, - imageproc::BinaryThreshold bw_threshold, - DebugImages* dbg = nullptr); - -private: - static std::unique_ptr tryCutAtFoldingLine(LayoutType layout_type, - const QImage& input, - const ImageTransformation& pre_xform, - DebugImages* dbg); - - static PageLayout cutAtWhitespace(LayoutType layout_type, - const QImage& input, - const ImageTransformation& pre_xform, - imageproc::BinaryThreshold bw_threshold, - DebugImages* dbg); - - static PageLayout cutAtWhitespaceDeskewed150(LayoutType layout_type, - int num_pages, - const imageproc::BinaryImage& input, - bool left_offcut, - bool right_offcut, - DebugImages* dbg); - - static imageproc::BinaryImage to300DpiBinary(const QImage& img, - QTransform& xform, - imageproc::BinaryThreshold threshold); - - static imageproc::BinaryImage removeGarbageAnd2xDownscale(const imageproc::BinaryImage& image, DebugImages* dbg); - - static bool checkForLeftOffcut(const imageproc::BinaryImage& image); - - static bool checkForRightOffcut(const imageproc::BinaryImage& image); - - static void visualizeSpans(DebugImages& dbg, - const std::deque& spans, - const imageproc::BinaryImage& image, - const char* label); - - static void removeInsignificantEdgeSpans(std::deque& spans); - - static PageLayout processContentSpansSinglePage(LayoutType layout_type, - const std::deque& spans, - int width, - int height, - bool left_offcut, - bool right_offcut); - - static PageLayout processContentSpansTwoPages(LayoutType layout_type, + public: + /** + * \brief Estimates the page layout according to the provided layout type. + * + * \param layout_type The type of a layout to detect. If set to + * something other than Rule::AUTO_DETECT, the returned + * layout will have the same type. + * \param input The input image. Will be converted to grayscale unless + * it's already grayscale. + * \param pre_xform The logical transformation applied to the input image. + * The resulting page layout will be in transformed coordinates. + * \param bw_threshold The global binarization threshold for the + * input image. + * \param dbg An optional sink for debugging images. + * \return The estimated PageLayout of type consistent with the + * requested layout type. + */ + static PageLayout estimatePageLayout(LayoutType layout_type, + const QImage& input, + const ImageTransformation& pre_xform, + imageproc::BinaryThreshold bw_threshold, + DebugImages* dbg = nullptr); + + private: + static std::unique_ptr tryCutAtFoldingLine(LayoutType layout_type, + const QImage& input, + const ImageTransformation& pre_xform, + DebugImages* dbg); + + static PageLayout cutAtWhitespace(LayoutType layout_type, + const QImage& input, + const ImageTransformation& pre_xform, + imageproc::BinaryThreshold bw_threshold, + DebugImages* dbg); + + static PageLayout cutAtWhitespaceDeskewed150(LayoutType layout_type, + int num_pages, + const imageproc::BinaryImage& input, + bool left_offcut, + bool right_offcut, + DebugImages* dbg); + + static imageproc::BinaryImage to300DpiBinary(const QImage& img, + QTransform& xform, + imageproc::BinaryThreshold threshold); + + static imageproc::BinaryImage removeGarbageAnd2xDownscale(const imageproc::BinaryImage& image, DebugImages* dbg); + + static bool checkForLeftOffcut(const imageproc::BinaryImage& image); + + static bool checkForRightOffcut(const imageproc::BinaryImage& image); + + static void visualizeSpans(DebugImages& dbg, + const std::deque& spans, + const imageproc::BinaryImage& image, + const char* label); + + static void removeInsignificantEdgeSpans(std::deque& spans); + + static PageLayout processContentSpansSinglePage(LayoutType layout_type, const std::deque& spans, int width, - int height); + int height, + bool left_offcut, + bool right_offcut); + + static PageLayout processContentSpansTwoPages(LayoutType layout_type, + const std::deque& spans, + int width, + int height); - static PageLayout processTwoPagesWithSingleSpan(const Span& span, int width, int height); + static PageLayout processTwoPagesWithSingleSpan(const Span& span, int width, int height); - static QLineF vertLine(double x); + static QLineF vertLine(double x); }; } // namespace page_split #endif // ifndef PAGE_SPLIT_PAGELAYOUTESTIMATOR_H_ diff --git a/filters/page_split/Params.cpp b/filters/page_split/Params.cpp index 212d7b88b..c6dfcf043 100644 --- a/filters/page_split/Params.cpp +++ b/filters/page_split/Params.cpp @@ -21,47 +21,45 @@ namespace page_split { Params::Params(const PageLayout& layout, const Dependencies& deps, const AutoManualMode split_line_mode) - : m_layout(layout), m_deps(deps), m_splitLineMode(split_line_mode) { -} + : m_layout(layout), m_deps(deps), m_splitLineMode(split_line_mode) {} Params::Params(const QDomElement& el) - : m_layout(el.namedItem("pages").toElement()), - m_deps(el.namedItem("dependencies").toElement()), - m_splitLineMode(el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO) { -} + : m_layout(el.namedItem("pages").toElement()), + m_deps(el.namedItem("dependencies").toElement()), + m_splitLineMode(el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO) {} Params::~Params() = default; QDomElement Params::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.setAttribute("mode", m_splitLineMode == MODE_AUTO ? "auto" : "manual"); - el.appendChild(m_layout.toXml(doc, "pages")); - el.appendChild(m_deps.toXml(doc, "dependencies")); + QDomElement el(doc.createElement(name)); + el.setAttribute("mode", m_splitLineMode == MODE_AUTO ? "auto" : "manual"); + el.appendChild(m_layout.toXml(doc, "pages")); + el.appendChild(m_deps.toXml(doc, "dependencies")); - return el; + return el; } const PageLayout& Params::pageLayout() const { - return m_layout; + return m_layout; } void Params::setPageLayout(const PageLayout& layout) { - m_layout = layout; + m_layout = layout; } const Dependencies& Params::dependencies() const { - return m_deps; + return m_deps; } void Params::setDependencies(const Dependencies& deps) { - m_deps = deps; + m_deps = deps; } AutoManualMode Params::splitLineMode() const { - return m_splitLineMode; + return m_splitLineMode; } void Params::setSplitLineMode(AutoManualMode mode) { - m_splitLineMode = mode; + m_splitLineMode = mode; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/Params.h b/filters/page_split/Params.h index a451b000e..d9fc8bd3b 100644 --- a/filters/page_split/Params.h +++ b/filters/page_split/Params.h @@ -19,44 +19,44 @@ #ifndef PAGE_SPLIT_PARAMS_H_ #define PAGE_SPLIT_PARAMS_H_ -#include "PageLayout.h" -#include "Dependencies.h" -#include "AutoManualMode.h" #include #include +#include "AutoManualMode.h" +#include "Dependencies.h" +#include "PageLayout.h" class QDomDocument; class QDomElement; namespace page_split { class Params { -public: - // Member-wise copying is OK. + public: + // Member-wise copying is OK. - Params(const PageLayout& layout, const Dependencies& deps, AutoManualMode split_line_mode); + Params(const PageLayout& layout, const Dependencies& deps, AutoManualMode split_line_mode); - explicit Params(const QDomElement& el); + explicit Params(const QDomElement& el); - ~Params(); + ~Params(); - const PageLayout& pageLayout() const; + const PageLayout& pageLayout() const; - void setPageLayout(const PageLayout& layout); + void setPageLayout(const PageLayout& layout); - const Dependencies& dependencies() const; + const Dependencies& dependencies() const; - void setDependencies(const Dependencies& deps); + void setDependencies(const Dependencies& deps); - AutoManualMode splitLineMode() const; + AutoManualMode splitLineMode() const; - void setSplitLineMode(AutoManualMode mode); + void setSplitLineMode(AutoManualMode mode); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; -private: - PageLayout m_layout; - Dependencies m_deps; - AutoManualMode m_splitLineMode; + private: + PageLayout m_layout; + Dependencies m_deps; + AutoManualMode m_splitLineMode; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_PARAMS_H_ diff --git a/filters/page_split/Settings.cpp b/filters/page_split/Settings.cpp index e57404b59..192b1686c 100644 --- a/filters/page_split/Settings.cpp +++ b/filters/page_split/Settings.cpp @@ -17,312 +17,307 @@ */ #include "Settings.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" #include +#include "AbstractRelinker.h" +#include "RelinkablePath.h" namespace page_split { -Settings::Settings() : m_defaultLayoutType(AUTO_LAYOUT_TYPE) { -} +Settings::Settings() : m_defaultLayoutType(AUTO_LAYOUT_TYPE) {} Settings::~Settings() = default; void Settings::clear() { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - m_perPageRecords.clear(); - m_defaultLayoutType = AUTO_LAYOUT_TYPE; + m_perPageRecords.clear(); + m_defaultLayoutType = AUTO_LAYOUT_TYPE; } void Settings::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); - PerPageRecords new_records; - - for (const PerPageRecords::value_type& kv : m_perPageRecords) { - const RelinkablePath old_path(kv.first.filePath(), RelinkablePath::File); - ImageId new_image_id(kv.first); - new_image_id.setFilePath(relinker.substitutionPathFor(old_path)); - new_records.insert(PerPageRecords::value_type(new_image_id, kv.second)); - } + QMutexLocker locker(&m_mutex); + PerPageRecords new_records; + + for (const PerPageRecords::value_type& kv : m_perPageRecords) { + const RelinkablePath old_path(kv.first.filePath(), RelinkablePath::File); + ImageId new_image_id(kv.first); + new_image_id.setFilePath(relinker.substitutionPathFor(old_path)); + new_records.insert(PerPageRecords::value_type(new_image_id, kv.second)); + } - m_perPageRecords.swap(new_records); + m_perPageRecords.swap(new_records); } LayoutType Settings::defaultLayoutType() const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return m_defaultLayoutType; + return m_defaultLayoutType; } void Settings::setLayoutTypeForAllPages(const LayoutType layout_type) { - QMutexLocker locker(&m_mutex); - - auto it(m_perPageRecords.begin()); - const auto end(m_perPageRecords.end()); - while (it != end) { - if (it->second.hasLayoutTypeConflict(layout_type)) { - m_perPageRecords.erase(it++); - } else { - it->second.clearLayoutType(); - ++it; - } + QMutexLocker locker(&m_mutex); + + auto it(m_perPageRecords.begin()); + const auto end(m_perPageRecords.end()); + while (it != end) { + if (it->second.hasLayoutTypeConflict(layout_type)) { + m_perPageRecords.erase(it++); + } else { + it->second.clearLayoutType(); + ++it; } + } - m_defaultLayoutType = layout_type; + m_defaultLayoutType = layout_type; } void Settings::setLayoutTypeFor(const LayoutType layout_type, const std::set& pages) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - UpdateAction action; + UpdateAction action; - for (const PageId& page_id : pages) { - updatePageLocked(page_id.imageId(), action); - } + for (const PageId& page_id : pages) { + updatePageLocked(page_id.imageId(), action); + } } Settings::Record Settings::getPageRecord(const ImageId& image_id) const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return getPageRecordLocked(image_id); + return getPageRecordLocked(image_id); } Settings::Record Settings::getPageRecordLocked(const ImageId& image_id) const { - auto it(m_perPageRecords.find(image_id)); - if (it == m_perPageRecords.end()) { - return Record(m_defaultLayoutType); - } else { - return Record(it->second, m_defaultLayoutType); - } + auto it(m_perPageRecords.find(image_id)); + if (it == m_perPageRecords.end()) { + return Record(m_defaultLayoutType); + } else { + return Record(it->second, m_defaultLayoutType); + } } void Settings::updatePage(const ImageId& image_id, const UpdateAction& action) { - QMutexLocker locker(&m_mutex); - updatePageLocked(image_id, action); + QMutexLocker locker(&m_mutex); + updatePageLocked(image_id, action); } void Settings::updatePageLocked(const ImageId& image_id, const UpdateAction& action) { - auto it(m_perPageRecords.find(image_id)); - if (it == m_perPageRecords.end()) { - // No record exists for this page. - - Record record(m_defaultLayoutType); - record.update(action); + auto it(m_perPageRecords.find(image_id)); + if (it == m_perPageRecords.end()) { + // No record exists for this page. - if (record.hasLayoutTypeConflict()) { - record.clearParams(); - } - - if (!record.isNull()) { - m_perPageRecords.insert(it, PerPageRecords::value_type(image_id, record)); - } - } else { - // A record was found. - updatePageLocked(it, action); - } -} - -void Settings::updatePageLocked(const PerPageRecords::iterator it, const UpdateAction& action) { - Record record(it->second, m_defaultLayoutType); + Record record(m_defaultLayoutType); record.update(action); if (record.hasLayoutTypeConflict()) { - record.clearParams(); + record.clearParams(); } - if (record.isNull()) { - m_perPageRecords.erase(it); - } else { - it->second = record; + if (!record.isNull()) { + m_perPageRecords.insert(it, PerPageRecords::value_type(image_id, record)); } + } else { + // A record was found. + updatePageLocked(it, action); + } +} + +void Settings::updatePageLocked(const PerPageRecords::iterator it, const UpdateAction& action) { + Record record(it->second, m_defaultLayoutType); + record.update(action); + + if (record.hasLayoutTypeConflict()) { + record.clearParams(); + } + + if (record.isNull()) { + m_perPageRecords.erase(it); + } else { + it->second = record; + } } Settings::Record Settings::conditionalUpdate(const ImageId& image_id, const UpdateAction& action, bool* conflict) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - auto it(m_perPageRecords.find(image_id)); - if (it == m_perPageRecords.end()) { - // No record exists for this page. + auto it(m_perPageRecords.find(image_id)); + if (it == m_perPageRecords.end()) { + // No record exists for this page. - Record record(m_defaultLayoutType); - record.update(action); + Record record(m_defaultLayoutType); + record.update(action); - if (record.hasLayoutTypeConflict()) { - if (conflict) { - *conflict = true; - } + if (record.hasLayoutTypeConflict()) { + if (conflict) { + *conflict = true; + } - return Record(m_defaultLayoutType); - } + return Record(m_defaultLayoutType); + } - if (!record.isNull()) { - m_perPageRecords.insert(it, PerPageRecords::value_type(image_id, record)); - } + if (!record.isNull()) { + m_perPageRecords.insert(it, PerPageRecords::value_type(image_id, record)); + } - if (conflict) { - *conflict = false; - } + if (conflict) { + *conflict = false; + } - return record; - } else { - // A record was found. + return record; + } else { + // A record was found. - Record record(it->second, m_defaultLayoutType); - record.update(action); + Record record(it->second, m_defaultLayoutType); + record.update(action); - if (record.hasLayoutTypeConflict()) { - if (conflict) { - *conflict = true; - } + if (record.hasLayoutTypeConflict()) { + if (conflict) { + *conflict = true; + } - return Record(it->second, m_defaultLayoutType); - } + return Record(it->second, m_defaultLayoutType); + } - if (conflict) { - *conflict = false; - } + if (conflict) { + *conflict = false; + } - if (record.isNull()) { - m_perPageRecords.erase(it); + if (record.isNull()) { + m_perPageRecords.erase(it); - return Record(m_defaultLayoutType); - } else { - it->second = record; + return Record(m_defaultLayoutType); + } else { + it->second = record; - return record; - } + return record; } + } } // Settings::conditionalUpdate /*======================= Settings::BaseRecord ======================*/ Settings::BaseRecord::BaseRecord() - : m_params(PageLayout(), Dependencies(), MODE_AUTO), - m_layoutType(AUTO_LAYOUT_TYPE), - m_paramsValid(false), - m_layoutTypeValid(false) { -} + : m_params(PageLayout(), Dependencies(), MODE_AUTO), + m_layoutType(AUTO_LAYOUT_TYPE), + m_paramsValid(false), + m_layoutTypeValid(false) {} void Settings::BaseRecord::setParams(const Params& params) { - m_params = params; - m_paramsValid = true; + m_params = params; + m_paramsValid = true; } void Settings::BaseRecord::setLayoutType(const LayoutType layout_type) { - m_layoutType = layout_type; - m_layoutTypeValid = true; + m_layoutType = layout_type; + m_layoutTypeValid = true; } bool Settings::BaseRecord::hasLayoutTypeConflict(const LayoutType layout_type) const { - if (!m_paramsValid) { - // No data - no conflict. - return false; - } + if (!m_paramsValid) { + // No data - no conflict. + return false; + } - if (layout_type == AUTO_LAYOUT_TYPE) { - // This one is compatible with everything. - return false; - } + if (layout_type == AUTO_LAYOUT_TYPE) { + // This one is compatible with everything. + return false; + } - switch (m_params.pageLayout().type()) { - case PageLayout::SINGLE_PAGE_UNCUT: - return layout_type != SINGLE_PAGE_UNCUT; - case PageLayout::SINGLE_PAGE_CUT: - return layout_type != PAGE_PLUS_OFFCUT; - case PageLayout::TWO_PAGES: - return layout_type != TWO_PAGES; - } + switch (m_params.pageLayout().type()) { + case PageLayout::SINGLE_PAGE_UNCUT: + return layout_type != SINGLE_PAGE_UNCUT; + case PageLayout::SINGLE_PAGE_CUT: + return layout_type != PAGE_PLUS_OFFCUT; + case PageLayout::TWO_PAGES: + return layout_type != TWO_PAGES; + } - assert(!"Unreachable"); + assert(!"Unreachable"); - return false; + return false; } const LayoutType* Settings::BaseRecord::layoutType() const { - return m_layoutTypeValid ? &m_layoutType : nullptr; + return m_layoutTypeValid ? &m_layoutType : nullptr; } const Params* Settings::BaseRecord::params() const { - return m_paramsValid ? &m_params : nullptr; + return m_paramsValid ? &m_params : nullptr; } bool Settings::BaseRecord::isNull() const { - return !(m_paramsValid || m_layoutTypeValid); + return !(m_paramsValid || m_layoutTypeValid); } void Settings::BaseRecord::clearParams() { - m_paramsValid = false; + m_paramsValid = false; } void Settings::BaseRecord::clearLayoutType() { - m_layoutTypeValid = false; + m_layoutTypeValid = false; } /*========================= Settings::Record ========================*/ -Settings::Record::Record(const LayoutType default_layout_type) : m_defaultLayoutType(default_layout_type) { -} +Settings::Record::Record(const LayoutType default_layout_type) : m_defaultLayoutType(default_layout_type) {} Settings::Record::Record(const BaseRecord& base_record, const LayoutType default_layout_type) - : BaseRecord(base_record), m_defaultLayoutType(default_layout_type) { -} + : BaseRecord(base_record), m_defaultLayoutType(default_layout_type) {} LayoutType Settings::Record::combinedLayoutType() const { - return m_layoutTypeValid ? m_layoutType : m_defaultLayoutType; + return m_layoutTypeValid ? m_layoutType : m_defaultLayoutType; } void Settings::Record::update(const UpdateAction& action) { - switch (action.m_layoutTypeAction) { - case UpdateAction::SET: - setLayoutType(action.m_layoutType); - break; - case UpdateAction::CLEAR: - clearLayoutType(); - break; - case UpdateAction::DONT_TOUCH: - break; - } - - switch (action.m_paramsAction) { - case UpdateAction::SET: - setParams(action.m_params); - break; - case UpdateAction::CLEAR: - clearParams(); - break; - case UpdateAction::DONT_TOUCH: - break; - } + switch (action.m_layoutTypeAction) { + case UpdateAction::SET: + setLayoutType(action.m_layoutType); + break; + case UpdateAction::CLEAR: + clearLayoutType(); + break; + case UpdateAction::DONT_TOUCH: + break; + } + + switch (action.m_paramsAction) { + case UpdateAction::SET: + setParams(action.m_params); + break; + case UpdateAction::CLEAR: + clearParams(); + break; + case UpdateAction::DONT_TOUCH: + break; + } } bool Settings::Record::hasLayoutTypeConflict() const { - return BaseRecord::hasLayoutTypeConflict(combinedLayoutType()); + return BaseRecord::hasLayoutTypeConflict(combinedLayoutType()); } /*======================= Settings::UpdateAction ======================*/ Settings::UpdateAction::UpdateAction() - : m_params(PageLayout(), Dependencies(), MODE_AUTO), - m_layoutType(AUTO_LAYOUT_TYPE), - m_paramsAction(DONT_TOUCH), - m_layoutTypeAction(DONT_TOUCH) { -} + : m_params(PageLayout(), Dependencies(), MODE_AUTO), + m_layoutType(AUTO_LAYOUT_TYPE), + m_paramsAction(DONT_TOUCH), + m_layoutTypeAction(DONT_TOUCH) {} void Settings::UpdateAction::setLayoutType(const LayoutType layout_type) { - m_layoutType = layout_type; - m_layoutTypeAction = SET; + m_layoutType = layout_type; + m_layoutTypeAction = SET; } void Settings::UpdateAction::clearLayoutType() { - m_layoutTypeAction = CLEAR; + m_layoutTypeAction = CLEAR; } void Settings::UpdateAction::setParams(const Params& params) { - m_params = params; - m_paramsAction = SET; + m_params = params; + m_paramsAction = SET; } void Settings::UpdateAction::clearParams() { - m_paramsAction = CLEAR; + m_paramsAction = CLEAR; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/Settings.h b/filters/page_split/Settings.h index ee6e4e758..9cb63829b 100644 --- a/filters/page_split/Settings.h +++ b/filters/page_split/Settings.h @@ -19,170 +19,170 @@ #ifndef PAGE_SPLIT_SETTINGS_H_ #define PAGE_SPLIT_SETTINGS_H_ -#include "ref_countable.h" -#include "NonCopyable.h" -#include "PageLayout.h" -#include "LayoutType.h" -#include "Params.h" -#include "ImageId.h" -#include "PageId.h" #include #include -#include #include +#include +#include "ImageId.h" +#include "LayoutType.h" +#include "NonCopyable.h" +#include "PageId.h" +#include "PageLayout.h" +#include "Params.h" +#include "ref_countable.h" class AbstractRelinker; namespace page_split { class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) + DECLARE_NON_COPYABLE(Settings) -private: - class BaseRecord { - // Member-wise copying is OK. - friend class Settings; + private: + class BaseRecord { + // Member-wise copying is OK. + friend class Settings; - public: - BaseRecord(); + public: + BaseRecord(); - const LayoutType* layoutType() const; + const LayoutType* layoutType() const; - const Params* params() const; + const Params* params() const; - /** - * \brief A record is considered null of it doesn't carry any - * information. - */ - bool isNull() const; + /** + * \brief A record is considered null of it doesn't carry any + * information. + */ + bool isNull() const; - protected: - void setParams(const Params& params); + protected: + void setParams(const Params& params); - void setLayoutType(LayoutType layout_type); + void setLayoutType(LayoutType layout_type); - void clearParams(); + void clearParams(); - void clearLayoutType(); + void clearLayoutType(); - /** - * \brief Checks if a particular layout type conflicts with PageLayout - * that is part of Params. - */ - bool hasLayoutTypeConflict(LayoutType layout_type) const; + /** + * \brief Checks if a particular layout type conflicts with PageLayout + * that is part of Params. + */ + bool hasLayoutTypeConflict(LayoutType layout_type) const; - Params m_params; - LayoutType m_layoutType; - bool m_paramsValid; - bool m_layoutTypeValid; - }; + Params m_params; + LayoutType m_layoutType; + bool m_paramsValid; + bool m_layoutTypeValid; + }; -public: - class UpdateAction; + public: + class UpdateAction; - class Record : public BaseRecord { - // Member-wise copying is OK. - public: - explicit Record(LayoutType default_layout_type); + class Record : public BaseRecord { + // Member-wise copying is OK. + public: + explicit Record(LayoutType default_layout_type); - Record(const BaseRecord& base_record, LayoutType default_layout_type); + Record(const BaseRecord& base_record, LayoutType default_layout_type); - LayoutType combinedLayoutType() const; + LayoutType combinedLayoutType() const; - void update(const UpdateAction& action); + void update(const UpdateAction& action); - bool hasLayoutTypeConflict() const; + bool hasLayoutTypeConflict() const; - private: - LayoutType m_defaultLayoutType; - }; + private: + LayoutType m_defaultLayoutType; + }; - class UpdateAction { - friend class Settings::Record; + class UpdateAction { + friend class Settings::Record; - public: - UpdateAction(); + public: + UpdateAction(); - void setLayoutType(LayoutType layout_type); + void setLayoutType(LayoutType layout_type); - void clearLayoutType(); + void clearLayoutType(); - void setParams(const Params& params); + void setParams(const Params& params); - void clearParams(); + void clearParams(); - private: - enum Action { DONT_TOUCH, SET, CLEAR }; + private: + enum Action { DONT_TOUCH, SET, CLEAR }; - Params m_params; - LayoutType m_layoutType; - Action m_paramsAction; - Action m_layoutTypeAction; - }; + Params m_params; + LayoutType m_layoutType; + Action m_paramsAction; + Action m_layoutTypeAction; + }; - Settings(); + Settings(); - ~Settings() override; + ~Settings() override; - /** - * \brief Reset all settings to their initial state. - */ - void clear(); + /** + * \brief Reset all settings to their initial state. + */ + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - LayoutType defaultLayoutType() const; + LayoutType defaultLayoutType() const; - /** - * Sets layout type for all pages, removing other page - * parameters where they conflict with the new layout type. - */ - void setLayoutTypeForAllPages(LayoutType layout_type); + /** + * Sets layout type for all pages, removing other page + * parameters where they conflict with the new layout type. + */ + void setLayoutTypeForAllPages(LayoutType layout_type); - /** - * Sets layout type for specified pages, removing other page - * parameters where they conflict with the new layout type. - */ - void setLayoutTypeFor(LayoutType layout_type, const std::set& pages); + /** + * Sets layout type for specified pages, removing other page + * parameters where they conflict with the new layout type. + */ + void setLayoutTypeFor(LayoutType layout_type, const std::set& pages); - /** - * \brief Returns all data related to a page as a single object. - */ - Record getPageRecord(const ImageId& image_id) const; + /** + * \brief Returns all data related to a page as a single object. + */ + Record getPageRecord(const ImageId& image_id) const; - /** - * \brief Performs the requested update on the page. - * - * If the update would lead to a conflict between the layout - * type and page parameters, the page parameters will be - * cleared. - */ - void updatePage(const ImageId& image_id, const UpdateAction& action); + /** + * \brief Performs the requested update on the page. + * + * If the update would lead to a conflict between the layout + * type and page parameters, the page parameters will be + * cleared. + */ + void updatePage(const ImageId& image_id, const UpdateAction& action); - /** - * \brief Performs a conditional update on the page. - * - * If the update would lead to a conflict between the layout - * type and page parameters, the update won't take place. - * Whether the update took place or not, the current page record - * (updated or not) will be returned. - */ - Record conditionalUpdate(const ImageId& image_id, const UpdateAction& action, bool* conflict = nullptr); + /** + * \brief Performs a conditional update on the page. + * + * If the update would lead to a conflict between the layout + * type and page parameters, the update won't take place. + * Whether the update took place or not, the current page record + * (updated or not) will be returned. + */ + Record conditionalUpdate(const ImageId& image_id, const UpdateAction& action, bool* conflict = nullptr); -private: - typedef std::unordered_map PerPageRecords; + private: + typedef std::unordered_map PerPageRecords; - Record getPageRecordLocked(const ImageId& image_id) const; + Record getPageRecordLocked(const ImageId& image_id) const; - void updatePageLocked(const ImageId& image_id, const UpdateAction& action); + void updatePageLocked(const ImageId& image_id, const UpdateAction& action); - void updatePageLocked(PerPageRecords::iterator it, const UpdateAction& action); + void updatePageLocked(PerPageRecords::iterator it, const UpdateAction& action); - mutable QMutex m_mutex; - PerPageRecords m_perPageRecords; - LayoutType m_defaultLayoutType; + mutable QMutex m_mutex; + PerPageRecords m_perPageRecords; + LayoutType m_defaultLayoutType; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_SETTINGS_H_ diff --git a/filters/page_split/SplitLineObject.h b/filters/page_split/SplitLineObject.h index b7179a5c9..ed8b8bab0 100644 --- a/filters/page_split/SplitLineObject.h +++ b/filters/page_split/SplitLineObject.h @@ -23,24 +23,20 @@ namespace page_split { class SplitLineObject : public DraggableObject { -protected: - virtual Proximity lineProximity(const QPointF& widget_mouse_pos, const InteractionState& interaction) const = 0; + protected: + virtual Proximity lineProximity(const QPointF& widget_mouse_pos, const InteractionState& interaction) const = 0; - virtual QPointF linePosition(const InteractionState& interaction) const = 0; + virtual QPointF linePosition(const InteractionState& interaction) const = 0; - virtual void lineMoveRequest(const QPointF& widget_pos) = 0; + virtual void lineMoveRequest(const QPointF& widget_pos) = 0; - virtual Proximity proximity(const QPointF& widget_mouse_pos, const InteractionState& interaction) { - return lineProximity(widget_mouse_pos, interaction); - } + virtual Proximity proximity(const QPointF& widget_mouse_pos, const InteractionState& interaction) { + return lineProximity(widget_mouse_pos, interaction); + } - virtual QPointF position(const InteractionState& interaction) const { - return linePosition(interaction); - } + virtual QPointF position(const InteractionState& interaction) const { return linePosition(interaction); } - virtual void moveRequest(const QPointF& widget_pos) { - return lineMoveRequest(widget_pos); - } + virtual void moveRequest(const QPointF& widget_pos) { return lineMoveRequest(widget_pos); } }; } // namespace page_split #endif // ifndef PAGE_SPLIT_SPLIT_LINE_OBJECT_H_ diff --git a/filters/page_split/SplitModeDialog.cpp b/filters/page_split/SplitModeDialog.cpp index 420c0297b..215381b81 100644 --- a/filters/page_split/SplitModeDialog.cpp +++ b/filters/page_split/SplitModeDialog.cpp @@ -17,9 +17,9 @@ */ #include "SplitModeDialog.h" -#include "PageSelectionAccessor.h" #include #include +#include "PageSelectionAccessor.h" namespace page_split { SplitModeDialog::SplitModeDialog(QWidget* const parent, @@ -28,143 +28,143 @@ SplitModeDialog::SplitModeDialog(QWidget* const parent, const LayoutType layout_type, const PageLayout::Type auto_detected_layout_type, const bool auto_detected_layout_type_valid) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_curPage(cur_page), - m_pScopeGroup(new QButtonGroup(this)), - m_layoutType(layout_type), - m_autoDetectedLayoutType(auto_detected_layout_type), - m_autoDetectedLayoutTypeValid(auto_detected_layout_type_valid) { - setupUi(this); - m_pScopeGroup->addButton(thisPageRB); - m_pScopeGroup->addButton(allPagesRB); - m_pScopeGroup->addButton(thisPageAndFollowersRB); - m_pScopeGroup->addButton(thisEveryOtherRB); - m_pScopeGroup->addButton(everyOtherRB); - m_pScopeGroup->addButton(selectedPagesRB); - m_pScopeGroup->addButton(everyOtherSelectedRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - everyOtherSelectedRB->setEnabled(false); - everyOtherSelectedHint->setEnabled(false); - } - - layoutTypeLabel->setPixmap(QPixmap(iconFor(m_layoutType))); - if (m_layoutType == AUTO_LAYOUT_TYPE) { - modeAuto->setChecked(true); - applyCutOption->setEnabled(false); - } else { - modeManual->setChecked(true); - applyCutOption->setEnabled(true); - } + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_curPage(cur_page), + m_scopeGroup(new QButtonGroup(this)), + m_layoutType(layout_type), + m_autoDetectedLayoutType(auto_detected_layout_type), + m_autoDetectedLayoutTypeValid(auto_detected_layout_type_valid) { + setupUi(this); + m_scopeGroup->addButton(thisPageRB); + m_scopeGroup->addButton(allPagesRB); + m_scopeGroup->addButton(thisPageAndFollowersRB); + m_scopeGroup->addButton(thisEveryOtherRB); + m_scopeGroup->addButton(everyOtherRB); + m_scopeGroup->addButton(selectedPagesRB); + m_scopeGroup->addButton(everyOtherSelectedRB); + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + everyOtherSelectedRB->setEnabled(false); + everyOtherSelectedHint->setEnabled(false); + } + + layoutTypeLabel->setPixmap(QPixmap(iconFor(m_layoutType))); + if (m_layoutType == AUTO_LAYOUT_TYPE) { + modeAuto->setChecked(true); + applyCutOption->setEnabled(false); + } else { + modeManual->setChecked(true); + applyCutOption->setEnabled(true); + } - connect(modeAuto, SIGNAL(pressed()), this, SLOT(autoDetectionSelected())); - connect(modeManual, SIGNAL(pressed()), this, SLOT(manualModeSelected())); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(modeAuto, SIGNAL(pressed()), this, SLOT(autoDetectionSelected())); + connect(modeManual, SIGNAL(pressed()), this, SLOT(manualModeSelected())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } SplitModeDialog::~SplitModeDialog() = default; void SplitModeDialog::autoDetectionSelected() { - layoutTypeLabel->setPixmap(QPixmap(":/icons/layout_type_auto.png")); - applyCutOption->setEnabled(false); - applyCutOption->setChecked(false); + layoutTypeLabel->setPixmap(QPixmap(":/icons/layout_type_auto.png")); + applyCutOption->setEnabled(false); + applyCutOption->setChecked(false); } void SplitModeDialog::manualModeSelected() { - const char* resource = iconFor(combinedLayoutType()); - layoutTypeLabel->setPixmap(QPixmap(resource)); - applyCutOption->setEnabled(true); + const char* resource = iconFor(combinedLayoutType()); + layoutTypeLabel->setPixmap(QPixmap(resource)); + applyCutOption->setEnabled(true); } void SplitModeDialog::onSubmit() { - LayoutType layout_type = AUTO_LAYOUT_TYPE; - if (modeManual->isChecked()) { - layout_type = combinedLayoutType(); - } + LayoutType layout_type = AUTO_LAYOUT_TYPE; + if (modeManual->isChecked()) { + layout_type = combinedLayoutType(); + } + + std::set pages; + + if (thisPageRB->isChecked()) { + pages.insert(m_curPage); + } else if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + emit accepted(m_selectedPages, layout_type, applyCutOption->isChecked()); + accept(); - std::set pages; - - if (thisPageRB->isChecked()) { - pages.insert(m_curPage); - } else if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - emit accepted(m_selectedPages, layout_type, applyCutOption->isChecked()); - accept(); - - return; - } else if (everyOtherRB->isChecked()) { - m_pages.selectEveryOther(m_curPage).swap(pages); - } else if (thisEveryOtherRB->isChecked()) { - std::set tmp; - m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); - auto it = tmp.begin(); - for (int i = 0; it != tmp.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - } else if (everyOtherSelectedRB->isChecked()) { - auto it = m_selectedPages.begin(); - for (int i = 0; it != m_selectedPages.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } + return; + } else if (everyOtherRB->isChecked()) { + m_pages.selectEveryOther(m_curPage).swap(pages); + } else if (thisEveryOtherRB->isChecked()) { + std::set tmp; + m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); + auto it = tmp.begin(); + for (int i = 0; it != tmp.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } + } + } else if (everyOtherSelectedRB->isChecked()) { + auto it = m_selectedPages.begin(); + for (int i = 0; it != m_selectedPages.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } } + } - emit accepted(pages, layout_type, applyCutOption->isChecked()); - // We assume the default connection from accepted() to accept() - // was removed. - accept(); + emit accepted(pages, layout_type, applyCutOption->isChecked()); + // We assume the default connection from accepted() to accept() + // was removed. + accept(); } // SplitModeDialog::onSubmit LayoutType SplitModeDialog::combinedLayoutType() const { - if (m_layoutType != AUTO_LAYOUT_TYPE) { - return m_layoutType; - } + if (m_layoutType != AUTO_LAYOUT_TYPE) { + return m_layoutType; + } - if (!m_autoDetectedLayoutTypeValid) { - return AUTO_LAYOUT_TYPE; - } + if (!m_autoDetectedLayoutTypeValid) { + return AUTO_LAYOUT_TYPE; + } - switch (m_autoDetectedLayoutType) { - case PageLayout::SINGLE_PAGE_UNCUT: - return SINGLE_PAGE_UNCUT; - case PageLayout::SINGLE_PAGE_CUT: - return PAGE_PLUS_OFFCUT; - case PageLayout::TWO_PAGES: - return TWO_PAGES; - } + switch (m_autoDetectedLayoutType) { + case PageLayout::SINGLE_PAGE_UNCUT: + return SINGLE_PAGE_UNCUT; + case PageLayout::SINGLE_PAGE_CUT: + return PAGE_PLUS_OFFCUT; + case PageLayout::TWO_PAGES: + return TWO_PAGES; + } - assert(!"Unreachable"); + assert(!"Unreachable"); - return AUTO_LAYOUT_TYPE; + return AUTO_LAYOUT_TYPE; } const char* SplitModeDialog::iconFor(const LayoutType layout_type) { - const char* resource = ""; - - switch (layout_type) { - case AUTO_LAYOUT_TYPE: - resource = ":/icons/layout_type_auto.png"; - break; - case SINGLE_PAGE_UNCUT: - resource = ":/icons/single_page_uncut_selected.png"; - break; - case PAGE_PLUS_OFFCUT: - resource = ":/icons/right_page_plus_offcut_selected.png"; - break; - case TWO_PAGES: - resource = ":/icons/two_pages_selected.png"; - break; - } - - return resource; + const char* resource = ""; + + switch (layout_type) { + case AUTO_LAYOUT_TYPE: + resource = ":/icons/layout_type_auto.png"; + break; + case SINGLE_PAGE_UNCUT: + resource = ":/icons/single_page_uncut_selected.png"; + break; + case PAGE_PLUS_OFFCUT: + resource = ":/icons/right_page_plus_offcut_selected.png"; + break; + case TWO_PAGES: + resource = ":/icons/two_pages_selected.png"; + break; + } + + return resource; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/SplitModeDialog.h b/filters/page_split/SplitModeDialog.h index 2cf9e91c6..81fff0f25 100644 --- a/filters/page_split/SplitModeDialog.h +++ b/filters/page_split/SplitModeDialog.h @@ -19,56 +19,56 @@ #ifndef PAGE_SPLIT_SPLITMODEDIALOG_H_ #define PAGE_SPLIT_SPLITMODEDIALOG_H_ -#include "ui_PageSplitModeDialog.h" +#include +#include +#include #include "LayoutType.h" -#include "PageLayout.h" #include "PageId.h" +#include "PageLayout.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include +#include "ui_PageSplitModeDialog.h" class ProjectPages; class PageSelectionAccessor; -class QButtonGroup; namespace page_split { class SplitModeDialog : public QDialog, private Ui::PageSplitModeDialog { - Q_OBJECT -public: - SplitModeDialog(QWidget* parent, - const PageId& cur_page, - const PageSelectionAccessor& page_selection_accessor, - LayoutType layout_type, - PageLayout::Type auto_detected_layout_type, - bool auto_detected_layout_type_valid); + Q_OBJECT + public: + SplitModeDialog(QWidget* parent, + const PageId& cur_page, + const PageSelectionAccessor& page_selection_accessor, + LayoutType layout_type, + PageLayout::Type auto_detected_layout_type, + bool auto_detected_layout_type_valid); - ~SplitModeDialog() override; + ~SplitModeDialog() override; -signals: + signals: - void accepted(const std::set& pages, LayoutType layout_type, bool apply_cut); + void accepted(const std::set& pages, LayoutType layout_type, bool apply_cut); -private slots: + private slots: - void autoDetectionSelected(); + void autoDetectionSelected(); - void manualModeSelected(); + void manualModeSelected(); - void onSubmit(); + void onSubmit(); -private: - LayoutType combinedLayoutType() const; + private: + LayoutType combinedLayoutType() const; - static const char* iconFor(LayoutType layout_type); + static const char* iconFor(LayoutType layout_type); - PageSequence m_pages; - std::set m_selectedPages; - PageId m_curPage; - QButtonGroup* m_pScopeGroup; - LayoutType m_layoutType; - PageLayout::Type m_autoDetectedLayoutType; - bool m_autoDetectedLayoutTypeValid; + PageSequence m_pages; + std::set m_selectedPages; + PageId m_curPage; + QButtonGroup* m_scopeGroup; + LayoutType m_layoutType; + PageLayout::Type m_autoDetectedLayoutType; + bool m_autoDetectedLayoutTypeValid; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_SPLITMODEDIALOG_H_ diff --git a/filters/page_split/Task.cpp b/filters/page_split/Task.cpp index 7760b7075..d04e1ba16 100644 --- a/filters/page_split/Task.cpp +++ b/filters/page_split/Task.cpp @@ -19,66 +19,64 @@ #include #include -#include "Task.h" -#include "TaskStatus.h" +#include "DebugImages.h" +#include "Dpm.h" #include "Filter.h" -#include "OptionsWidget.h" -#include "Settings.h" -#include "ProjectPages.h" -#include "PageLayoutEstimator.h" #include "FilterData.h" -#include "Dpm.h" -#include "filters/deskew/Task.h" -#include "ImageView.h" #include "FilterUiInterface.h" -#include "DebugImages.h" +#include "ImageView.h" +#include "OptionsWidget.h" #include "PageLayoutAdapter.h" +#include "PageLayoutEstimator.h" +#include "ProjectPages.h" +#include "Settings.h" +#include "Task.h" +#include "TaskStatus.h" +#include "filters/deskew/Task.h" namespace page_split { using imageproc::BinaryThreshold; class Task::UiUpdater : public FilterResult { -public: - UiUpdater(intrusive_ptr filter, - intrusive_ptr pages, - std::unique_ptr dbg_img, - const QImage& image, - const PageInfo& page_info, - const ImageTransformation& xform, - const OptionsWidget::UiData& ui_data, - bool batch_processing); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrPages; - std::unique_ptr m_ptrDbg; - QImage m_image; - QImage m_downscaledImage; - PageInfo m_pageInfo; - ImageTransformation m_xform; - OptionsWidget::UiData m_uiData; - bool m_batchProcessing; + public: + UiUpdater(intrusive_ptr filter, + intrusive_ptr pages, + std::unique_ptr dbg_img, + const QImage& image, + const PageInfo& page_info, + const ImageTransformation& xform, + const OptionsWidget::UiData& ui_data, + bool batch_processing); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + intrusive_ptr m_pages; + std::unique_ptr m_dbg; + QImage m_image; + QImage m_downscaledImage; + PageInfo m_pageInfo; + ImageTransformation m_xform; + OptionsWidget::UiData m_uiData; + bool m_batchProcessing; }; static ProjectPages::LayoutType toPageLayoutType(const PageLayout& layout) { - switch (layout.type()) { - case PageLayout::SINGLE_PAGE_UNCUT: - case PageLayout::SINGLE_PAGE_CUT: - return ProjectPages::ONE_PAGE_LAYOUT; - case PageLayout::TWO_PAGES: - return ProjectPages::TWO_PAGE_LAYOUT; - } + switch (layout.type()) { + case PageLayout::SINGLE_PAGE_UNCUT: + case PageLayout::SINGLE_PAGE_CUT: + return ProjectPages::ONE_PAGE_LAYOUT; + case PageLayout::TWO_PAGES: + return ProjectPages::TWO_PAGE_LAYOUT; + } - assert(!"Unreachable"); + assert(!"Unreachable"); - return ProjectPages::ONE_PAGE_LAYOUT; + return ProjectPages::ONE_PAGE_LAYOUT; } Task::Task(intrusive_ptr filter, @@ -88,124 +86,108 @@ Task::Task(intrusive_ptr filter, const PageInfo& page_info, const bool batch_processing, const bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrSettings(std::move(settings)), - m_ptrPages(std::move(pages)), - m_ptrNextTask(std::move(next_task)), - m_pageInfo(page_info), - m_batchProcessing(batch_processing) { - if (debug) { - m_ptrDbg = std::make_unique(); - } + : m_filter(std::move(filter)), + m_settings(std::move(settings)), + m_pages(std::move(pages)), + m_nextTask(std::move(next_task)), + m_pageInfo(page_info), + m_batchProcessing(batch_processing) { + if (debug) { + m_dbg = std::make_unique(); + } } Task::~Task() = default; FilterResultPtr Task::process(const TaskStatus& status, const FilterData& data) { - status.throwIfCancelled(); - - Settings::Record record(m_ptrSettings->getPageRecord(m_pageInfo.imageId())); - - const OrthogonalRotation pre_rotation(data.xform().preRotation()); - const Dependencies deps(data.origImage().size(), pre_rotation, record.combinedLayoutType()); - - OptionsWidget::UiData ui_data; - ui_data.setDependencies(deps); - - for (;;) { - const Params* const params = record.params(); - - PageLayout new_layout; - std::unique_ptr new_params; - - if (!params || !deps.compatibleWith(*params)) { - if (!params || ((record.layoutType() == nullptr) || (*record.layoutType() == AUTO_LAYOUT_TYPE))) { - new_layout = PageLayoutEstimator::estimatePageLayout(record.combinedLayoutType(), data.grayImage(), - data.xform(), data.bwThreshold(), m_ptrDbg.get()); - - status.throwIfCancelled(); - - new_params = std::make_unique(new_layout, deps, MODE_AUTO); - - Dependencies newDeps = deps; - newDeps.setLayoutType(new_layout.toLayoutType()); - new_params->setDependencies(newDeps); - } else { - new_params = std::make_unique(*params); - - std::unique_ptr newPageLayout - = PageLayoutAdapter::adaptPageLayout(params->pageLayout(), data.xform().resultingRect()); - - new_params->setPageLayout(*newPageLayout); - - Dependencies newDeps = deps; - newDeps.setLayoutType(newPageLayout->toLayoutType()); - new_params->setDependencies(newDeps); - } - } else { - PageLayout correctedPageLayout = params->pageLayout(); - PageLayoutAdapter::correctPageLayoutType(&correctedPageLayout); - if (correctedPageLayout.type() != params->pageLayout().type()) { - new_params = std::make_unique(*params); - new_params->setPageLayout(correctedPageLayout); - - Dependencies newDeps = deps; - newDeps.setLayoutType(correctedPageLayout.toLayoutType()); - new_params->setDependencies(newDeps); - } else { - break; - } - } + status.throwIfCancelled(); + + Settings::Record record(m_settings->getPageRecord(m_pageInfo.imageId())); + Dependencies deps(data.origImage().size(), data.xform().preRotation(), record.combinedLayoutType()); + + while (true) { + const Params* const params = record.params(); + + LayoutType new_layout_type = record.combinedLayoutType(); + AutoManualMode split_line_mode = MODE_AUTO; + PageLayout new_layout; + + if (!params || !deps.compatibleWith(*params)) { + if (!params || (record.combinedLayoutType() == AUTO_LAYOUT_TYPE)) { + new_layout = PageLayoutEstimator::estimatePageLayout(record.combinedLayoutType(), data.grayImage(), + data.xform(), data.bwThreshold(), m_dbg.get()); + + status.throwIfCancelled(); + } else { + new_layout = PageLayoutAdapter::adaptPageLayout(params->pageLayout(), data.xform().resultingRect()); + new_layout_type = new_layout.toLayoutType(); + split_line_mode = params->splitLineMode(); + } + } else { + PageLayout corrected_page_layout = params->pageLayout(); + PageLayoutAdapter::correctPageLayoutType(&corrected_page_layout); + if (corrected_page_layout.type() == params->pageLayout().type()) { + break; + } else { + new_layout = corrected_page_layout; + new_layout_type = new_layout.toLayoutType(); + split_line_mode = params->splitLineMode(); + } + } + deps.setLayoutType(new_layout_type); + const Params new_params(new_layout, deps, split_line_mode); - Settings::UpdateAction update; - update.setLayoutType(new_params->pageLayout().toLayoutType()); - update.setParams(*new_params); + Settings::UpdateAction update; + update.setLayoutType(new_layout_type); + update.setParams(new_params); #ifndef NDEBUG - { - Settings::Record updated_record(record); - updated_record.update(update); - assert(!updated_record.hasLayoutTypeConflict()); - // This assert effectively verifies that PageLayoutEstimator::estimatePageLayout() - // returned a layout with of a type consistent with the requested one. - // If it didn't, it's a bug which will in fact cause a dead loop. - } + { + Settings::Record updated_record(record); + updated_record.update(update); + assert(!updated_record.hasLayoutTypeConflict()); + // This assert effectively verifies that PageLayoutEstimator::estimatePageLayout() + // returned a layout with of a type consistent with the requested one. + // If it didn't, it's a bug which will in fact cause a dead loop. + } #endif - bool conflict = false; - record = m_ptrSettings->conditionalUpdate(m_pageInfo.imageId(), update, &conflict); - if (conflict && !record.params()) { - // If there was a conflict, it means - // the record was updated by another - // thread somewhere between getPageRecord() - // and conditionalUpdate(). If that - // external update didn't leave page - // parameters clear, we are just going - // to use it's data, otherwise we need - // to process this page again for the - // new layout type. - continue; - } - - break; + bool conflict = false; + record = m_settings->conditionalUpdate(m_pageInfo.imageId(), update, &conflict); + if (conflict && !record.params()) { + // If there was a conflict, it means + // the record was updated by another + // thread somewhere between getPageRecord() + // and conditionalUpdate(). If that + // external update didn't leave page + // parameters clear, we are just going + // to use it's data, otherwise we need + // to process this page again for the + // new layout type. + continue; } - const PageLayout& layout = record.params()->pageLayout(); - ui_data.setLayoutTypeAutoDetected(record.combinedLayoutType() == AUTO_LAYOUT_TYPE); - ui_data.setPageLayout(layout); - ui_data.setSplitLineMode(record.params()->splitLineMode()); + break; + } - m_ptrPages->setLayoutTypeFor(m_pageInfo.imageId(), toPageLayoutType(layout)); + OptionsWidget::UiData ui_data; + ui_data.setDependencies(deps); + const PageLayout& layout = record.params()->pageLayout(); + ui_data.setLayoutTypeAutoDetected(record.combinedLayoutType() == AUTO_LAYOUT_TYPE); + ui_data.setPageLayout(layout); + ui_data.setSplitLineMode(record.params()->splitLineMode()); - if (m_ptrNextTask != nullptr) { - ImageTransformation new_xform(data.xform()); - new_xform.setPreCropArea(layout.pageOutline(m_pageInfo.id().subPage()).toPolygon()); + m_pages->setLayoutTypeFor(m_pageInfo.imageId(), toPageLayoutType(layout)); - return m_ptrNextTask->process(status, FilterData(data, new_xform)); - } + if (m_nextTask != nullptr) { + ImageTransformation new_xform(data.xform()); + new_xform.setPreCropArea(layout.pageOutline(m_pageInfo.id().subPage()).toPolygon()); + + return m_nextTask->process(status, FilterData(data, new_xform)); + } - return make_intrusive(m_ptrFilter, m_ptrPages, std::move(m_ptrDbg), data.origImage(), m_pageInfo, - data.xform(), ui_data, m_batchProcessing); + return make_intrusive(m_filter, m_pages, std::move(m_dbg), data.origImage(), m_pageInfo, data.xform(), + ui_data, m_batchProcessing); } // Task::process /*============================ Task::UiUpdater =========================*/ @@ -218,38 +200,37 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const ImageTransformation& xform, const OptionsWidget::UiData& ui_data, const bool batch_processing) - : m_ptrFilter(std::move(filter)), - m_ptrPages(std::move(pages)), - m_ptrDbg(std::move(dbg_img)), - m_image(image), - m_downscaledImage(ImageView::createDownscaledImage(image)), - m_pageInfo(page_info), - m_xform(xform), - m_uiData(ui_data), - m_batchProcessing(batch_processing) { -} + : m_filter(std::move(filter)), + m_pages(std::move(pages)), + m_dbg(std::move(dbg_img)), + m_image(image), + m_downscaledImage(ImageView::createDownscaledImage(image)), + m_pageInfo(page_info), + m_xform(xform), + m_uiData(ui_data), + m_batchProcessing(batch_processing) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(m_uiData); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - - ui->invalidateThumbnail(m_pageInfo.id()); - - if (m_batchProcessing) { - return; - } - - auto view = new ImageView(m_image, m_downscaledImage, m_xform, m_uiData.pageLayout(), m_ptrPages, - m_pageInfo.imageId(), m_pageInfo.leftHalfRemoved(), m_pageInfo.rightHalfRemoved()); - ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); - - QObject::connect(view, SIGNAL(invalidateThumbnail(const PageInfo&)), opt_widget, - SIGNAL(invalidateThumbnail(const PageInfo&))); - QObject::connect(view, SIGNAL(pageLayoutSetLocally(const PageLayout&)), opt_widget, - SLOT(pageLayoutSetExternally(const PageLayout&))); - QObject::connect(opt_widget, SIGNAL(pageLayoutSetLocally(const PageLayout&)), view, - SLOT(pageLayoutSetExternally(const PageLayout&))); + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(m_uiData); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + + ui->invalidateThumbnail(m_pageInfo.id()); + + if (m_batchProcessing) { + return; + } + + auto view = new ImageView(m_image, m_downscaledImage, m_xform, m_uiData.pageLayout(), m_pages, m_pageInfo.imageId(), + m_pageInfo.leftHalfRemoved(), m_pageInfo.rightHalfRemoved()); + ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_dbg.get()); + + QObject::connect(view, SIGNAL(invalidateThumbnail(const PageInfo&)), opt_widget, + SIGNAL(invalidateThumbnail(const PageInfo&))); + QObject::connect(view, SIGNAL(pageLayoutSetLocally(const PageLayout&)), opt_widget, + SLOT(pageLayoutSetExternally(const PageLayout&))); + QObject::connect(opt_widget, SIGNAL(pageLayoutSetLocally(const PageLayout&)), view, + SLOT(pageLayoutSetExternally(const PageLayout&))); } // Task::UiUpdater::updateUI } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/Task.h b/filters/page_split/Task.h index 1b50f1026..1eedf5756 100644 --- a/filters/page_split/Task.h +++ b/filters/page_split/Task.h @@ -19,12 +19,12 @@ #ifndef PAGE_SPLIT_TASK_H_ #define PAGE_SPLIT_TASK_H_ -#include "NonCopyable.h" -#include "ref_countable.h" +#include #include "FilterResult.h" -#include "intrusive_ptr.h" +#include "NonCopyable.h" #include "PageInfo.h" -#include +#include "intrusive_ptr.h" +#include "ref_countable.h" class TaskStatus; class FilterData; @@ -43,31 +43,31 @@ class Settings; class PageLayout; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(intrusive_ptr filter, - intrusive_ptr settings, - intrusive_ptr pages, - intrusive_ptr next_task, - const PageInfo& page_info, - bool batch_processing, - bool debug); + public: + Task(intrusive_ptr filter, + intrusive_ptr settings, + intrusive_ptr pages, + intrusive_ptr next_task, + const PageInfo& page_info, + bool batch_processing, + bool debug); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, const FilterData& data); + FilterResultPtr process(const TaskStatus& status, const FilterData& data); -private: - class UiUpdater; + private: + class UiUpdater; - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrPages; - intrusive_ptr m_ptrNextTask; - std::unique_ptr m_ptrDbg; - PageInfo m_pageInfo; - bool m_batchProcessing; + intrusive_ptr m_filter; + intrusive_ptr m_settings; + intrusive_ptr m_pages; + intrusive_ptr m_nextTask; + std::unique_ptr m_dbg; + PageInfo m_pageInfo; + bool m_batchProcessing; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_TASK_H_ diff --git a/filters/page_split/Thumbnail.cpp b/filters/page_split/Thumbnail.cpp index a3d69ebcf..992875f9a 100644 --- a/filters/page_split/Thumbnail.cpp +++ b/filters/page_split/Thumbnail.cpp @@ -28,89 +28,89 @@ Thumbnail::Thumbnail(intrusive_ptr thumbnail_cache, const PageLayout& layout, bool left_half_removed, bool right_half_removed) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), - m_layout(layout), - m_leftHalfRemoved(left_half_removed), - m_rightHalfRemoved(right_half_removed) { - if (left_half_removed || right_half_removed) { - m_trashPixmap = QPixmap(":/icons/trashed-small.png"); - } + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), + m_layout(layout), + m_leftHalfRemoved(left_half_removed), + m_rightHalfRemoved(right_half_removed) { + if (left_half_removed || right_half_removed) { + m_trashPixmap = QPixmap(":/icons/trashed-small.png"); + } } void Thumbnail::prePaintOverImage(QPainter& painter, const QTransform& image_to_display, const QTransform& thumb_to_display) { - const QRectF canvas_rect(imageXform().resultingRect()); - - painter.setRenderHint(QPainter::Antialiasing, false); - painter.setWorldTransform(image_to_display); - - painter.setPen(Qt::NoPen); - switch (m_layout.type()) { - case PageLayout::SINGLE_PAGE_UNCUT: - painter.setBrush(QColor(0, 0, 255, 50)); - painter.drawRect(canvas_rect); - - return; // No split line will be drawn. - case PageLayout::SINGLE_PAGE_CUT: - painter.setBrush(QColor(0, 0, 255, 50)); - painter.drawPolygon(m_layout.singlePageOutline()); - break; - case PageLayout::TWO_PAGES: { - const QPolygonF left_poly(m_layout.leftPageOutline()); - const QPolygonF right_poly(m_layout.rightPageOutline()); - painter.setBrush(m_leftHalfRemoved ? QColor(0, 0, 0, 80) : QColor(0, 0, 255, 50)); - painter.drawPolygon(left_poly); - painter.setBrush(m_rightHalfRemoved ? QColor(0, 0, 0, 80) : QColor(255, 0, 0, 50)); - painter.drawPolygon(right_poly); - // Draw the trash icon. - if (m_leftHalfRemoved || m_rightHalfRemoved) { - painter.setWorldTransform(QTransform()); - - const int subpage_idx = m_leftHalfRemoved ? 0 : 1; - const QPointF center(subPageCenter(left_poly, right_poly, image_to_display, subpage_idx)); - - QRectF rect(m_trashPixmap.rect()); - rect.moveCenter(center); - painter.drawPixmap(rect.topLeft(), m_trashPixmap); - - painter.setWorldTransform(image_to_display); - } - break; - } - } // switch - - painter.setRenderHint(QPainter::Antialiasing, true); - - QPen pen(QColor(0x00, 0x00, 0xff)); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); - - switch (m_layout.type()) { - case PageLayout::SINGLE_PAGE_CUT: - painter.drawLine(m_layout.inscribedCutterLine(0)); - painter.drawLine(m_layout.inscribedCutterLine(1)); - break; - case PageLayout::TWO_PAGES: - painter.drawLine(m_layout.inscribedCutterLine(0)); - break; - default:; + const QRectF canvas_rect(imageXform().resultingRect()); + + painter.setRenderHint(QPainter::Antialiasing, false); + painter.setWorldTransform(image_to_display); + + painter.setPen(Qt::NoPen); + switch (m_layout.type()) { + case PageLayout::SINGLE_PAGE_UNCUT: + painter.setBrush(QColor(0, 0, 255, 50)); + painter.drawRect(canvas_rect); + + return; // No split line will be drawn. + case PageLayout::SINGLE_PAGE_CUT: + painter.setBrush(QColor(0, 0, 255, 50)); + painter.drawPolygon(m_layout.singlePageOutline()); + break; + case PageLayout::TWO_PAGES: { + const QPolygonF left_poly(m_layout.leftPageOutline()); + const QPolygonF right_poly(m_layout.rightPageOutline()); + painter.setBrush(m_leftHalfRemoved ? QColor(0, 0, 0, 80) : QColor(0, 0, 255, 50)); + painter.drawPolygon(left_poly); + painter.setBrush(m_rightHalfRemoved ? QColor(0, 0, 0, 80) : QColor(255, 0, 0, 50)); + painter.drawPolygon(right_poly); + // Draw the trash icon. + if (m_leftHalfRemoved || m_rightHalfRemoved) { + painter.setWorldTransform(QTransform()); + + const int subpage_idx = m_leftHalfRemoved ? 0 : 1; + const QPointF center(subPageCenter(left_poly, right_poly, image_to_display, subpage_idx)); + + QRectF rect(m_trashPixmap.rect()); + rect.moveCenter(center); + painter.drawPixmap(rect.topLeft(), m_trashPixmap); + + painter.setWorldTransform(image_to_display); + } + break; } + } // switch + + painter.setRenderHint(QPainter::Antialiasing, true); + + QPen pen(QColor(0x00, 0x00, 0xff)); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + + switch (m_layout.type()) { + case PageLayout::SINGLE_PAGE_CUT: + painter.drawLine(m_layout.inscribedCutterLine(0)); + painter.drawLine(m_layout.inscribedCutterLine(1)); + break; + case PageLayout::TWO_PAGES: + painter.drawLine(m_layout.inscribedCutterLine(0)); + break; + default:; + } } // Thumbnail::paintOverImage QPointF Thumbnail::subPageCenter(const QPolygonF& left_page, const QPolygonF& right_page, const QTransform& image_to_display, int subpage_idx) { - QRectF rects[2]; - rects[0] = left_page.boundingRect(); - rects[1] = right_page.boundingRect(); + QRectF rects[2]; + rects[0] = left_page.boundingRect(); + rects[1] = right_page.boundingRect(); - const double x_mid = 0.5 * (rects[0].right() + rects[1].left()); - rects[0].setRight(x_mid); - rects[1].setLeft(x_mid); + const double x_mid = 0.5 * (rects[0].right() + rects[1].left()); + rects[0].setRight(x_mid); + rects[1].setLeft(x_mid); - return image_to_display.map(rects[subpage_idx].center()); + return image_to_display.map(rects[subpage_idx].center()); } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/Thumbnail.h b/filters/page_split/Thumbnail.h index aea16c3b8..a19ed1be5 100644 --- a/filters/page_split/Thumbnail.h +++ b/filters/page_split/Thumbnail.h @@ -19,10 +19,10 @@ #ifndef PAGE_SPLIT_THUMBNAIL_H_ #define PAGE_SPLIT_THUMBNAIL_H_ -#include "ThumbnailBase.h" +#include #include "PageLayout.h" +#include "ThumbnailBase.h" #include "intrusive_ptr.h" -#include class QPointF; class QSizeF; @@ -33,29 +33,29 @@ class ImageTransformation; namespace page_split { class Thumbnail : public ThumbnailBase { -public: - Thumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& xform, - const PageLayout& layout, - bool left_half_removed, - bool right_half_removed); - - void prePaintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) override; - -private: - QPointF subPageCenter(const QPolygonF& left_page, - const QPolygonF& right_page, - const QTransform& image_to_display, - int subpage_idx); - - PageLayout m_layout; - QPixmap m_trashPixmap; - bool m_leftHalfRemoved; - bool m_rightHalfRemoved; + public: + Thumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& xform, + const PageLayout& layout, + bool left_half_removed, + bool right_half_removed); + + void prePaintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) override; + + private: + QPointF subPageCenter(const QPolygonF& left_page, + const QPolygonF& right_page, + const QTransform& image_to_display, + int subpage_idx); + + PageLayout m_layout; + QPixmap m_trashPixmap; + bool m_leftHalfRemoved; + bool m_rightHalfRemoved; }; } // namespace page_split #endif // ifndef PAGE_SPLIT_THUMBNAIL_H_ diff --git a/filters/page_split/UnremoveButton.cpp b/filters/page_split/UnremoveButton.cpp index 8871fb2f5..d9ab24079 100644 --- a/filters/page_split/UnremoveButton.cpp +++ b/filters/page_split/UnremoveButton.cpp @@ -17,54 +17,53 @@ */ #include "UnremoveButton.h" -#include #include +#include namespace page_split { UnremoveButton::UnremoveButton(const PositionGetter& position_getter) - : m_positionGetter(position_getter), - m_clickCallback(&UnremoveButton::noOp), - m_defaultPixmap(":/icons/trashed-big.png"), - m_hoveredPixmap(":/icons/untrash-big.png"), - m_wasHovered(false) { - m_proximityInteraction.setProximityCursor(Qt::PointingHandCursor); - m_proximityInteraction.setProximityStatusTip(tr("Restore removed page.")); + : m_positionGetter(position_getter), + m_clickCallback(&UnremoveButton::noOp), + m_defaultPixmap(":/icons/trashed-big.png"), + m_hoveredPixmap(":/icons/untrash-big.png"), + m_wasHovered(false) { + m_proximityInteraction.setProximityCursor(Qt::PointingHandCursor); + m_proximityInteraction.setProximityStatusTip(tr("Restore removed page.")); } void UnremoveButton::onPaint(QPainter& painter, const InteractionState& interaction) { - const QPixmap& pixmap = interaction.proximityLeader(m_proximityInteraction) ? m_hoveredPixmap : m_defaultPixmap; + const QPixmap& pixmap = interaction.proximityLeader(m_proximityInteraction) ? m_hoveredPixmap : m_defaultPixmap; - QRectF rect(pixmap.rect()); - rect.moveCenter(m_positionGetter()); + QRectF rect(pixmap.rect()); + rect.moveCenter(m_positionGetter()); - painter.setWorldTransform(QTransform()); - painter.drawPixmap(rect.topLeft(), pixmap); + painter.setWorldTransform(QTransform()); + painter.drawPixmap(rect.topLeft(), pixmap); } void UnremoveButton::onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) { - QRectF rect(m_defaultPixmap.rect()); - rect.moveCenter(m_positionGetter()); + QRectF rect(m_defaultPixmap.rect()); + rect.moveCenter(m_positionGetter()); - const bool hovered = rect.contains(screen_mouse_pos); - if (hovered != m_wasHovered) { - m_wasHovered = hovered; - interaction.setRedrawRequested(true); - } + const bool hovered = rect.contains(screen_mouse_pos); + if (hovered != m_wasHovered) { + m_wasHovered = hovered; + interaction.setRedrawRequested(true); + } - interaction.updateProximity(m_proximityInteraction, Proximity::fromSqDist(hovered ? 0.0 : 1e10)); + interaction.updateProximity(m_proximityInteraction, Proximity::fromSqDist(hovered ? 0.0 : 1e10)); } void UnremoveButton::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (!interaction.captured() && interaction.proximityLeader(m_proximityInteraction)) { - event->accept(); - m_clickCallback(); - } + if (!interaction.captured() && interaction.proximityLeader(m_proximityInteraction)) { + event->accept(); + m_clickCallback(); + } } void UnremoveButton::setClickCallback(const UnremoveButton::ClickCallback& callback) { - m_clickCallback = callback; + m_clickCallback = callback; } -void UnremoveButton::noOp() { -} +void UnremoveButton::noOp() {} } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/UnremoveButton.h b/filters/page_split/UnremoveButton.h index 12fc24013..ddd8b7b7a 100644 --- a/filters/page_split/UnremoveButton.h +++ b/filters/page_split/UnremoveButton.h @@ -19,41 +19,41 @@ #ifndef UNREMOVE_BUTTON_H_ #define UNREMOVE_BUTTON_H_ +#include +#include +#include +#include #include "InteractionHandler.h" #include "InteractionState.h" #include "Proximity.h" -#include -#include -#include -#include namespace page_split { class UnremoveButton : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(page_split::UnremoveButton) -public: - typedef boost::function PositionGetter; - typedef boost::function ClickCallback; + Q_DECLARE_TR_FUNCTIONS(page_split::UnremoveButton) + public: + typedef boost::function PositionGetter; + typedef boost::function ClickCallback; - explicit UnremoveButton(const PositionGetter& position_getter); + explicit UnremoveButton(const PositionGetter& position_getter); - void setClickCallback(const ClickCallback& callback); + void setClickCallback(const ClickCallback& callback); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; + void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; -private: - static void noOp(); + private: + static void noOp(); - PositionGetter m_positionGetter; - ClickCallback m_clickCallback; - InteractionState::Captor m_proximityInteraction; - QPixmap m_defaultPixmap; - QPixmap m_hoveredPixmap; - bool m_wasHovered; + PositionGetter m_positionGetter; + ClickCallback m_clickCallback; + InteractionState::Captor m_proximityInteraction; + QPixmap m_defaultPixmap; + QPixmap m_hoveredPixmap; + bool m_wasHovered; }; } // namespace page_split #endif // ifndef UNREMOVE_BUTTON_H_ diff --git a/filters/page_split/VertLineFinder.cpp b/filters/page_split/VertLineFinder.cpp index 7dbf30b72..d1f74da78 100644 --- a/filters/page_split/VertLineFinder.cpp +++ b/filters/page_split/VertLineFinder.cpp @@ -17,19 +17,19 @@ */ #include "VertLineFinder.h" -#include "ImageTransformation.h" +#include +#include +#include #include "DebugImages.h" -#include "imageproc/Transform.h" +#include "ImageTransformation.h" +#include "imageproc/Constants.h" #include "imageproc/GrayImage.h" -#include "imageproc/Grayscale.h" #include "imageproc/GrayRasterOp.h" -#include "imageproc/Morphology.h" -#include "imageproc/MorphGradientDetect.h" +#include "imageproc/Grayscale.h" #include "imageproc/HoughLineDetector.h" -#include "imageproc/Constants.h" -#include -#include -#include +#include "imageproc/MorphGradientDetect.h" +#include "imageproc/Morphology.h" +#include "imageproc/Transform.h" namespace page_split { using namespace imageproc; @@ -40,29 +40,29 @@ std::vector VertLineFinder::findLines(const QImage& image, DebugImages* dbg, GrayImage* gray_downscaled, QTransform* out_to_downscaled) { - const int dpi = 100; - - ImageTransformation xform_100dpi(xform); - xform_100dpi.preScaleToDpi(Dpi(dpi, dpi)); - - QRect target_rect(xform_100dpi.resultingRect().toRect()); - if (target_rect.isEmpty()) { - target_rect.setWidth(1); - target_rect.setHeight(1); - } - - const GrayImage gray100(transformToGray(image, xform_100dpi.transform(), target_rect, - OutsidePixels::assumeWeakColor(Qt::black), QSizeF(5.0, 5.0))); - if (dbg) { - dbg->add(gray100, "gray100"); - } - - if (gray_downscaled) { - *gray_downscaled = gray100; - } - if (out_to_downscaled) { - *out_to_downscaled = xform.transformBack() * xform_100dpi.transform(); - } + const int dpi = 100; + + ImageTransformation xform_100dpi(xform); + xform_100dpi.preScaleToDpi(Dpi(dpi, dpi)); + + QRect target_rect(xform_100dpi.resultingRect().toRect()); + if (target_rect.isEmpty()) { + target_rect.setWidth(1); + target_rect.setHeight(1); + } + + const GrayImage gray100(transformToGray(image, xform_100dpi.transform(), target_rect, + OutsidePixels::assumeWeakColor(Qt::black), QSizeF(5.0, 5.0))); + if (dbg) { + dbg->add(gray100, "gray100"); + } + + if (gray_downscaled) { + *gray_downscaled = gray100; + } + if (out_to_downscaled) { + *out_to_downscaled = xform.transformBack() * xform_100dpi.transform(); + } #if 0 GrayImage preprocessed(removeDarkVertBorders(gray100)); @@ -70,13 +70,13 @@ std::vector VertLineFinder::findLines(const QImage& image, dbg->add(preprocessed, "preprocessed"); } #else - // It looks like preprocessing causes more problems than it solves. - // It can reduce the visibility of a folding line to a level where - // it can't be detected, while it can't always fulfill its purpose of - // removing vertical edges of a book. Because of that, other methods - // of dealing with them were developed, which makes preprocessing - // obsolete. - GrayImage preprocessed(gray100); + // It looks like preprocessing causes more problems than it solves. + // It can reduce the visibility of a folding line to a level where + // it can't be detected, while it can't always fulfill its purpose of + // removing vertical edges of a book. Because of that, other methods + // of dealing with them were developed, which makes preprocessing + // obsolete. + GrayImage preprocessed(gray100); #endif #if 0 @@ -87,240 +87,239 @@ std::vector VertLineFinder::findLines(const QImage& image, dbg->add(v_gradient, "v_gradient"); } #else - // These are not gradients, but their difference is the same as for - // the two gradients above. This branch is an optimization. - GrayImage h_gradient(erodeGray(preprocessed, QSize(11, 1), 0x00)); - GrayImage v_gradient(erodeGray(preprocessed, QSize(1, 11), 0x00)); + // These are not gradients, but their difference is the same as for + // the two gradients above. This branch is an optimization. + GrayImage h_gradient(erodeGray(preprocessed, QSize(11, 1), 0x00)); + GrayImage v_gradient(erodeGray(preprocessed, QSize(1, 11), 0x00)); #endif - if (!dbg) { - // We'll need it later if debugging is on. - preprocessed = GrayImage(); - } - - grayRasterOp>(h_gradient, v_gradient); - v_gradient = GrayImage(); - if (dbg) { - dbg->add(h_gradient, "vert_raster_lines"); + if (!dbg) { + // We'll need it later if debugging is on. + preprocessed = GrayImage(); + } + + grayRasterOp>(h_gradient, v_gradient); + v_gradient = GrayImage(); + if (dbg) { + dbg->add(h_gradient, "vert_raster_lines"); + } + + const GrayImage raster_lines(closeGray(h_gradient, QSize(1, 19), 0x00)); + h_gradient = GrayImage(); + if (dbg) { + dbg->add(raster_lines, "short_segments_removed"); + } + + const double line_thickness = 5.0; + const double max_angle = 7.0; // degrees + const double angle_step = 0.25; + const auto angle_steps_to_max = (int) (max_angle / angle_step); + const int total_angle_steps = angle_steps_to_max * 2 + 1; + const double min_angle = -angle_steps_to_max * angle_step; + HoughLineDetector line_detector(raster_lines.size(), line_thickness, min_angle, angle_step, total_angle_steps); + + unsigned weight_table[256]; + buildWeightTable(weight_table); + + // We don't want to process areas too close to the vertical edges. + const double margin_mm = 3.5; + const auto margin = (int) std::floor(0.5 + margin_mm * constants::MM2INCH * dpi); + + const int x_limit = raster_lines.width() - margin; + const int height = raster_lines.height(); + const uint8_t* line = raster_lines.data(); + const int stride = raster_lines.stride(); + for (int y = 0; y < height; ++y, line += stride) { + for (int x = margin; x < x_limit; ++x) { + const unsigned val = line[x]; + if (val > 1) { + line_detector.process(x, y, weight_table[val]); + } } - - const GrayImage raster_lines(closeGray(h_gradient, QSize(1, 19), 0x00)); - h_gradient = GrayImage(); - if (dbg) { - dbg->add(raster_lines, "short_segments_removed"); - } - - const double line_thickness = 5.0; - const double max_angle = 7.0; // degrees - const double angle_step = 0.25; - const auto angle_steps_to_max = (int) (max_angle / angle_step); - const int total_angle_steps = angle_steps_to_max * 2 + 1; - const double min_angle = -angle_steps_to_max * angle_step; - HoughLineDetector line_detector(raster_lines.size(), line_thickness, min_angle, angle_step, total_angle_steps); - - unsigned weight_table[256]; - buildWeightTable(weight_table); - - // We don't want to process areas too close to the vertical edges. - const double margin_mm = 3.5; - const auto margin = (int) std::floor(0.5 + margin_mm * constants::MM2INCH * dpi); - - const int x_limit = raster_lines.width() - margin; - const int height = raster_lines.height(); - const uint8_t* line = raster_lines.data(); - const int stride = raster_lines.stride(); - for (int y = 0; y < height; ++y, line += stride) { - for (int x = margin; x < x_limit; ++x) { - const unsigned val = line[x]; - if (val > 1) { - line_detector.process(x, y, weight_table[val]); - } + } + + const unsigned min_quality = (unsigned) (height * line_thickness * 1.8) + 1; + + if (dbg) { + dbg->add(line_detector.visualizeHoughSpace(min_quality), "hough_space"); + } + + const std::vector hough_lines(line_detector.findLines(min_quality)); + + typedef std::list LineGroups; + LineGroups line_groups; + for (const HoughLine& hough_line : hough_lines) { + const QualityLine new_line(hough_line.pointAtY(0.0), hough_line.pointAtY(height), hough_line.quality()); + LineGroup* home_group = nullptr; + + auto it(line_groups.begin()); + const auto end(line_groups.end()); + while (it != end) { + LineGroup& group = *it; + if (group.belongsHere(new_line)) { + if (home_group) { + home_group->merge(group); + line_groups.erase(it++); + continue; + } else { + group.add(new_line); + home_group = &group; } + } + ++it; } - const unsigned min_quality = (unsigned) (height * line_thickness * 1.8) + 1; - - if (dbg) { - dbg->add(line_detector.visualizeHoughSpace(min_quality), "hough_space"); - } - - const std::vector hough_lines(line_detector.findLines(min_quality)); - - typedef std::list LineGroups; - LineGroups line_groups; - for (const HoughLine& hough_line : hough_lines) { - const QualityLine new_line(hough_line.pointAtY(0.0), hough_line.pointAtY(height), hough_line.quality()); - LineGroup* home_group = nullptr; - - auto it(line_groups.begin()); - const auto end(line_groups.end()); - while (it != end) { - LineGroup& group = *it; - if (group.belongsHere(new_line)) { - if (home_group) { - home_group->merge(group); - line_groups.erase(it++); - continue; - } else { - group.add(new_line); - home_group = &group; - } - } - ++it; - } - - if (!home_group) { - line_groups.emplace_back(new_line); - } + if (!home_group) { + line_groups.emplace_back(new_line); } + } - std::vector lines; - for (const LineGroup& group : line_groups) { - lines.push_back(group.leader().toQLine()); - if ((int) lines.size() == max_lines) { - break; - } + std::vector lines; + for (const LineGroup& group : line_groups) { + lines.push_back(group.leader().toQLine()); + if ((int) lines.size() == max_lines) { + break; } + } - if (dbg) { - QImage visual(preprocessed.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); + if (dbg) { + QImage visual(preprocessed.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); - { - QPainter painter(&visual); - painter.setRenderHint(QPainter::Antialiasing); - QPen pen(QColor(0xff, 0x00, 0x00, 0x80)); - pen.setWidthF(3.0); - painter.setPen(pen); + { + QPainter painter(&visual); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(QColor(0xff, 0x00, 0x00, 0x80)); + pen.setWidthF(3.0); + painter.setPen(pen); - for (const QLineF& line : lines) { - painter.drawLine(line); - } - } - dbg->add(visual, "vector_lines"); + for (const QLineF& line : lines) { + painter.drawLine(line); + } } + dbg->add(visual, "vector_lines"); + } - // Transform lines back into original coordinates. - const QTransform undo_100dpi(xform_100dpi.transformBack() * xform.transform()); - for (QLineF& line : lines) { - line = undo_100dpi.map(line); - } + // Transform lines back into original coordinates. + const QTransform undo_100dpi(xform_100dpi.transformBack() * xform.transform()); + for (QLineF& line : lines) { + line = undo_100dpi.map(line); + } - return lines; + return lines; } // VertLineFinder::findLines GrayImage VertLineFinder::removeDarkVertBorders(const GrayImage& src) { - GrayImage dst(src); + GrayImage dst(src); - selectVertBorders(dst); - grayRasterOp>>(dst, src); + selectVertBorders(dst); + grayRasterOp>>(dst, src); - return dst; + return dst; } void VertLineFinder::selectVertBorders(GrayImage& image) { - const int w = image.width(); - const int h = image.height(); + const int w = image.width(); + const int h = image.height(); - unsigned char* image_line = image.data(); - const int image_stride = image.stride(); + unsigned char* image_line = image.data(); + const int image_stride = image.stride(); - std::vector tmp_line(w, 0x00); + std::vector tmp_line(w, 0x00); - for (int y = 0; y < h; ++y, image_line += image_stride) { - // Left to right. - unsigned char prev_pixel = 0x00; // Black vertical border. - for (int x = 0; x < w; ++x) { - prev_pixel = std::max(image_line[x], prev_pixel); - tmp_line[x] = prev_pixel; - } - // Right to left - prev_pixel = 0x00; // Black vertical border. - for (int x = w - 1; x >= 0; --x) { - prev_pixel = std::max(image_line[x], std::min(prev_pixel, tmp_line[x])); - image_line[x] = prev_pixel; - } + for (int y = 0; y < h; ++y, image_line += image_stride) { + // Left to right. + unsigned char prev_pixel = 0x00; // Black vertical border. + for (int x = 0; x < w; ++x) { + prev_pixel = std::max(image_line[x], prev_pixel); + tmp_line[x] = prev_pixel; + } + // Right to left + prev_pixel = 0x00; // Black vertical border. + for (int x = w - 1; x >= 0; --x) { + prev_pixel = std::max(image_line[x], std::min(prev_pixel, tmp_line[x])); + image_line[x] = prev_pixel; } + } } void VertLineFinder::buildWeightTable(unsigned weight_table[]) { - int gray_level = 0; - unsigned weight = 2; - int segment = 2; - int prev_segment = 1; - - while (gray_level < 256) { - const int limit = std::min(256, gray_level + segment); - for (; gray_level < limit; ++gray_level) { - weight_table[gray_level] = weight; - } - ++weight; - segment += prev_segment; - prev_segment = segment; + int gray_level = 0; + unsigned weight = 2; + int segment = 2; + int prev_segment = 1; + + while (gray_level < 256) { + const int limit = std::min(256, gray_level + segment); + for (; gray_level < limit; ++gray_level) { + weight_table[gray_level] = weight; } + ++weight; + segment += prev_segment; + prev_segment = segment; + } } /*======================= VertLineFinder::QualityLine =======================*/ VertLineFinder::QualityLine::QualityLine(const QPointF& top, const QPointF& bottom, const unsigned quality) - : m_quality(quality) { - if (top.x() < bottom.x()) { - m_left = top; - m_right = bottom; - } else { - m_left = bottom; - m_right = top; - } + : m_quality(quality) { + if (top.x() < bottom.x()) { + m_left = top; + m_right = bottom; + } else { + m_left = bottom; + m_right = top; + } } QLineF VertLineFinder::QualityLine::toQLine() const { - return QLineF(m_left, m_right); + return QLineF(m_left, m_right); } const QPointF& VertLineFinder::QualityLine::left() const { - return m_left; + return m_left; } const QPointF& VertLineFinder::QualityLine::right() const { - return m_right; + return m_right; } unsigned VertLineFinder::QualityLine::quality() const { - return m_quality; + return m_quality; } /*======================= VertLineFinder::LineGroup ========================*/ VertLineFinder::LineGroup::LineGroup(const QualityLine& line) - : m_leader(line), m_left(line.left().x()), m_right(line.right().x()) { -} + : m_leader(line), m_left(line.left().x()), m_right(line.right().x()) {} bool VertLineFinder::LineGroup::belongsHere(const QualityLine& line) const { - if (m_left > line.right().x()) { - return false; - } else if (m_right < line.left().x()) { - return false; - } else { - return true; - } + if (m_left > line.right().x()) { + return false; + } else if (m_right < line.left().x()) { + return false; + } else { + return true; + } } void VertLineFinder::LineGroup::add(const QualityLine& line) { - m_left = std::min(m_left, line.left().x()); - m_right = std::max(m_right, line.right().x()); - if (line.quality() > m_leader.quality()) { - m_leader = line; - } + m_left = std::min(m_left, line.left().x()); + m_right = std::max(m_right, line.right().x()); + if (line.quality() > m_leader.quality()) { + m_leader = line; + } } void VertLineFinder::LineGroup::merge(const LineGroup& other) { - m_left = std::min(m_left, other.m_left); - m_right = std::max(m_right, other.m_right); - if (other.leader().quality() > m_leader.quality()) { - m_leader = other.leader(); - } + m_left = std::min(m_left, other.m_left); + m_right = std::max(m_right, other.m_right); + if (other.leader().quality() > m_leader.quality()) { + m_leader = other.leader(); + } } const VertLineFinder::QualityLine& VertLineFinder::LineGroup::leader() const { - return m_leader; + return m_leader; } } // namespace page_split \ No newline at end of file diff --git a/filters/page_split/VertLineFinder.h b/filters/page_split/VertLineFinder.h index 3079c2302..bbe012df2 100644 --- a/filters/page_split/VertLineFinder.h +++ b/filters/page_split/VertLineFinder.h @@ -19,8 +19,8 @@ #ifndef PAGE_SPLIT_VERTLINEFINDER_H_ #define PAGE_SPLIT_VERTLINEFINDER_H_ -#include #include +#include #include #include @@ -35,58 +35,58 @@ class GrayImage; namespace page_split { class VertLineFinder { -public: - static std::vector findLines(const QImage& image, - const ImageTransformation& xform, - int max_lines, - DebugImages* dbg = nullptr, - imageproc::GrayImage* gray_downscaled = nullptr, - QTransform* out_to_downscaled = nullptr); + public: + static std::vector findLines(const QImage& image, + const ImageTransformation& xform, + int max_lines, + DebugImages* dbg = nullptr, + imageproc::GrayImage* gray_downscaled = nullptr, + QTransform* out_to_downscaled = nullptr); -private: - class QualityLine { - public: - QualityLine(const QPointF& top, const QPointF& bottom, unsigned quality); + private: + class QualityLine { + public: + QualityLine(const QPointF& top, const QPointF& bottom, unsigned quality); - const QPointF& left() const; + const QPointF& left() const; - const QPointF& right() const; + const QPointF& right() const; - unsigned quality() const; + unsigned quality() const; - QLineF toQLine() const; + QLineF toQLine() const; - private: - QPointF m_left; - QPointF m_right; - unsigned m_quality; - }; + private: + QPointF m_left; + QPointF m_right; + unsigned m_quality; + }; - class LineGroup { - public: - explicit LineGroup(const QualityLine& line); + class LineGroup { + public: + explicit LineGroup(const QualityLine& line); - bool belongsHere(const QualityLine& line) const; + bool belongsHere(const QualityLine& line) const; - void add(const QualityLine& line); + void add(const QualityLine& line); - void merge(const LineGroup& other); + void merge(const LineGroup& other); - const QualityLine& leader() const; + const QualityLine& leader() const; - private: - QualityLine m_leader; - double m_left; - double m_right; - }; + private: + QualityLine m_leader; + double m_left; + double m_right; + }; - static imageproc::GrayImage removeDarkVertBorders(const imageproc::GrayImage& src); + static imageproc::GrayImage removeDarkVertBorders(const imageproc::GrayImage& src); - static void selectVertBorders(imageproc::GrayImage& image); + static void selectVertBorders(imageproc::GrayImage& image); - static void buildWeightTable(unsigned weight_table[]); + static void buildWeightTable(unsigned weight_table[]); }; } // namespace page_split #endif // ifndef PAGE_SPLIT_VERTLINEFINDER_H_ diff --git a/filters/page_split/ui/PageSplitOptionsWidget.ui b/filters/page_split/ui/PageSplitOptionsWidget.ui index da183ec43..b0dd81a40 100644 --- a/filters/page_split/ui/PageSplitOptionsWidget.ui +++ b/filters/page_split/ui/PageSplitOptionsWidget.ui @@ -25,6 +25,9 @@ Page Layout + + Qt::AlignCenter + @@ -183,6 +186,9 @@ Split Line + + Qt::AlignCenter + 9 diff --git a/filters/select_content/ApplyDialog.cpp b/filters/select_content/ApplyDialog.cpp index 98f871f74..180ada18f 100644 --- a/filters/select_content/ApplyDialog.cpp +++ b/filters/select_content/ApplyDialog.cpp @@ -17,67 +17,67 @@ */ #include "ApplyDialog.h" -#include "PageSelectionAccessor.h" #include +#include "PageSelectionAccessor.h" namespace select_content { ApplyDialog::ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor) - : QDialog(parent), - m_pages(page_selection_accessor.allPages()), - m_selectedPages(page_selection_accessor.selectedPages()), - m_selectedRanges(page_selection_accessor.selectedRanges()), - m_curPage(cur_page), - m_pBtnGroup(new QButtonGroup(this)) { - setupUi(this); - m_pBtnGroup->addButton(thisPageOnlyRB); - m_pBtnGroup->addButton(allPagesRB); - m_pBtnGroup->addButton(thisPageAndFollowersRB); - m_pBtnGroup->addButton(selectedPagesRB); - m_pBtnGroup->addButton(everyOtherRB); - m_pBtnGroup->addButton(thisEveryOtherRB); - m_pBtnGroup->addButton(everyOtherSelectedRB); + : QDialog(parent), + m_pages(page_selection_accessor.allPages()), + m_selectedPages(page_selection_accessor.selectedPages()), + m_selectedRanges(page_selection_accessor.selectedRanges()), + m_curPage(cur_page), + m_btnGroup(new QButtonGroup(this)) { + setupUi(this); + m_btnGroup->addButton(thisPageOnlyRB); + m_btnGroup->addButton(allPagesRB); + m_btnGroup->addButton(thisPageAndFollowersRB); + m_btnGroup->addButton(selectedPagesRB); + m_btnGroup->addButton(everyOtherRB); + m_btnGroup->addButton(thisEveryOtherRB); + m_btnGroup->addButton(everyOtherSelectedRB); - if (m_selectedPages.size() <= 1) { - selectedPagesRB->setEnabled(false); - selectedPagesHint->setEnabled(false); - everyOtherSelectedRB->setEnabled(false); - everyOtherSelectedHint->setEnabled(false); - } + if (m_selectedPages.size() <= 1) { + selectedPagesRB->setEnabled(false); + selectedPagesHint->setEnabled(false); + everyOtherSelectedRB->setEnabled(false); + everyOtherSelectedHint->setEnabled(false); + } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() = default; void ApplyDialog::onSubmit() { - std::set pages; + std::set pages; - // thisPageOnlyRB is intentionally not handled. - if (allPagesRB->isChecked()) { - m_pages.selectAll().swap(pages); - } else if (thisPageAndFollowersRB->isChecked()) { - m_pages.selectPagePlusFollowers(m_curPage).swap(pages); - } else if (selectedPagesRB->isChecked()) { - m_selectedPages.swap(pages); - } else if (everyOtherRB->isChecked()) { - m_pages.selectEveryOther(m_curPage).swap(pages); - } else if (thisEveryOtherRB->isChecked()) { - std::set tmp; - m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); - auto it = tmp.begin(); - for (int i = 0; it != tmp.end(); ++it, ++i) { - if (i % 2 == 0) { - pages.insert(*it); - } - } - } else if (everyOtherSelectedRB->isChecked()) { - assert(m_selectedRanges.size() == 1); - const PageRange& range = m_selectedRanges.front(); - range.selectEveryOther(m_curPage).swap(pages); + // thisPageOnlyRB is intentionally not handled. + if (allPagesRB->isChecked()) { + m_pages.selectAll().swap(pages); + } else if (thisPageAndFollowersRB->isChecked()) { + m_pages.selectPagePlusFollowers(m_curPage).swap(pages); + } else if (selectedPagesRB->isChecked()) { + m_selectedPages.swap(pages); + } else if (everyOtherRB->isChecked()) { + m_pages.selectEveryOther(m_curPage).swap(pages); + } else if (thisEveryOtherRB->isChecked()) { + std::set tmp; + m_pages.selectPagePlusFollowers(m_curPage).swap(tmp); + auto it = tmp.begin(); + for (int i = 0; it != tmp.end(); ++it, ++i) { + if (i % 2 == 0) { + pages.insert(*it); + } } + } else if (everyOtherSelectedRB->isChecked()) { + assert(m_selectedRanges.size() == 1); + const PageRange& range = m_selectedRanges.front(); + range.selectEveryOther(m_curPage).swap(pages); + } - emit applySelection(pages, applyContentBoxOption->isChecked(), applyPageBoxOption->isChecked()); - // We assume the default connection from accept() to accepted() was removed. - accept(); + emit applySelection(pages, applyContentBoxOption->isChecked(), applyPageBoxOption->isChecked()); + // We assume the default connection from accept() to accepted() was removed. + accept(); } // ApplyDialog::onSubmit } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/ApplyDialog.h b/filters/select_content/ApplyDialog.h index f0f70414e..d2e05c0f5 100644 --- a/filters/select_content/ApplyDialog.h +++ b/filters/select_content/ApplyDialog.h @@ -19,40 +19,40 @@ #ifndef SELECT_CONTENT_APPLYDIALOG_H_ #define SELECT_CONTENT_APPLYDIALOG_H_ -#include "ui_SelectContentApplyDialog.h" +#include +#include +#include +#include #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "intrusive_ptr.h" -#include -#include -#include +#include "ui_SelectContentApplyDialog.h" class PageSelectionAccessor; -class QButtonGroup; namespace select_content { class ApplyDialog : public QDialog, private Ui::SelectContentApplyDialog { - Q_OBJECT -public: - ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); + Q_OBJECT + public: + ApplyDialog(QWidget* parent, const PageId& cur_page, const PageSelectionAccessor& page_selection_accessor); - ~ApplyDialog() override; + ~ApplyDialog() override; -signals: + signals: - void applySelection(const std::set& pages, bool apply_content_box, bool apply_page_box); + void applySelection(const std::set& pages, bool apply_content_box, bool apply_page_box); -private slots: + private slots: - void onSubmit(); + void onSubmit(); -private: - PageSequence m_pages; - std::set m_selectedPages; - std::vector m_selectedRanges; - PageId m_curPage; - QButtonGroup* m_pBtnGroup; + private: + PageSequence m_pages; + std::set m_selectedPages; + std::vector m_selectedRanges; + PageId m_curPage; + QButtonGroup* m_btnGroup; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_APPLYDIALOG_H_ diff --git a/filters/select_content/CMakeLists.txt b/filters/select_content/CMakeLists.txt index 3aa41a599..d28223493 100644 --- a/filters/select_content/CMakeLists.txt +++ b/filters/select_content/CMakeLists.txt @@ -1,35 +1,34 @@ -PROJECT("Select Content Filter") +project("Select Content Filter") -INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") +include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") -FILE(GLOB ui_files "ui/*.ui") -QT5_WRAP_UI(ui_sources ${ui_files}) -SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) -SOURCE_GROUP("UI Files" FILES ${ui_files}) -SOURCE_GROUP("Generated" FILES ${ui_sources}) +file(GLOB ui_files "ui/*.ui") +qt5_wrap_ui(ui_sources ${ui_files}) +set_source_files_properties(${ui_sources} PROPERTIES GENERATED TRUE) +source_group("UI Files" FILES ${ui_files}) +source_group("Generated" FILES ${ui_sources}) -SET( - sources - ImageView.cpp ImageView.h - Filter.cpp Filter.h - OptionsWidget.cpp OptionsWidget.h - ApplyDialog.cpp ApplyDialog.h - ContentBoxFinder.cpp ContentBoxFinder.h - PageFinder.cpp PageFinder.h - Task.cpp Task.h - CacheDrivenTask.cpp CacheDrivenTask.h - Dependencies.cpp Dependencies.h - Params.cpp Params.h - Settings.cpp Settings.h - Thumbnail.cpp Thumbnail.h - PhysSizeCalc.cpp PhysSizeCalc.h - OrderByWidthProvider.cpp OrderByWidthProvider.h - OrderByHeightProvider.cpp OrderByHeightProvider.h -) -SOURCE_GROUP("Sources" FILES ${sources}) +set(sources + ImageView.cpp ImageView.h + Filter.cpp Filter.h + OptionsWidget.cpp OptionsWidget.h + ApplyDialog.cpp ApplyDialog.h + ContentBoxFinder.cpp ContentBoxFinder.h + PageFinder.cpp PageFinder.h + Task.cpp Task.h + CacheDrivenTask.cpp CacheDrivenTask.h + Dependencies.cpp Dependencies.h + Params.cpp Params.h + Settings.cpp Settings.h + Thumbnail.cpp Thumbnail.h + PhysSizeCalc.cpp PhysSizeCalc.h + OrderByWidthProvider.cpp OrderByWidthProvider.h + OrderByHeightProvider.cpp OrderByHeightProvider.h) + +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(select_content STATIC ${sources} ${ui_sources}) +add_library(select_content STATIC ${sources} ${ui_sources}) -TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) +translation_sources(scantailor ${sources} ${ui_files}) diff --git a/filters/select_content/CacheDrivenTask.cpp b/filters/select_content/CacheDrivenTask.cpp index c3eb73bdf..6e216070d 100644 --- a/filters/select_content/CacheDrivenTask.cpp +++ b/filters/select_content/CacheDrivenTask.cpp @@ -17,60 +17,62 @@ */ #include "CacheDrivenTask.h" -#include "Thumbnail.h" #include "IncompleteThumbnail.h" -#include "Settings.h" #include "PageInfo.h" +#include "Settings.h" +#include "Thumbnail.h" #include "filter_dc/AbstractFilterDataCollector.h" -#include "filter_dc/ThumbnailCollector.h" #include "filter_dc/ContentBoxCollector.h" +#include "filter_dc/ThumbnailCollector.h" #include "filters/page_layout/CacheDrivenTask.h" +#include #include #include -#include namespace select_content { CacheDrivenTask::CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task) - : m_ptrSettings(std::move(settings)), m_ptrNextTask(std::move(next_task)) { -} + : m_settings(std::move(settings)), m_nextTask(std::move(next_task)) {} CacheDrivenTask::~CacheDrivenTask() = default; void CacheDrivenTask::process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform) { - std::unique_ptr params(m_ptrSettings->getPageParams(page_info.id())); - const Dependencies deps(xform.resultingPreCropArea()); - if (!params || !params->dependencies().matches(deps)) { - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); - } + std::unique_ptr params(m_settings->getPageParams(page_info.id())); + const Dependencies deps = (params) ? Dependencies(xform.resultingPreCropArea(), params->contentDetectionMode(), + params->pageDetectionMode(), params->isFineTuningEnabled()) + : Dependencies(xform.resultingPreCropArea()); - return; + if (!params || !deps.compatibleWith(params->dependencies())) { + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr(new IncompleteThumbnail( + thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform))); } - if (auto* col = dynamic_cast(collector)) { - col->process(xform, params->contentRect()); - } + return; + } - if (m_ptrNextTask) { - m_ptrNextTask->process(page_info, collector, xform, params->pageRect(), params->contentRect()); + if (auto* col = dynamic_cast(collector)) { + col->process(xform, params->contentRect()); + } - return; - } + if (m_nextTask) { + m_nextTask->process(page_info, collector, xform, params->pageRect(), params->contentRect()); - QSettings settings; - const double deviationCoef = settings.value("settings/selectContentDeviationCoef", 0.35).toDouble(); - const double deviationThreshold = settings.value("settings/selectContentDeviationThreshold", 1.0).toDouble(); + return; + } - if (auto* thumb_col = dynamic_cast(collector)) { - thumb_col->processThumbnail(std::unique_ptr(new Thumbnail( - thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, - params->contentRect(), params->pageRect(), params->isPageDetectionEnabled(), - m_ptrSettings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); - } + QSettings settings; + const double deviationCoef = settings.value("settings/selectContentDeviationCoef", 0.35).toDouble(); + const double deviationThreshold = settings.value("settings/selectContentDeviationThreshold", 1.0).toDouble(); + + if (auto* thumb_col = dynamic_cast(collector)) { + thumb_col->processThumbnail(std::unique_ptr( + new Thumbnail(thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, + params->contentRect(), params->pageRect(), params->pageDetectionMode() != MODE_DISABLED, + m_settings->deviationProvider().isDeviant(page_info.id(), deviationCoef, deviationThreshold)))); + } } // CacheDrivenTask::process } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/CacheDrivenTask.h b/filters/select_content/CacheDrivenTask.h index a2baca947..c92983790 100644 --- a/filters/select_content/CacheDrivenTask.h +++ b/filters/select_content/CacheDrivenTask.h @@ -20,8 +20,8 @@ #define SELECT_CONTENT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" -#include "ref_countable.h" #include "intrusive_ptr.h" +#include "ref_countable.h" class QSizeF; class PageInfo; @@ -36,18 +36,18 @@ namespace select_content { class Settings; class CacheDrivenTask : public ref_countable { - DECLARE_NON_COPYABLE(CacheDrivenTask) + DECLARE_NON_COPYABLE(CacheDrivenTask) -public: - CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); + public: + CacheDrivenTask(intrusive_ptr settings, intrusive_ptr next_task); - ~CacheDrivenTask() override; + ~CacheDrivenTask() override; - void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); + void process(const PageInfo& page_info, AbstractFilterDataCollector* collector, const ImageTransformation& xform); -private: - intrusive_ptr m_ptrSettings; - intrusive_ptr m_ptrNextTask; + private: + intrusive_ptr m_settings; + intrusive_ptr m_nextTask; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_CACHEDRIVENTASK_H_ diff --git a/filters/select_content/ContentBoxFinder.cpp b/filters/select_content/ContentBoxFinder.cpp index 559856123..7c0d62e9f 100644 --- a/filters/select_content/ContentBoxFinder.cpp +++ b/filters/select_content/ContentBoxFinder.cpp @@ -17,67 +17,65 @@ */ #include "ContentBoxFinder.h" -#include "TaskStatus.h" +#include +#include +#include +#include #include "DebugImages.h" -#include "FilterData.h" #include "Despeckle.h" -#include "imageproc/BinaryImage.h" +#include "FilterData.h" +#include "TaskStatus.h" #include "imageproc/Binarize.h" -#include "imageproc/Connectivity.h" +#include "imageproc/BinaryImage.h" #include "imageproc/ConnComp.h" #include "imageproc/ConnCompEraserExt.h" -#include "imageproc/Transform.h" -#include "imageproc/RasterOp.h" +#include "imageproc/Connectivity.h" +#include "imageproc/ConnectivityMap.h" #include "imageproc/GrayRasterOp.h" -#include "imageproc/SeedFill.h" +#include "imageproc/InfluenceMap.h" +#include "imageproc/MaxWhitespaceFinder.h" #include "imageproc/Morphology.h" -#include "imageproc/SlicedHistogram.h" #include "imageproc/PolygonRasterizer.h" -#include "imageproc/MaxWhitespaceFinder.h" -#include "imageproc/ConnectivityMap.h" -#include "imageproc/InfluenceMap.h" +#include "imageproc/RasterOp.h" #include "imageproc/SEDM.h" -#include -#include -#include -#include +#include "imageproc/SeedFill.h" +#include "imageproc/SlicedHistogram.h" +#include "imageproc/Transform.h" namespace select_content { using namespace imageproc; class ContentBoxFinder::Garbage { -public: - enum Type { HOR, VERT }; + public: + enum Type { HOR, VERT }; - Garbage(Type type, const BinaryImage& garbage); + Garbage(Type type, const BinaryImage& garbage); - void add(const BinaryImage& garbage, const QRect& rect); + void add(const BinaryImage& garbage, const QRect& rect); - const BinaryImage& image() const { - return m_garbage; - } + const BinaryImage& image() const { return m_garbage; } - const SEDM& sedm(); + const SEDM& sedm(); -private: - imageproc::BinaryImage m_garbage; - SEDM m_sedm; - SEDM::Borders m_sedmBorders; - bool m_sedmUpdatePending; + private: + imageproc::BinaryImage m_garbage; + SEDM m_sedm; + SEDM::Borders m_sedmBorders; + bool m_sedmUpdatePending; }; namespace { struct PreferHorizontal { - bool operator()(const QRect& lhs, const QRect& rhs) const { - return lhs.width() * lhs.width() * lhs.height() < rhs.width() * rhs.width() * rhs.height(); - } + bool operator()(const QRect& lhs, const QRect& rhs) const { + return lhs.width() * lhs.width() * lhs.height() < rhs.width() * rhs.width() * rhs.height(); + } }; struct PreferVertical { - bool operator()(const QRect& lhs, const QRect& rhs) const { - return lhs.width() * lhs.height() * lhs.height() < rhs.width() * rhs.height() * rhs.height(); - } + bool operator()(const QRect& lhs, const QRect& rhs) const { + return lhs.width() * lhs.height() * lhs.height() < rhs.width() * rhs.height() * rhs.height(); + } }; } // namespace @@ -85,801 +83,801 @@ QRectF ContentBoxFinder::findContentBox(const TaskStatus& status, const FilterData& data, const QRectF& page_rect, DebugImages* dbg) { - ImageTransformation xform_150dpi(data.xform()); - xform_150dpi.preScaleToDpi(Dpi(150, 150)); + ImageTransformation xform_150dpi(data.xform()); + xform_150dpi.preScaleToDpi(Dpi(150, 150)); + + if (xform_150dpi.resultingRect().toRect().isEmpty()) { + return QRectF(); + } - if (xform_150dpi.resultingRect().toRect().isEmpty()) { - return QRectF(); - } - - const uint8_t darkest_gray_level = darkestGrayLevel(data.grayImage()); - const QColor outside_color(darkest_gray_level, darkest_gray_level, darkest_gray_level); + const GrayImage dataGrayImage = data.isBlackOnWhite() ? data.grayImage() : data.grayImage().inverted(); + const uint8_t darkest_gray_level = darkestGrayLevel(dataGrayImage); + const QColor outside_color(darkest_gray_level, darkest_gray_level, darkest_gray_level); - QImage gray150(transformToGray(data.grayImage(), xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), - OutsidePixels::assumeColor(outside_color))); - // Note that we fill new areas that appear as a result of - // rotation with black, not white. Filling them with white - // may be bad for detecting the shadow around the page. - if (dbg) { - dbg->add(gray150, "gray150"); + QImage gray150(transformToGray(dataGrayImage, xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), + OutsidePixels::assumeColor(outside_color))); + // Note that we fill new areas that appear as a result of + // rotation with black, not white. Filling them with white + // may be bad for detecting the shadow around the page. + if (dbg) { + dbg->add(gray150, "gray150"); + } + + BinaryImage bw150(binarizeWolf(gray150, QSize(51, 51), 50)); + if (dbg) { + dbg->add(bw150, "bw150"); + } + + const double xscale = 150.0 / data.xform().origDpi().horizontal(); + const double yscale = 150.0 / data.xform().origDpi().vertical(); + QRectF page_rect150(page_rect.left() * xscale, page_rect.top() * yscale, page_rect.right() * xscale, + page_rect.bottom() * yscale); + PolygonRasterizer::fillExcept(bw150, BLACK, page_rect150, Qt::WindingFill); + + PolygonRasterizer::fillExcept(bw150, BLACK, xform_150dpi.resultingPreCropArea(), Qt::WindingFill); + if (dbg) { + dbg->add(bw150, "page_mask_applied"); + } + + BinaryImage hor_shadows_seed(openBrick(bw150, QSize(200, 14), BLACK)); + if (dbg) { + dbg->add(hor_shadows_seed, "hor_shadows_seed"); + } + + status.throwIfCancelled(); + + BinaryImage ver_shadows_seed(openBrick(bw150, QSize(14, 300), BLACK)); + if (dbg) { + dbg->add(ver_shadows_seed, "ver_shadows_seed"); + } + + status.throwIfCancelled(); + + BinaryImage shadows_seed(hor_shadows_seed.release()); + rasterOp>(shadows_seed, ver_shadows_seed); + ver_shadows_seed.release(); + if (dbg) { + dbg->add(shadows_seed, "shadows_seed"); + } + + status.throwIfCancelled(); + + BinaryImage dilated(dilateBrick(bw150, QSize(3, 3))); + if (dbg) { + dbg->add(dilated, "dilated"); + } + + status.throwIfCancelled(); + + BinaryImage shadows_dilated(seedFill(shadows_seed, dilated, CONN8)); + dilated.release(); + if (dbg) { + dbg->add(shadows_dilated, "shadows_dilated"); + } + + status.throwIfCancelled(); + + rasterOp>(shadows_dilated, bw150); + BinaryImage garbage(shadows_dilated.release()); + if (dbg) { + dbg->add(garbage, "shadows"); + } + + status.throwIfCancelled(); + + filterShadows(status, garbage, dbg); + if (dbg) { + dbg->add(garbage, "filtered_shadows"); + } + + status.throwIfCancelled(); + + BinaryImage content(bw150.release()); + rasterOp>(content, garbage); + if (dbg) { + dbg->add(content, "content"); + } + + status.throwIfCancelled(); + + Despeckle::Level despeckleLevel = Despeckle::NORMAL; + + BinaryImage despeckled(Despeckle::despeckle(content, Dpi(150, 150), despeckleLevel, status, dbg)); + if (dbg) { + dbg->add(despeckled, "despeckled"); + } + + status.throwIfCancelled(); + + BinaryImage content_blocks(content.size(), BLACK); + const int area_threshold = std::min(content.width(), content.height()); + + { + MaxWhitespaceFinder hor_ws_finder(PreferHorizontal(), despeckled); + + for (int i = 0; i < 80; ++i) { + QRect ws(hor_ws_finder.next(hor_ws_finder.MANUAL_OBSTACLES)); + if (ws.isNull()) { + break; + } + if (ws.width() * ws.height() < area_threshold) { + break; + } + content_blocks.fill(ws, WHITE); + const int height_fraction = ws.height() / 5; + ws.setTop(ws.top() + height_fraction); + ws.setBottom(ws.bottom() - height_fraction); + hor_ws_finder.addObstacle(ws); } - - BinaryImage bw150(binarizeWolf(gray150, QSize(51, 51), 50)); - if (dbg) { - dbg->add(bw150, "bw150"); + } + + { + MaxWhitespaceFinder vert_ws_finder(PreferVertical(), despeckled); + + for (int i = 0; i < 40; ++i) { + QRect ws(vert_ws_finder.next(vert_ws_finder.MANUAL_OBSTACLES)); + if (ws.isNull()) { + break; + } + if (ws.width() * ws.height() < area_threshold) { + break; + } + content_blocks.fill(ws, WHITE); + const int width_fraction = ws.width() / 5; + ws.setLeft(ws.left() + width_fraction); + ws.setRight(ws.right() - width_fraction); + vert_ws_finder.addObstacle(ws); } - - const double xscale = 150.0 / data.xform().origDpi().horizontal(); - const double yscale = 150.0 / data.xform().origDpi().vertical(); - QRectF page_rect150(page_rect.left() * xscale, page_rect.top() * yscale, page_rect.right() * xscale, - page_rect.bottom() * yscale); - PolygonRasterizer::fillExcept(bw150, BLACK, page_rect150, Qt::WindingFill); - - PolygonRasterizer::fillExcept(bw150, BLACK, xform_150dpi.resultingPreCropArea(), Qt::WindingFill); - if (dbg) { - dbg->add(bw150, "page_mask_applied"); + } + + if (dbg) { + dbg->add(content_blocks, "content_blocks"); + } + + trimContentBlocksInPlace(despeckled, content_blocks); + if (dbg) { + dbg->add(content_blocks, "initial_trimming"); + } + + // Do some more whitespace finding. This should help us separate + // blocks that don't belong together. + { + BinaryImage tmp(content); + rasterOp, RopDst>>(tmp, content_blocks); + MaxWhitespaceFinder ws_finder(tmp.release(), QSize(4, 4)); + + for (int i = 0; i < 10; ++i) { + QRect ws(ws_finder.next()); + if (ws.isNull()) { + break; + } + if (ws.width() * ws.height() < area_threshold) { + break; + } + content_blocks.fill(ws, WHITE); } + } + if (dbg) { + dbg->add(content_blocks, "more_whitespace"); + } - BinaryImage hor_shadows_seed(openBrick(bw150, QSize(200, 14), BLACK)); - if (dbg) { - dbg->add(hor_shadows_seed, "hor_shadows_seed"); - } + trimContentBlocksInPlace(despeckled, content_blocks); + if (dbg) { + dbg->add(content_blocks, "more_trimming"); + } - status.throwIfCancelled(); + despeckled.release(); - BinaryImage ver_shadows_seed(openBrick(bw150, QSize(14, 300), BLACK)); - if (dbg) { - dbg->add(ver_shadows_seed, "ver_shadows_seed"); - } + inPlaceRemoveAreasTouchingBorders(content_blocks, dbg); + if (dbg) { + dbg->add(content_blocks, "except_bordering"); + } - status.throwIfCancelled(); + BinaryImage text_mask(estimateTextMask(content, content_blocks, dbg)); + if (dbg) { + QImage text_mask_visualized(content.size(), QImage::Format_ARGB32_Premultiplied); + text_mask_visualized.fill(0xffffffff); // Opaque white. + QPainter painter(&text_mask_visualized); - BinaryImage shadows_seed(hor_shadows_seed.release()); - rasterOp>(shadows_seed, ver_shadows_seed); - ver_shadows_seed.release(); - if (dbg) { - dbg->add(shadows_seed, "shadows_seed"); - } + QImage tmp(content.size(), QImage::Format_ARGB32_Premultiplied); + tmp.fill(0xff64dd62); // Opaque light green. + tmp.setAlphaChannel(text_mask.inverted().toQImage()); + painter.drawImage(QPoint(0, 0), tmp); - status.throwIfCancelled(); + tmp.fill(0xe0000000); // Mostly transparent black. + tmp.setAlphaChannel(content.inverted().toQImage()); + painter.drawImage(QPoint(0, 0), tmp); - BinaryImage dilated(dilateBrick(bw150, QSize(3, 3))); - if (dbg) { - dbg->add(dilated, "dilated"); - } + painter.end(); - status.throwIfCancelled(); + dbg->add(text_mask_visualized, "text_mask"); + } - BinaryImage shadows_dilated(seedFill(shadows_seed, dilated, CONN8)); - dilated.release(); - if (dbg) { - dbg->add(shadows_dilated, "shadows_dilated"); - } + // Make text_mask strore the actual content pixels that are text. + rasterOp>(text_mask, content); - status.throwIfCancelled(); + QRect content_rect(content_blocks.contentBoundingBox()); + // Temporarily reuse hor_shadows_seed and ver_shadows_seed. + // It's OK they are null. + segmentGarbage(garbage, hor_shadows_seed, ver_shadows_seed, dbg); + garbage.release(); - rasterOp>(shadows_dilated, bw150); - BinaryImage garbage(shadows_dilated.release()); - if (dbg) { - dbg->add(garbage, "shadows"); - } + if (dbg) { + dbg->add(hor_shadows_seed, "initial_hor_garbage"); + dbg->add(ver_shadows_seed, "initial_vert_garbage"); + } - status.throwIfCancelled(); + Garbage hor_garbage(Garbage::HOR, hor_shadows_seed.release()); + Garbage vert_garbage(Garbage::VERT, ver_shadows_seed.release()); - filterShadows(status, garbage, dbg); - if (dbg) { - dbg->add(garbage, "filtered_shadows"); - } + enum Side { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; - status.throwIfCancelled(); + int side_mask = LEFT | RIGHT | TOP | BOTTOM; - BinaryImage content(bw150.release()); - rasterOp>(content, garbage); - if (dbg) { - dbg->add(content, "content"); - } + while (side_mask && !content_rect.isEmpty()) { + QRect old_content_rect; - status.throwIfCancelled(); + if (side_mask & LEFT) { + side_mask &= ~LEFT; + old_content_rect = content_rect; + content_rect = trimLeft(content, content_blocks, text_mask, content_rect, vert_garbage, dbg); - Despeckle::Level despeckleLevel = Despeckle::NORMAL; + status.throwIfCancelled(); - BinaryImage despeckled(Despeckle::despeckle(content, Dpi(150, 150), despeckleLevel, status, dbg)); - if (dbg) { - dbg->add(despeckled, "despeckled"); + if (content_rect.isEmpty()) { + break; + } + if (old_content_rect != content_rect) { + side_mask |= LEFT | TOP | BOTTOM; + } } - status.throwIfCancelled(); - - BinaryImage content_blocks(content.size(), BLACK); - const int area_threshold = std::min(content.width(), content.height()); - - { - MaxWhitespaceFinder hor_ws_finder(PreferHorizontal(), despeckled); - - for (int i = 0; i < 80; ++i) { - QRect ws(hor_ws_finder.next(hor_ws_finder.MANUAL_OBSTACLES)); - if (ws.isNull()) { - break; - } - if (ws.width() * ws.height() < area_threshold) { - break; - } - content_blocks.fill(ws, WHITE); - const int height_fraction = ws.height() / 5; - ws.setTop(ws.top() + height_fraction); - ws.setBottom(ws.bottom() - height_fraction); - hor_ws_finder.addObstacle(ws); - } - } + if (side_mask & RIGHT) { + side_mask &= ~RIGHT; + old_content_rect = content_rect; + content_rect = trimRight(content, content_blocks, text_mask, content_rect, vert_garbage, dbg); - { - MaxWhitespaceFinder vert_ws_finder(PreferVertical(), despeckled); - - for (int i = 0; i < 40; ++i) { - QRect ws(vert_ws_finder.next(vert_ws_finder.MANUAL_OBSTACLES)); - if (ws.isNull()) { - break; - } - if (ws.width() * ws.height() < area_threshold) { - break; - } - content_blocks.fill(ws, WHITE); - const int width_fraction = ws.width() / 5; - ws.setLeft(ws.left() + width_fraction); - ws.setRight(ws.right() - width_fraction); - vert_ws_finder.addObstacle(ws); - } - } + status.throwIfCancelled(); - if (dbg) { - dbg->add(content_blocks, "content_blocks"); + if (content_rect.isEmpty()) { + break; + } + if (old_content_rect != content_rect) { + side_mask |= RIGHT | TOP | BOTTOM; + } } - trimContentBlocksInPlace(despeckled, content_blocks); - if (dbg) { - dbg->add(content_blocks, "initial_trimming"); - } + if (side_mask & TOP) { + side_mask &= ~TOP; + old_content_rect = content_rect; + content_rect = trimTop(content, content_blocks, text_mask, content_rect, hor_garbage, dbg); - // Do some more whitespace finding. This should help us separate - // blocks that don't belong together. - { - BinaryImage tmp(content); - rasterOp, RopDst>>(tmp, content_blocks); - MaxWhitespaceFinder ws_finder(tmp.release(), QSize(4, 4)); - - for (int i = 0; i < 10; ++i) { - QRect ws(ws_finder.next()); - if (ws.isNull()) { - break; - } - if (ws.width() * ws.height() < area_threshold) { - break; - } - content_blocks.fill(ws, WHITE); - } - } - if (dbg) { - dbg->add(content_blocks, "more_whitespace"); - } + status.throwIfCancelled(); - trimContentBlocksInPlace(despeckled, content_blocks); - if (dbg) { - dbg->add(content_blocks, "more_trimming"); + if (content_rect.isEmpty()) { + break; + } + if (old_content_rect != content_rect) { + side_mask |= TOP | LEFT | RIGHT; + } } - despeckled.release(); - - inPlaceRemoveAreasTouchingBorders(content_blocks, dbg); - if (dbg) { - dbg->add(content_blocks, "except_bordering"); - } - - BinaryImage text_mask(estimateTextMask(content, content_blocks, dbg)); - if (dbg) { - QImage text_mask_visualized(content.size(), QImage::Format_ARGB32_Premultiplied); - text_mask_visualized.fill(0xffffffff); // Opaque white. - QPainter painter(&text_mask_visualized); + if (side_mask & BOTTOM) { + side_mask &= ~BOTTOM; + old_content_rect = content_rect; + content_rect = trimBottom(content, content_blocks, text_mask, content_rect, hor_garbage, dbg); - QImage tmp(content.size(), QImage::Format_ARGB32_Premultiplied); - tmp.fill(0xff64dd62); // Opaque light green. - tmp.setAlphaChannel(text_mask.inverted().toQImage()); - painter.drawImage(QPoint(0, 0), tmp); + status.throwIfCancelled(); - tmp.fill(0xe0000000); // Mostly transparent black. - tmp.setAlphaChannel(content.inverted().toQImage()); - painter.drawImage(QPoint(0, 0), tmp); - - painter.end(); - - dbg->add(text_mask_visualized, "text_mask"); + if (content_rect.isEmpty()) { + break; + } + if (old_content_rect != content_rect) { + side_mask |= BOTTOM | LEFT | RIGHT; + } } - // Make text_mask strore the actual content pixels that are text. - rasterOp>(text_mask, content); - - QRect content_rect(content_blocks.contentBoundingBox()); - // Temporarily reuse hor_shadows_seed and ver_shadows_seed. - // It's OK they are null. - segmentGarbage(garbage, hor_shadows_seed, ver_shadows_seed, dbg); - garbage.release(); - - if (dbg) { - dbg->add(hor_shadows_seed, "initial_hor_garbage"); - dbg->add(ver_shadows_seed, "initial_vert_garbage"); + if ((content_rect.width() < 8) || (content_rect.height() < 8)) { + content_rect = QRect(); + break; + } else if ((content_rect.width() < 30) && (content_rect.height() > content_rect.width() * 20)) { + content_rect = QRect(); + break; } + } - Garbage hor_garbage(Garbage::HOR, hor_shadows_seed.release()); - Garbage vert_garbage(Garbage::VERT, ver_shadows_seed.release()); - - enum Side { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; - - int side_mask = LEFT | RIGHT | TOP | BOTTOM; - - while (side_mask && !content_rect.isEmpty()) { - QRect old_content_rect; + // Increase the content rect due to cutting off the content at edges because of rescaling made. + if (!content_rect.isEmpty()) { + content_rect.adjust(-1, -1, 1, 1); + } - if (side_mask & LEFT) { - side_mask &= ~LEFT; - old_content_rect = content_rect; - content_rect = trimLeft(content, content_blocks, text_mask, content_rect, vert_garbage, dbg); + // Transform back from 150dpi. + QTransform combined_xform(xform_150dpi.transform().inverted()); + combined_xform *= data.xform().transform(); - status.throwIfCancelled(); - - if (content_rect.isEmpty()) { - break; - } - if (old_content_rect != content_rect) { - side_mask |= LEFT | TOP | BOTTOM; - } - } - - if (side_mask & RIGHT) { - side_mask &= ~RIGHT; - old_content_rect = content_rect; - content_rect = trimRight(content, content_blocks, text_mask, content_rect, vert_garbage, dbg); - - status.throwIfCancelled(); - - if (content_rect.isEmpty()) { - break; - } - if (old_content_rect != content_rect) { - side_mask |= RIGHT | TOP | BOTTOM; - } - } - - if (side_mask & TOP) { - side_mask &= ~TOP; - old_content_rect = content_rect; - content_rect = trimTop(content, content_blocks, text_mask, content_rect, hor_garbage, dbg); - - status.throwIfCancelled(); - - if (content_rect.isEmpty()) { - break; - } - if (old_content_rect != content_rect) { - side_mask |= TOP | LEFT | RIGHT; - } - } - - if (side_mask & BOTTOM) { - side_mask &= ~BOTTOM; - old_content_rect = content_rect; - content_rect = trimBottom(content, content_blocks, text_mask, content_rect, hor_garbage, dbg); - - status.throwIfCancelled(); - - if (content_rect.isEmpty()) { - break; - } - if (old_content_rect != content_rect) { - side_mask |= BOTTOM | LEFT | RIGHT; - } - } - - if ((content_rect.width() < 8) || (content_rect.height() < 8)) { - content_rect = QRect(); - break; - } else if ((content_rect.width() < 30) && (content_rect.height() > content_rect.width() * 20)) { - content_rect = QRect(); - break; - } - } - - // Increase the content rect due to cutting off the content at edges because of rescaling made. - if (!content_rect.isEmpty()) { - content_rect.adjust(-1, -1, 1, 1); - } - - // Transform back from 150dpi. - QTransform combined_xform(xform_150dpi.transform().inverted()); - combined_xform *= data.xform().transform(); - - return combined_xform.map(QRectF(content_rect)).boundingRect().intersected(data.xform().resultingRect()); + return combined_xform.map(QRectF(content_rect)).boundingRect().intersected(data.xform().resultingRect()); } // ContentBoxFinder::findContentBox namespace { struct Bounds { - // All are inclusive. - int left; - int right; - int top; - int bottom; - - Bounds() : left(INT_MAX), right(INT_MIN), top(INT_MAX), bottom(INT_MIN) { + // All are inclusive. + int left; + int right; + int top; + int bottom; + + Bounds() : left(INT_MAX), right(INT_MIN), top(INT_MAX), bottom(INT_MIN) {} + + bool isInside(int x, int y) const { + if (x < left) { + return false; + } else if (x > right) { + return false; + } else if (y < top) { + return false; + } else if (y > bottom) { + return false; + } else { + return true; } + } - bool isInside(int x, int y) const { - if (x < left) { - return false; - } else if (x > right) { - return false; - } else if (y < top) { - return false; - } else if (y > bottom) { - return false; - } else { - return true; - } + void forceInside(int x, int y) { + if (x < left) { + left = x; } - - void forceInside(int x, int y) { - if (x < left) { - left = x; - } - if (x > right) { - right = x; - } - if (y < top) { - top = y; - } - if (y > bottom) { - bottom = y; - } + if (x > right) { + right = x; + } + if (y < top) { + top = y; } + if (y > bottom) { + bottom = y; + } + } }; } // namespace void ContentBoxFinder::trimContentBlocksInPlace(const imageproc::BinaryImage& content, imageproc::BinaryImage& content_blocks) { - const ConnectivityMap cmap(content_blocks, CONN4); - std::vector bounds(cmap.maxLabel() + 1); - - int width = content.width(); - int height = content.height(); - const uint32_t msb = uint32_t(1) << 31; - - const uint32_t* content_line = content.data(); - const int content_stride = content.wordsPerLine(); - const uint32_t* cmap_line = cmap.data(); - const int cmap_stride = cmap.stride(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = cmap_line[x]; - if (label == 0) { - continue; - } - if (content_line[x >> 5] & (msb >> (x & 31))) { - bounds[label].forceInside(x, y); - } - } - cmap_line += cmap_stride; - content_line += content_stride; + const ConnectivityMap cmap(content_blocks, CONN4); + std::vector bounds(cmap.maxLabel() + 1); + + int width = content.width(); + int height = content.height(); + const uint32_t msb = uint32_t(1) << 31; + + const uint32_t* content_line = content.data(); + const int content_stride = content.wordsPerLine(); + const uint32_t* cmap_line = cmap.data(); + const int cmap_stride = cmap.stride(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = cmap_line[x]; + if (label == 0) { + continue; + } + if (content_line[x >> 5] & (msb >> (x & 31))) { + bounds[label].forceInside(x, y); + } } - - uint32_t* cb_line = content_blocks.data(); - const int cb_stride = content_blocks.wordsPerLine(); - cmap_line = cmap.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = cmap_line[x]; - if (label == 0) { - continue; - } - if (!bounds[label].isInside(x, y)) { - cb_line[x >> 5] &= ~(msb >> (x & 31)); - } - } - cmap_line += cmap_stride; - cb_line += cb_stride; + cmap_line += cmap_stride; + content_line += content_stride; + } + + uint32_t* cb_line = content_blocks.data(); + const int cb_stride = content_blocks.wordsPerLine(); + cmap_line = cmap.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = cmap_line[x]; + if (label == 0) { + continue; + } + if (!bounds[label].isInside(x, y)) { + cb_line[x >> 5] &= ~(msb >> (x & 31)); + } } + cmap_line += cmap_stride; + cb_line += cb_stride; + } } // ContentBoxFinder::trimContentBlocksInPlace void ContentBoxFinder::inPlaceRemoveAreasTouchingBorders(imageproc::BinaryImage& content_blocks, DebugImages* dbg) { - // We could just do a seed fill from borders, but that - // has the potential to remove too much. Instead, we - // do something similar to a seed fill, but with a limited - // spread distance. + // We could just do a seed fill from borders, but that + // has the potential to remove too much. Instead, we + // do something similar to a seed fill, but with a limited + // spread distance. - const int width = content_blocks.width(); - const int height = content_blocks.height(); + const int width = content_blocks.width(); + const int height = content_blocks.height(); - const auto max_spread_dist = static_cast(std::min(width, height) / 4); + const auto max_spread_dist = static_cast(std::min(width, height) / 4); - std::vector map((width + 2) * (height + 2), ~uint16_t(0)); + std::vector map((width + 2) * (height + 2), ~uint16_t(0)); - uint32_t* cb_line = content_blocks.data(); - const int cb_stride = content_blocks.wordsPerLine(); - uint16_t* map_line = &map[0] + width + 3; - const int map_stride = width + 2; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t mask = cb_line[x >> 5] >> (31 - (x & 31)); - mask &= uint32_t(1); - --mask; + uint32_t* cb_line = content_blocks.data(); + const int cb_stride = content_blocks.wordsPerLine(); + uint16_t* map_line = &map[0] + width + 3; + const int map_stride = width + 2; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t mask = cb_line[x >> 5] >> (31 - (x & 31)); + mask &= uint32_t(1); + --mask; - // WHITE -> max, BLACK -> 0 - map_line[x] = static_cast(mask); - } - map_line += map_stride; - cb_line += cb_stride; + // WHITE -> max, BLACK -> 0 + map_line[x] = static_cast(mask); } - - std::queue queue; - // Initialize border seeds. - map_line = &map[0] + width + 3; - for (int x = 0; x < width; ++x) { - if (map_line[x] == 0) { - map_line[x] = max_spread_dist; - queue.push(&map_line[x]); - } + map_line += map_stride; + cb_line += cb_stride; + } + + std::queue queue; + // Initialize border seeds. + map_line = &map[0] + width + 3; + for (int x = 0; x < width; ++x) { + if (map_line[x] == 0) { + map_line[x] = max_spread_dist; + queue.push(&map_line[x]); } - for (int y = 1; y < height - 1; ++y) { - if (map_line[0] == 0) { - map_line[0] = max_spread_dist; - queue.push(&map_line[0]); - } - if (map_line[width - 1] == 0) { - map_line[width - 1] = max_spread_dist; - queue.push(&map_line[width - 1]); - } - map_line += map_stride; + } + for (int y = 1; y < height - 1; ++y) { + if (map_line[0] == 0) { + map_line[0] = max_spread_dist; + queue.push(&map_line[0]); } - for (int x = 0; x < width; ++x) { - if (map_line[x] == 0) { - map_line[x] = max_spread_dist; - queue.push(&map_line[x]); - } + if (map_line[width - 1] == 0) { + map_line[width - 1] = max_spread_dist; + queue.push(&map_line[width - 1]); } - - if (queue.empty()) { - // Common case optimization. - return; + map_line += map_stride; + } + for (int x = 0; x < width; ++x) { + if (map_line[x] == 0) { + map_line[x] = max_spread_dist; + queue.push(&map_line[x]); } + } - while (!queue.empty()) { - uint16_t* cell = queue.front(); - queue.pop(); + if (queue.empty()) { + // Common case optimization. + return; + } - assert(*cell != 0); - const auto new_dist = static_cast(*cell - 1); + while (!queue.empty()) { + uint16_t* cell = queue.front(); + queue.pop(); - uint16_t* nbh = cell - map_stride; - if (new_dist > *nbh) { - *nbh = new_dist; - queue.push(nbh); - } + assert(*cell != 0); + const auto new_dist = static_cast(*cell - 1); - nbh = cell - 1; - if (new_dist > *nbh) { - *nbh = new_dist; - queue.push(nbh); - } + uint16_t* nbh = cell - map_stride; + if (new_dist > *nbh) { + *nbh = new_dist; + queue.push(nbh); + } - nbh = cell + 1; - if (new_dist > *nbh) { - *nbh = new_dist; - queue.push(nbh); - } + nbh = cell - 1; + if (new_dist > *nbh) { + *nbh = new_dist; + queue.push(nbh); + } - nbh = cell + map_stride; - if (new_dist > *nbh) { - *nbh = new_dist; - queue.push(nbh); - } + nbh = cell + 1; + if (new_dist > *nbh) { + *nbh = new_dist; + queue.push(nbh); } - cb_line = content_blocks.data(); - map_line = &map[0] + width + 3; - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (map_line[x] + 1 > 1) { // If not 0 or ~uint16_t(0) - cb_line[x >> 5] &= ~(msb >> (x & 31)); - } - } - map_line += map_stride; - cb_line += cb_stride; + nbh = cell + map_stride; + if (new_dist > *nbh) { + *nbh = new_dist; + queue.push(nbh); } + } + + cb_line = content_blocks.data(); + map_line = &map[0] + width + 3; + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (map_line[x] + 1 > 1) { // If not 0 or ~uint16_t(0) + cb_line[x >> 5] &= ~(msb >> (x & 31)); + } + } + map_line += map_stride; + cb_line += cb_stride; + } } // ContentBoxFinder::inPlaceRemoveAreasTouchingBorders void ContentBoxFinder::segmentGarbage(const imageproc::BinaryImage& garbage, imageproc::BinaryImage& hor_garbage, imageproc::BinaryImage& vert_garbage, DebugImages* dbg) { - hor_garbage = openBrick(garbage, QSize(200, 1), WHITE); - - QRect rect(garbage.rect()); - rect.setHeight(1); - rasterOp>(hor_garbage, rect, garbage, rect.topLeft()); - rect.moveBottom(garbage.rect().bottom()); - rasterOp>(hor_garbage, rect, garbage, rect.topLeft()); - - vert_garbage = openBrick(garbage, QSize(1, 200), WHITE); - - rect = garbage.rect(); - rect.setWidth(1); - rasterOp>(vert_garbage, rect, garbage, rect.topLeft()); - rect.moveRight(garbage.rect().right()); - rasterOp>(vert_garbage, rect, garbage, rect.topLeft()); - - ConnectivityMap cmap(garbage.size()); - - cmap.addComponent(vert_garbage); - vert_garbage.fill(WHITE); - cmap.addComponent(hor_garbage); - hor_garbage.fill(WHITE); - - InfluenceMap imap(cmap, garbage); - cmap = ConnectivityMap(); - - const int width = garbage.width(); - const int height = garbage.height(); - - InfluenceMap::Cell* imap_line = imap.data(); - const int imap_stride = imap.stride(); - - uint32_t* vg_line = vert_garbage.data(); - const int vg_stride = vert_garbage.wordsPerLine(); - uint32_t* hg_line = hor_garbage.data(); - const int hg_stride = hor_garbage.wordsPerLine(); - - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - switch (imap_line[x].label) { - case 1: - vg_line[x >> 5] |= msb >> (x & 31); - break; - case 2: - hg_line[x >> 5] |= msb >> (x & 31); - break; - default: - break; - } - } - imap_line += imap_stride; - vg_line += vg_stride; - hg_line += hg_stride; + hor_garbage = openBrick(garbage, QSize(200, 1), WHITE); + + QRect rect(garbage.rect()); + rect.setHeight(1); + rasterOp>(hor_garbage, rect, garbage, rect.topLeft()); + rect.moveBottom(garbage.rect().bottom()); + rasterOp>(hor_garbage, rect, garbage, rect.topLeft()); + + vert_garbage = openBrick(garbage, QSize(1, 200), WHITE); + + rect = garbage.rect(); + rect.setWidth(1); + rasterOp>(vert_garbage, rect, garbage, rect.topLeft()); + rect.moveRight(garbage.rect().right()); + rasterOp>(vert_garbage, rect, garbage, rect.topLeft()); + + ConnectivityMap cmap(garbage.size()); + + cmap.addComponent(vert_garbage); + vert_garbage.fill(WHITE); + cmap.addComponent(hor_garbage); + hor_garbage.fill(WHITE); + + InfluenceMap imap(cmap, garbage); + cmap = ConnectivityMap(); + + const int width = garbage.width(); + const int height = garbage.height(); + + InfluenceMap::Cell* imap_line = imap.data(); + const int imap_stride = imap.stride(); + + uint32_t* vg_line = vert_garbage.data(); + const int vg_stride = vert_garbage.wordsPerLine(); + uint32_t* hg_line = hor_garbage.data(); + const int hg_stride = hor_garbage.wordsPerLine(); + + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (imap_line[x].label) { + case 1: + vg_line[x >> 5] |= msb >> (x & 31); + break; + case 2: + hg_line[x >> 5] |= msb >> (x & 31); + break; + default: + break; + } } + imap_line += imap_stride; + vg_line += vg_stride; + hg_line += hg_stride; + } - BinaryImage unconnected_garbage(garbage); - rasterOp>(unconnected_garbage, hor_garbage); - rasterOp>(unconnected_garbage, vert_garbage); + BinaryImage unconnected_garbage(garbage); + rasterOp>(unconnected_garbage, hor_garbage); + rasterOp>(unconnected_garbage, vert_garbage); - rasterOp>(hor_garbage, unconnected_garbage); - rasterOp>(vert_garbage, unconnected_garbage); + rasterOp>(hor_garbage, unconnected_garbage); + rasterOp>(vert_garbage, unconnected_garbage); } // ContentBoxFinder::segmentGarbage imageproc::BinaryImage ContentBoxFinder::estimateTextMask(const imageproc::BinaryImage& content, const imageproc::BinaryImage& content_blocks, DebugImages* dbg) { - // We differentiate between a text line and a slightly skewed straight - // line (which may have a fill factor similar to that of text) by the - // presence of ultimate eroded points. - - const BinaryImage ueps(SEDM(content, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS).findPeaksDestructive()); - if (dbg) { - QImage canvas(content_blocks.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QPainter painter; - painter.begin(&canvas); - QImage overlay(canvas.size(), canvas.format()); - overlay.fill(0xff0000ff); // opaque blue - overlay.setAlphaChannel(content.inverted().toQImage()); - painter.drawImage(QPoint(0, 0), overlay); - - BinaryImage ueps_on_content_blocks(content_blocks); - rasterOp>(ueps_on_content_blocks, ueps); - - overlay.fill(0xffffff00); // opaque yellow - overlay.setAlphaChannel(ueps_on_content_blocks.inverted().toQImage()); - painter.drawImage(QPoint(0, 0), overlay); - - painter.end(); - dbg->add(canvas, "ueps"); + // We differentiate between a text line and a slightly skewed straight + // line (which may have a fill factor similar to that of text) by the + // presence of ultimate eroded points. + + const BinaryImage ueps(SEDM(content, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS).findPeaksDestructive()); + if (dbg) { + QImage canvas(content_blocks.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QPainter painter; + painter.begin(&canvas); + QImage overlay(canvas.size(), canvas.format()); + overlay.fill(0xff0000ff); // opaque blue + overlay.setAlphaChannel(content.inverted().toQImage()); + painter.drawImage(QPoint(0, 0), overlay); + + BinaryImage ueps_on_content_blocks(content_blocks); + rasterOp>(ueps_on_content_blocks, ueps); + + overlay.fill(0xffffff00); // opaque yellow + overlay.setAlphaChannel(ueps_on_content_blocks.inverted().toQImage()); + painter.drawImage(QPoint(0, 0), overlay); + + painter.end(); + dbg->add(canvas, "ueps"); + } + + BinaryImage text_mask(content.size(), WHITE); + + const int min_text_height = 6; + + ConnCompEraserExt eraser(content_blocks, CONN4); + while (true) { + const ConnComp cc(eraser.nextConnComp()); + if (cc.isNull()) { + break; } - BinaryImage text_mask(content.size(), WHITE); - - const int min_text_height = 6; - - ConnCompEraserExt eraser(content_blocks, CONN4); - for (;;) { - const ConnComp cc(eraser.nextConnComp()); - if (cc.isNull()) { - break; + BinaryImage cc_img(eraser.computeConnCompImage()); + BinaryImage content_img(cc_img.size()); + rasterOp(content_img, content_img.rect(), content, cc.rect().topLeft()); + + // Note that some content may actually be not masked + // by content_blocks, because we build content_blocks + // based on despeckled content image. + rasterOp>(content_img, cc_img); + + const SlicedHistogram hist(content_img, SlicedHistogram::ROWS); + const SlicedHistogram block_hist(cc_img, SlicedHistogram::ROWS); + + assert(hist.size() != 0); + + typedef std::pair Range; + std::vector ranges; + std::vector splittable_ranges; + splittable_ranges.emplace_back(&hist[0], &hist[hist.size() - 1]); + + std::vector max_forward(hist.size()); + std::vector max_backwards(hist.size()); + + // Try splitting text lines. + while (!splittable_ranges.empty()) { + const int* const first = splittable_ranges.back().first; + const int* const last = splittable_ranges.back().second; + splittable_ranges.pop_back(); + + if (last - first < min_text_height - 1) { + // Just ignore such a small segment. + continue; + } + // Fill max_forward and max_backwards. + { + int prev = *first; + for (int i = 0; i <= last - first; ++i) { + prev = std::max(prev, first[i]); + max_forward[i] = prev; + } + prev = *last; + for (int i = 0; i <= last - first; ++i) { + prev = std::max(prev, last[-i]); + max_backwards[i] = prev; + } + } + + int best_magnitude = std::numeric_limits::min(); + const int* best_split_pos = nullptr; + assert(first != last); + for (const int* p = first + 1; p != last; ++p) { + const int peak1 = max_forward[p - (first + 1)]; + const int peak2 = max_backwards[(last - 1) - p]; + if (*p * 3.5 > 0.5 * (peak1 + peak2)) { + continue; + } + const int shoulder1 = peak1 - *p; + const int shoulder2 = peak2 - *p; + if ((shoulder1 <= 0) || (shoulder2 <= 0)) { + continue; + } + if (std::min(shoulder1, shoulder2) * 20 < std::max(shoulder1, shoulder2)) { + continue; } - BinaryImage cc_img(eraser.computeConnCompImage()); - BinaryImage content_img(cc_img.size()); - rasterOp(content_img, content_img.rect(), content, cc.rect().topLeft()); - - // Note that some content may actually be not masked - // by content_blocks, because we build content_blocks - // based on despeckled content image. - rasterOp>(content_img, cc_img); - - const SlicedHistogram hist(content_img, SlicedHistogram::ROWS); - const SlicedHistogram block_hist(cc_img, SlicedHistogram::ROWS); - - assert(hist.size() != 0); - - typedef std::pair Range; - std::vector ranges; - std::vector splittable_ranges; - splittable_ranges.emplace_back(&hist[0], &hist[hist.size() - 1]); - - std::vector max_forward(hist.size()); - std::vector max_backwards(hist.size()); - - // Try splitting text lines. - while (!splittable_ranges.empty()) { - const int* const first = splittable_ranges.back().first; - const int* const last = splittable_ranges.back().second; - splittable_ranges.pop_back(); - - if (last - first < min_text_height - 1) { - // Just ignore such a small segment. - continue; - } - // Fill max_forward and max_backwards. - { - int prev = *first; - for (int i = 0; i <= last - first; ++i) { - prev = std::max(prev, first[i]); - max_forward[i] = prev; - } - prev = *last; - for (int i = 0; i <= last - first; ++i) { - prev = std::max(prev, last[-i]); - max_backwards[i] = prev; - } - } - - int best_magnitude = std::numeric_limits::min(); - const int* best_split_pos = nullptr; - assert(first != last); - for (const int* p = first + 1; p != last; ++p) { - const int peak1 = max_forward[p - (first + 1)]; - const int peak2 = max_backwards[(last - 1) - p]; - if (*p * 3.5 > 0.5 * (peak1 + peak2)) { - continue; - } - const int shoulder1 = peak1 - *p; - const int shoulder2 = peak2 - *p; - if ((shoulder1 <= 0) || (shoulder2 <= 0)) { - continue; - } - if (std::min(shoulder1, shoulder2) * 20 < std::max(shoulder1, shoulder2)) { - continue; - } - - const int magnitude = shoulder1 + shoulder2; - if (magnitude > best_magnitude) { - best_magnitude = magnitude; - best_split_pos = p; - } - } - - if (best_split_pos) { - splittable_ranges.emplace_back(first, best_split_pos - 1); - splittable_ranges.emplace_back(best_split_pos + 1, last); - } else { - ranges.emplace_back(first, last); - } + const int magnitude = shoulder1 + shoulder2; + if (magnitude > best_magnitude) { + best_magnitude = magnitude; + best_split_pos = p; } + } + + if (best_split_pos) { + splittable_ranges.emplace_back(first, best_split_pos - 1); + splittable_ranges.emplace_back(best_split_pos + 1, last); + } else { + ranges.emplace_back(first, last); + } + } - for (const Range range : ranges) { - const auto first = static_cast(range.first - &hist[0]); - const auto last = static_cast(range.second - &hist[0]); - if (last - first < min_text_height - 1) { - continue; - } - - int64_t weighted_y = 0; - int total_weight = 0; - for (int i = first; i <= last; ++i) { - const int val = hist[i]; - weighted_y += val * i; - total_weight += val; - } - - if (total_weight == 0) { - // qDebug() << "no black pixels at all"; - continue; - } - - const double min_fill_factor = 0.22; - const double max_fill_factor = 0.65; - - const auto center_y = static_cast((weighted_y + total_weight / 2) / total_weight); - int top = center_y - min_text_height / 2; - int bottom = top + min_text_height - 1; - int num_black = 0; - int num_total = 0; - int max_width = 0; - if ((top < first) || (bottom > last)) { - continue; - } - for (int i = top; i <= bottom; ++i) { - num_black += hist[i]; - num_total += block_hist[i]; - max_width = std::max(max_width, block_hist[i]); - } - if (num_black < num_total * min_fill_factor) { - // qDebug() << "initial fill factor too low"; - continue; - } - if (num_black > num_total * max_fill_factor) { - // qDebug() << "initial fill factor too high"; - continue; - } - - // Extend the top and bottom of the text line. - while ((top > first || bottom < last) && std::abs((center_y - top) - (bottom - center_y)) <= 1) { - const int new_top = (top > first) ? top - 1 : top; - const int new_bottom = (bottom < last) ? bottom + 1 : bottom; - num_black += hist[new_top] + hist[new_bottom]; - num_total += block_hist[new_top] + block_hist[new_bottom]; - if (num_black < num_total * min_fill_factor) { - break; - } - max_width = std::max(max_width, block_hist[new_top]); - max_width = std::max(max_width, block_hist[new_bottom]); - top = new_top; - bottom = new_bottom; - } - - if (num_black > num_total * max_fill_factor) { - // qDebug() << "final fill factor too high"; - continue; - } - - if (max_width < (bottom - top + 1) * 0.6) { - // qDebug() << "aspect ratio too low"; - continue; - } - - QRect line_rect(cc.rect()); - line_rect.setTop(cc.rect().top() + top); - line_rect.setBottom(cc.rect().top() + bottom); - - // Check if there are enough ultimate eroded points on the line. - auto ueps_todo = int(0.4 * line_rect.width() / line_rect.height()); - if (ueps_todo) { - BinaryImage line_ueps(line_rect.size()); - rasterOp(line_ueps, line_ueps.rect(), content_blocks, line_rect.topLeft()); - rasterOp>(line_ueps, line_ueps.rect(), ueps, line_rect.topLeft()); - ConnCompEraser ueps_eraser(line_ueps, CONN4); - ConnComp cc; - for (; ueps_todo && !(cc = ueps_eraser.nextConnComp()).isNull(); --ueps_todo) { - // Erase components until ueps_todo reaches zero or there are no more components. - } - if (ueps_todo) { - // Not enough ueps were found. - // qDebug() << "Not enough UEPs."; - continue; - } - } - // Write this block to the text mask. - rasterOp>(text_mask, line_rect, cc_img, QPoint(0, top)); + for (const Range range : ranges) { + const auto first = static_cast(range.first - &hist[0]); + const auto last = static_cast(range.second - &hist[0]); + if (last - first < min_text_height - 1) { + continue; + } + + int64_t weighted_y = 0; + int total_weight = 0; + for (int i = first; i <= last; ++i) { + const int val = hist[i]; + weighted_y += val * i; + total_weight += val; + } + + if (total_weight == 0) { + // qDebug() << "no black pixels at all"; + continue; + } + + const double min_fill_factor = 0.22; + const double max_fill_factor = 0.65; + + const auto center_y = static_cast((weighted_y + total_weight / 2) / total_weight); + int top = center_y - min_text_height / 2; + int bottom = top + min_text_height - 1; + int num_black = 0; + int num_total = 0; + int max_width = 0; + if ((top < first) || (bottom > last)) { + continue; + } + for (int i = top; i <= bottom; ++i) { + num_black += hist[i]; + num_total += block_hist[i]; + max_width = std::max(max_width, block_hist[i]); + } + if (num_black < num_total * min_fill_factor) { + // qDebug() << "initial fill factor too low"; + continue; + } + if (num_black > num_total * max_fill_factor) { + // qDebug() << "initial fill factor too high"; + continue; + } + + // Extend the top and bottom of the text line. + while ((top > first || bottom < last) && std::abs((center_y - top) - (bottom - center_y)) <= 1) { + const int new_top = (top > first) ? top - 1 : top; + const int new_bottom = (bottom < last) ? bottom + 1 : bottom; + num_black += hist[new_top] + hist[new_bottom]; + num_total += block_hist[new_top] + block_hist[new_bottom]; + if (num_black < num_total * min_fill_factor) { + break; + } + max_width = std::max(max_width, block_hist[new_top]); + max_width = std::max(max_width, block_hist[new_bottom]); + top = new_top; + bottom = new_bottom; + } + + if (num_black > num_total * max_fill_factor) { + // qDebug() << "final fill factor too high"; + continue; + } + + if (max_width < (bottom - top + 1) * 0.6) { + // qDebug() << "aspect ratio too low"; + continue; + } + + QRect line_rect(cc.rect()); + line_rect.setTop(cc.rect().top() + top); + line_rect.setBottom(cc.rect().top() + bottom); + + // Check if there are enough ultimate eroded points on the line. + auto ueps_todo = int(0.4 * line_rect.width() / line_rect.height()); + if (ueps_todo) { + BinaryImage line_ueps(line_rect.size()); + rasterOp(line_ueps, line_ueps.rect(), content_blocks, line_rect.topLeft()); + rasterOp>(line_ueps, line_ueps.rect(), ueps, line_rect.topLeft()); + ConnCompEraser ueps_eraser(line_ueps, CONN4); + ConnComp cc; + for (; ueps_todo && !(cc = ueps_eraser.nextConnComp()).isNull(); --ueps_todo) { + // Erase components until ueps_todo reaches zero or there are no more components. } + if (ueps_todo) { + // Not enough ueps were found. + // qDebug() << "Not enough UEPs."; + continue; + } + } + // Write this block to the text mask. + rasterOp>(text_mask, line_rect, cc_img, QPoint(0, top)); } + } - return text_mask; + return text_mask; } // ContentBoxFinder::estimateTextMask QRect ContentBoxFinder::trimLeft(const imageproc::BinaryImage& content, @@ -888,45 +886,45 @@ QRect ContentBoxFinder::trimLeft(const imageproc::BinaryImage& content, const QRect& area, Garbage& garbage, DebugImages* const dbg) { - const SlicedHistogram hist(content_blocks, area, SlicedHistogram::COLS); + const SlicedHistogram hist(content_blocks, area, SlicedHistogram::COLS); - size_t start = 0; - while (start < hist.size()) { - size_t first_ws = start; - for (; first_ws < hist.size() && hist[first_ws] != 0; ++first_ws) { - // Skip non-empty columns. - } - size_t first_non_ws = first_ws; - for (; first_non_ws < hist.size() && hist[first_non_ws] == 0; ++first_non_ws) { - // Skip empty columns. - } + size_t start = 0; + while (start < hist.size()) { + size_t first_ws = start; + for (; first_ws < hist.size() && hist[first_ws] != 0; ++first_ws) { + // Skip non-empty columns. + } + size_t first_non_ws = first_ws; + for (; first_non_ws < hist.size() && hist[first_non_ws] == 0; ++first_non_ws) { + // Skip empty columns. + } - first_ws += area.left(); - first_non_ws += area.left(); + first_ws += area.left(); + first_non_ws += area.left(); - QRect new_area(area); - new_area.setLeft(static_cast(first_non_ws)); - if (new_area.isEmpty()) { - return area; - } + QRect new_area(area); + new_area.setLeft(static_cast(first_non_ws)); + if (new_area.isEmpty()) { + return area; + } - QRect removed_area(area); - removed_area.setRight(static_cast(first_ws - 1)); - if (removed_area.isEmpty()) { - return new_area; - } + QRect removed_area(area); + removed_area.setRight(static_cast(first_ws - 1)); + if (removed_area.isEmpty()) { + return new_area; + } - bool can_retry_grouped = false; - const QRect res - = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); - if (can_retry_grouped) { - start = first_non_ws - area.left(); - } else { - return res; - } + bool can_retry_grouped = false; + const QRect res + = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); + if (can_retry_grouped) { + start = first_non_ws - area.left(); + } else { + return res; } + } - return area; + return area; } // ContentBoxFinder::trimLeft QRect ContentBoxFinder::trimRight(const imageproc::BinaryImage& content, @@ -935,45 +933,45 @@ QRect ContentBoxFinder::trimRight(const imageproc::BinaryImage& content, const QRect& area, Garbage& garbage, DebugImages* const dbg) { - const SlicedHistogram hist(content_blocks, area, SlicedHistogram::COLS); + const SlicedHistogram hist(content_blocks, area, SlicedHistogram::COLS); - auto start = static_cast(hist.size() - 1); - while (start >= 0) { - int first_ws = start; - for (; first_ws >= 0 && hist[first_ws] != 0; --first_ws) { - // Skip non-empty columns. - } - int first_non_ws = first_ws; - for (; first_non_ws >= 0 && hist[first_non_ws] == 0; --first_non_ws) { - // Skip empty columns. - } + auto start = static_cast(hist.size() - 1); + while (start >= 0) { + int first_ws = start; + for (; first_ws >= 0 && hist[first_ws] != 0; --first_ws) { + // Skip non-empty columns. + } + int first_non_ws = first_ws; + for (; first_non_ws >= 0 && hist[first_non_ws] == 0; --first_non_ws) { + // Skip empty columns. + } - first_ws += area.left(); - first_non_ws += area.left(); + first_ws += area.left(); + first_non_ws += area.left(); - QRect new_area(area); - new_area.setRight(first_non_ws); - if (new_area.isEmpty()) { - return area; - } + QRect new_area(area); + new_area.setRight(first_non_ws); + if (new_area.isEmpty()) { + return area; + } - QRect removed_area(area); - removed_area.setLeft(first_ws + 1); - if (removed_area.isEmpty()) { - return new_area; - } + QRect removed_area(area); + removed_area.setLeft(first_ws + 1); + if (removed_area.isEmpty()) { + return new_area; + } - bool can_retry_grouped = false; - const QRect res - = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); - if (can_retry_grouped) { - start = first_non_ws - area.left(); - } else { - return res; - } + bool can_retry_grouped = false; + const QRect res + = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); + if (can_retry_grouped) { + start = first_non_ws - area.left(); + } else { + return res; } + } - return area; + return area; } // ContentBoxFinder::trimRight QRect ContentBoxFinder::trimTop(const imageproc::BinaryImage& content, @@ -982,45 +980,45 @@ QRect ContentBoxFinder::trimTop(const imageproc::BinaryImage& content, const QRect& area, Garbage& garbage, DebugImages* const dbg) { - const SlicedHistogram hist(content_blocks, area, SlicedHistogram::ROWS); + const SlicedHistogram hist(content_blocks, area, SlicedHistogram::ROWS); - size_t start = 0; - while (start < hist.size()) { - size_t first_ws = start; - for (; first_ws < hist.size() && hist[first_ws] != 0; ++first_ws) { - // Skip non-empty columns. - } - size_t first_non_ws = first_ws; - for (; first_non_ws < hist.size() && hist[first_non_ws] == 0; ++first_non_ws) { - // Skip empty columns. - } + size_t start = 0; + while (start < hist.size()) { + size_t first_ws = start; + for (; first_ws < hist.size() && hist[first_ws] != 0; ++first_ws) { + // Skip non-empty columns. + } + size_t first_non_ws = first_ws; + for (; first_non_ws < hist.size() && hist[first_non_ws] == 0; ++first_non_ws) { + // Skip empty columns. + } - first_ws += area.top(); - first_non_ws += area.top(); + first_ws += area.top(); + first_non_ws += area.top(); - QRect new_area(area); - new_area.setTop(static_cast(first_non_ws)); - if (new_area.isEmpty()) { - return area; - } + QRect new_area(area); + new_area.setTop(static_cast(first_non_ws)); + if (new_area.isEmpty()) { + return area; + } - QRect removed_area(area); - removed_area.setBottom(static_cast(first_ws - 1)); - if (removed_area.isEmpty()) { - return new_area; - } + QRect removed_area(area); + removed_area.setBottom(static_cast(first_ws - 1)); + if (removed_area.isEmpty()) { + return new_area; + } - bool can_retry_grouped = false; - const QRect res - = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); - if (can_retry_grouped) { - start = first_non_ws - area.top(); - } else { - return res; - } + bool can_retry_grouped = false; + const QRect res + = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); + if (can_retry_grouped) { + start = first_non_ws - area.top(); + } else { + return res; } + } - return area; + return area; } // ContentBoxFinder::trimTop QRect ContentBoxFinder::trimBottom(const imageproc::BinaryImage& content, @@ -1029,45 +1027,45 @@ QRect ContentBoxFinder::trimBottom(const imageproc::BinaryImage& content, const QRect& area, Garbage& garbage, DebugImages* const dbg) { - const SlicedHistogram hist(content_blocks, area, SlicedHistogram::ROWS); + const SlicedHistogram hist(content_blocks, area, SlicedHistogram::ROWS); - auto start = static_cast(hist.size() - 1); - while (start >= 0) { - int first_ws = start; - for (; first_ws >= 0 && hist[first_ws] != 0; --first_ws) { - // Skip non-empty columns. - } - int first_non_ws = first_ws; - for (; first_non_ws >= 0 && hist[first_non_ws] == 0; --first_non_ws) { - // Skip empty columns. - } + auto start = static_cast(hist.size() - 1); + while (start >= 0) { + int first_ws = start; + for (; first_ws >= 0 && hist[first_ws] != 0; --first_ws) { + // Skip non-empty columns. + } + int first_non_ws = first_ws; + for (; first_non_ws >= 0 && hist[first_non_ws] == 0; --first_non_ws) { + // Skip empty columns. + } - first_ws += area.top(); - first_non_ws += area.top(); + first_ws += area.top(); + first_non_ws += area.top(); - QRect new_area(area); - new_area.setBottom(first_non_ws); - if (new_area.isEmpty()) { - return area; - } + QRect new_area(area); + new_area.setBottom(first_non_ws); + if (new_area.isEmpty()) { + return area; + } - QRect removed_area(area); - removed_area.setTop(first_ws + 1); - if (removed_area.isEmpty()) { - return new_area; - } + QRect removed_area(area); + removed_area.setTop(first_ws + 1); + if (removed_area.isEmpty()) { + return new_area; + } - bool can_retry_grouped = false; - const QRect res - = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); - if (can_retry_grouped) { - start = first_non_ws - area.top(); - } else { - return res; - } + bool can_retry_grouped = false; + const QRect res + = trim(content, content_blocks, text, area, new_area, removed_area, garbage, can_retry_grouped, dbg); + if (can_retry_grouped) { + start = first_non_ws - area.top(); + } else { + return res; } + } - return area; + return area; } // ContentBoxFinder::trimBottom QRect ContentBoxFinder::trim(const imageproc::BinaryImage& content, @@ -1079,281 +1077,280 @@ QRect ContentBoxFinder::trim(const imageproc::BinaryImage& content, Garbage& garbage, bool& can_retry_grouped, DebugImages* const dbg) { - can_retry_grouped = false; - - QImage visualized; + can_retry_grouped = false; + + QImage visualized; + + if (dbg) { + visualized = QImage(content_blocks.size(), QImage::Format_ARGB32_Premultiplied); + QPainter painter(&visualized); + painter.drawImage(QPoint(0, 0), content_blocks.toQImage()); + + QPainterPath outer_path; + outer_path.addRect(visualized.rect()); + QPainterPath inner_path; + inner_path.addRect(area); + + // Fill already rejected area with translucent gray. + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0x00, 0x00, 0x00, 50)); + painter.drawPath(outer_path.subtracted(inner_path)); + } + + // Don't trim too much. + while (removed_area.width() * removed_area.height() > 0.3 * (new_area.width() * new_area.height())) { + // It's a loop just to be able to break from it. + + // There is a special case when there is nothing but + // garbage on the page. Let's try to handle it here. + if ((removed_area.width() < 6) || (removed_area.height() < 6)) { + break; + } if (dbg) { - visualized = QImage(content_blocks.size(), QImage::Format_ARGB32_Premultiplied); - QPainter painter(&visualized); - painter.drawImage(QPoint(0, 0), content_blocks.toQImage()); - - QPainterPath outer_path; - outer_path.addRect(visualized.rect()); - QPainterPath inner_path; - inner_path.addRect(area); - - // Fill already rejected area with translucent gray. - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0x00, 0x00, 0x00, 50)); - painter.drawPath(outer_path.subtracted(inner_path)); + QPainter painter(&visualized); + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); + painter.drawRect(removed_area); + painter.drawRect(new_area); + painter.end(); + dbg->add(visualized, "trim_too_much"); } - // Don't trim too much. - while (removed_area.width() * removed_area.height() > 0.3 * (new_area.width() * new_area.height())) { - // It's a loop just to be able to break from it. - - // There is a special case when there is nothing but - // garbage on the page. Let's try to handle it here. - if ((removed_area.width() < 6) || (removed_area.height() < 6)) { - break; - } - - if (dbg) { - QPainter painter(&visualized); - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); - painter.drawRect(removed_area); - painter.drawRect(new_area); - painter.end(); - dbg->add(visualized, "trim_too_much"); - } - - return area; + return area; + } + + const int content_pixels = content.countBlackPixels(removed_area); + + const bool vertical_cut = (new_area.top() == area.top() && new_area.bottom() == area.bottom()); + // qDebug() << "vertical cut: " << vertical_cut; + // Ranged from 0.0 to 1.0. When it's less than 0.5, objects + // are more likely to be considered as garbage. When it's + // more than 0.5, objects are less likely to be considered + // as garbage. + double proximity_bias = vertical_cut ? 0.5 : 0.65; + + const int num_text_pixels = text.countBlackPixels(removed_area); + if (num_text_pixels == 0) { + proximity_bias = vertical_cut ? 0.4 : 0.5; + } else { + int total_pixels = content_pixels; + total_pixels += garbage.image().countBlackPixels(removed_area); + + // qDebug() << "num_text_pixels = " << num_text_pixels; + // qDebug() << "total_pixels = " << total_pixels; + ++total_pixels; // just in case + const double min_text_influence = 0.2; + const double max_text_influence = 1.0; + const int upper_threshold = 5000; + double text_influence = max_text_influence; + if (num_text_pixels < upper_threshold) { + text_influence = min_text_influence + + (max_text_influence - min_text_influence) * std::log((double) num_text_pixels) + / std::log((double) upper_threshold); } + // qDebug() << "text_influence = " << text_influence; + + proximity_bias += (1.0 - proximity_bias) * text_influence * num_text_pixels / total_pixels; + proximity_bias = qBound(0.0, proximity_bias, 1.0); + } + + BinaryImage remaining_content(content_blocks.size(), WHITE); + rasterOp(remaining_content, new_area, content, new_area.topLeft()); + rasterOp>(remaining_content, new_area, content_blocks, new_area.topLeft()); + + const SEDM dm_to_others(remaining_content, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS); + remaining_content.release(); + + double sum_dist_to_garbage = 0; + double sum_dist_to_others = 0; + + const uint32_t* cb_line = content_blocks.data(); + const int cb_stride = content_blocks.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + const uint32_t* dm_garbage_line = garbage.sedm().data(); + const uint32_t* dm_others_line = dm_to_others.data(); + const int dm_stride = dm_to_others.stride(); + + int count = 0; + cb_line += cb_stride * removed_area.top(); + dm_garbage_line += dm_stride * removed_area.top(); + dm_others_line += dm_stride * removed_area.top(); + for (int y = removed_area.top(); y <= removed_area.bottom(); ++y) { + for (int x = removed_area.left(); x <= removed_area.right(); ++x) { + if (cb_line[x >> 5] & (msb >> (x & 31))) { + sum_dist_to_garbage += std::sqrt((double) dm_garbage_line[x]); + sum_dist_to_others += std::sqrt((double) dm_others_line[x]); + ++count; + } + } + cb_line += cb_stride; + dm_garbage_line += dm_stride; + dm_others_line += dm_stride; + } - const int content_pixels = content.countBlackPixels(removed_area); - - const bool vertical_cut = (new_area.top() == area.top() && new_area.bottom() == area.bottom()); - // qDebug() << "vertical cut: " << vertical_cut; - // Ranged from 0.0 to 1.0. When it's less than 0.5, objects - // are more likely to be considered as garbage. When it's - // more than 0.5, objects are less likely to be considered - // as garbage. - double proximity_bias = vertical_cut ? 0.5 : 0.65; + // qDebug() << "proximity_bias = " << proximity_bias; + // qDebug() << "sum_dist_to_garbage = " << sum_dist_to_garbage; + // qDebug() << "sum_dist_to_others = " << sum_dist_to_others; + // qDebug() << "count = " << count; - const int num_text_pixels = text.countBlackPixels(removed_area); - if (num_text_pixels == 0) { - proximity_bias = vertical_cut ? 0.4 : 0.5; - } else { - int total_pixels = content_pixels; - total_pixels += garbage.image().countBlackPixels(removed_area); - - // qDebug() << "num_text_pixels = " << num_text_pixels; - // qDebug() << "total_pixels = " << total_pixels; - ++total_pixels; // just in case - const double min_text_influence = 0.2; - const double max_text_influence = 1.0; - const int upper_threshold = 5000; - double text_influence = max_text_influence; - if (num_text_pixels < upper_threshold) { - text_influence = min_text_influence - + (max_text_influence - min_text_influence) * std::log((double) num_text_pixels) - / std::log((double) upper_threshold); - } - // qDebug() << "text_influence = " << text_influence; + sum_dist_to_garbage *= proximity_bias; + sum_dist_to_others *= 1.0 - proximity_bias; - proximity_bias += (1.0 - proximity_bias) * text_influence * num_text_pixels / total_pixels; - proximity_bias = qBound(0.0, proximity_bias, 1.0); + if (sum_dist_to_garbage < sum_dist_to_others) { + garbage.add(content, removed_area); + if (dbg) { + QPainter painter(&visualized); + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); + painter.drawRect(new_area); + painter.setBrush(QColor(0xff, 0x20, 0x1e, 50)); + painter.drawRect(removed_area); + painter.end(); + dbg->add(visualized, "trimmed"); } - BinaryImage remaining_content(content_blocks.size(), WHITE); - rasterOp(remaining_content, new_area, content, new_area.topLeft()); - rasterOp>(remaining_content, new_area, content_blocks, new_area.topLeft()); - - const SEDM dm_to_others(remaining_content, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS); - remaining_content.release(); - - double sum_dist_to_garbage = 0; - double sum_dist_to_others = 0; - - const uint32_t* cb_line = content_blocks.data(); - const int cb_stride = content_blocks.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - const uint32_t* dm_garbage_line = garbage.sedm().data(); - const uint32_t* dm_others_line = dm_to_others.data(); - const int dm_stride = dm_to_others.stride(); - - int count = 0; - cb_line += cb_stride * removed_area.top(); - dm_garbage_line += dm_stride * removed_area.top(); - dm_others_line += dm_stride * removed_area.top(); - for (int y = removed_area.top(); y <= removed_area.bottom(); ++y) { - for (int x = removed_area.left(); x <= removed_area.right(); ++x) { - if (cb_line[x >> 5] & (msb >> (x & 31))) { - sum_dist_to_garbage += std::sqrt((double) dm_garbage_line[x]); - sum_dist_to_others += std::sqrt((double) dm_others_line[x]); - ++count; - } - } - cb_line += cb_stride; - dm_garbage_line += dm_stride; - dm_others_line += dm_stride; + return new_area; + } else { + if (dbg) { + QPainter painter(&visualized); + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); + painter.drawRect(removed_area); + painter.drawRect(new_area); + painter.end(); + dbg->add(visualized, "not_trimmed"); } + can_retry_grouped = (proximity_bias < 0.85); - // qDebug() << "proximity_bias = " << proximity_bias; - // qDebug() << "sum_dist_to_garbage = " << sum_dist_to_garbage; - // qDebug() << "sum_dist_to_others = " << sum_dist_to_others; - // qDebug() << "count = " << count; - - sum_dist_to_garbage *= proximity_bias; - sum_dist_to_others *= 1.0 - proximity_bias; - - if (sum_dist_to_garbage < sum_dist_to_others) { - garbage.add(content, removed_area); - if (dbg) { - QPainter painter(&visualized); - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); - painter.drawRect(new_area); - painter.setBrush(QColor(0xff, 0x20, 0x1e, 50)); - painter.drawRect(removed_area); - painter.end(); - dbg->add(visualized, "trimmed"); - } - - return new_area; - } else { - if (dbg) { - QPainter painter(&visualized); - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(0x5f, 0xdf, 0x57, 50)); - painter.drawRect(removed_area); - painter.drawRect(new_area); - painter.end(); - dbg->add(visualized, "not_trimmed"); - } - can_retry_grouped = (proximity_bias < 0.85); - - return area; - } + return area; + } } // ContentBoxFinder::trim void ContentBoxFinder::filterShadows(const TaskStatus& status, imageproc::BinaryImage& shadows, DebugImages* const dbg) { - // The input image should only contain shadows from the edges - // of a page, but in practice it may also contain things like - // a black table header which white letters on it. Here we - // try to filter them out. + // The input image should only contain shadows from the edges + // of a page, but in practice it may also contain things like + // a black table header which white letters on it. Here we + // try to filter them out. #if 1 - // Shadows that touch borders are genuine and should not be removed. - BinaryImage borders(shadows.size(), WHITE); - borders.fillExcept(borders.rect().adjusted(1, 1, -1, -1), BLACK); - - BinaryImage touching_shadows(seedFill(borders, shadows, CONN8)); - rasterOp>(shadows, touching_shadows); + // Shadows that touch borders are genuine and should not be removed. + BinaryImage borders(shadows.size(), WHITE); + borders.fillExcept(borders.rect().adjusted(1, 1, -1, -1), BLACK); + + BinaryImage touching_shadows(seedFill(borders, shadows, CONN8)); + rasterOp>(shadows, touching_shadows); + if (dbg) { + dbg->add(shadows, "non_border_shadows"); + } + + if (shadows.countBlackPixels()) { + BinaryImage inv_shadows(shadows.inverted()); + BinaryImage mask(seedFill(borders, inv_shadows, CONN8)); + borders.release(); + rasterOp, RopSrc>>(mask, shadows); if (dbg) { - dbg->add(shadows, "non_border_shadows"); + dbg->add(mask, "shadows_no_holes"); } - if (shadows.countBlackPixels()) { - BinaryImage inv_shadows(shadows.inverted()); - BinaryImage mask(seedFill(borders, inv_shadows, CONN8)); - borders.release(); - rasterOp, RopSrc>>(mask, shadows); - if (dbg) { - dbg->add(mask, "shadows_no_holes"); - } - - BinaryImage text_mask(estimateTextMask(inv_shadows, mask, dbg)); - inv_shadows.release(); - mask.release(); - text_mask = seedFill(text_mask, shadows, CONN8); - if (dbg) { - dbg->add(text_mask, "misclassified_shadows"); - } - rasterOp>(shadows, text_mask); + BinaryImage text_mask(estimateTextMask(inv_shadows, mask, dbg)); + inv_shadows.release(); + mask.release(); + text_mask = seedFill(text_mask, shadows, CONN8); + if (dbg) { + dbg->add(text_mask, "misclassified_shadows"); } + rasterOp>(shadows, text_mask); + } - rasterOp>(shadows, touching_shadows); + rasterOp>(shadows, touching_shadows); #else // if 1 // White dots on black background may be a problem for us. // They may be misclassified as parts of white letters. - BinaryImage reduced_dithering(closeBrick(shadows, QSize(1, 2), BLACK)); - reduced_dithering = closeBrick(reduced_dithering, QSize(2, 1), BLACK); - if (dbg) { - dbg->add(reduced_dithering, "reduced_dithering"); - } - - status.throwIfCancelled(); - - // Long white vertical lines are definately not spaces between letters. - BinaryImage vert_whitespace(closeBrick(reduced_dithering, QSize(1, 150), BLACK)); - if (dbg) { - dbg->add(vert_whitespace, "vert_whitespace"); - } - - status.throwIfCancelled(); - - // Join neighboring white letters. - BinaryImage opened(openBrick(reduced_dithering, QSize(10, 4), BLACK)); - reduced_dithering.release(); - if (dbg) { - dbg->add(opened, "opened"); - } - - status.throwIfCancelled(); - - // Extract areas that became white as a result of the last operation. - rasterOp, RopNot>>(opened, shadows); - if (dbg) { - dbg->add(opened, "became white"); - } - - status.throwIfCancelled(); - // Join the spacings between words together. - BinaryImage closed(closeBrick(opened, QSize(20, 1), WHITE)); - opened.release(); - rasterOp>(closed, vert_whitespace); - vert_whitespace.release(); - if (dbg) { - dbg->add(closed, "closed"); - } - - status.throwIfCancelled(); - // If we've got long enough and tall enough blocks, we assume they - // are the text lines. - opened = openBrick(closed, QSize(50, 10), WHITE); - closed.release(); - if (dbg) { - dbg->add(opened, "reopened"); - } - - status.throwIfCancelled(); - - BinaryImage non_shadows(seedFill(opened, shadows, CONN8)); - opened.release(); - if (dbg) { - dbg->add(non_shadows, "non_shadows"); - } - - status.throwIfCancelled(); - - rasterOp>(shadows, non_shadows); + BinaryImage reduced_dithering(closeBrick(shadows, QSize(1, 2), BLACK)); + reduced_dithering = closeBrick(reduced_dithering, QSize(2, 1), BLACK); + if (dbg) { + dbg->add(reduced_dithering, "reduced_dithering"); + } + + status.throwIfCancelled(); + + // Long white vertical lines are definately not spaces between letters. + BinaryImage vert_whitespace(closeBrick(reduced_dithering, QSize(1, 150), BLACK)); + if (dbg) { + dbg->add(vert_whitespace, "vert_whitespace"); + } + + status.throwIfCancelled(); + + // Join neighboring white letters. + BinaryImage opened(openBrick(reduced_dithering, QSize(10, 4), BLACK)); + reduced_dithering.release(); + if (dbg) { + dbg->add(opened, "opened"); + } + + status.throwIfCancelled(); + + // Extract areas that became white as a result of the last operation. + rasterOp, RopNot>>(opened, shadows); + if (dbg) { + dbg->add(opened, "became white"); + } + + status.throwIfCancelled(); + // Join the spacings between words together. + BinaryImage closed(closeBrick(opened, QSize(20, 1), WHITE)); + opened.release(); + rasterOp>(closed, vert_whitespace); + vert_whitespace.release(); + if (dbg) { + dbg->add(closed, "closed"); + } + + status.throwIfCancelled(); + // If we've got long enough and tall enough blocks, we assume they + // are the text lines. + opened = openBrick(closed, QSize(50, 10), WHITE); + closed.release(); + if (dbg) { + dbg->add(opened, "reopened"); + } + + status.throwIfCancelled(); + + BinaryImage non_shadows(seedFill(opened, shadows, CONN8)); + opened.release(); + if (dbg) { + dbg->add(non_shadows, "non_shadows"); + } + + status.throwIfCancelled(); + + rasterOp>(shadows, non_shadows); #endif // if 1 } // ContentBoxFinder::filterShadows /*====================== ContentBoxFinder::Garbage =====================*/ ContentBoxFinder::Garbage::Garbage(const Type type, const BinaryImage& garbage) - : m_garbage(garbage), - m_sedmBorders(type == VERT ? SEDM::DIST_TO_VERT_BORDERS : SEDM::DIST_TO_HOR_BORDERS), - m_sedmUpdatePending(true) { -} + : m_garbage(garbage), + m_sedmBorders(type == VERT ? SEDM::DIST_TO_VERT_BORDERS : SEDM::DIST_TO_HOR_BORDERS), + m_sedmUpdatePending(true) {} void ContentBoxFinder::Garbage::add(const BinaryImage& garbage, const QRect& rect) { - rasterOp>(m_garbage, rect, garbage, rect.topLeft()); - m_sedmUpdatePending = true; + rasterOp>(m_garbage, rect, garbage, rect.topLeft()); + m_sedmUpdatePending = true; } const SEDM& ContentBoxFinder::Garbage::sedm() { - if (m_sedmUpdatePending) { - m_sedm = SEDM(m_garbage, SEDM::DIST_TO_BLACK, m_sedmBorders); - } + if (m_sedmUpdatePending) { + m_sedm = SEDM(m_garbage, SEDM::DIST_TO_BLACK, m_sedmBorders); + } - return m_sedm; + return m_sedm; } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/ContentBoxFinder.h b/filters/select_content/ContentBoxFinder.h index 595c91de6..98b802030 100644 --- a/filters/select_content/ContentBoxFinder.h +++ b/filters/select_content/ContentBoxFinder.h @@ -37,67 +37,67 @@ class SEDM; namespace select_content { class ContentBoxFinder { -public: - static QRectF findContentBox(const TaskStatus& status, - const FilterData& data, - const QRectF& page_rect, - DebugImages* dbg = nullptr); + public: + static QRectF findContentBox(const TaskStatus& status, + const FilterData& data, + const QRectF& page_rect, + DebugImages* dbg = nullptr); -private: - class Garbage; + private: + class Garbage; - static void segmentGarbage(const imageproc::BinaryImage& garbage, - imageproc::BinaryImage& hor_garbage, - imageproc::BinaryImage& vert_garbage, - DebugImages* dbg); + static void segmentGarbage(const imageproc::BinaryImage& garbage, + imageproc::BinaryImage& hor_garbage, + imageproc::BinaryImage& vert_garbage, + DebugImages* dbg); - static void trimContentBlocksInPlace(const imageproc::BinaryImage& content, imageproc::BinaryImage& content_blocks); + static void trimContentBlocksInPlace(const imageproc::BinaryImage& content, imageproc::BinaryImage& content_blocks); - static void inPlaceRemoveAreasTouchingBorders(imageproc::BinaryImage& content_blocks, DebugImages* dbg); + static void inPlaceRemoveAreasTouchingBorders(imageproc::BinaryImage& content_blocks, DebugImages* dbg); - static imageproc::BinaryImage estimateTextMask(const imageproc::BinaryImage& content, - const imageproc::BinaryImage& content_blocks, - DebugImages* dbg); + static imageproc::BinaryImage estimateTextMask(const imageproc::BinaryImage& content, + const imageproc::BinaryImage& content_blocks, + DebugImages* dbg); - static void filterShadows(const TaskStatus& status, imageproc::BinaryImage& shadows, DebugImages* dbg); + static void filterShadows(const TaskStatus& status, imageproc::BinaryImage& shadows, DebugImages* dbg); - static QRect trimLeft(const imageproc::BinaryImage& content, - const imageproc::BinaryImage& content_blocks, - const imageproc::BinaryImage& text_mask, - const QRect& area, - Garbage& garbage, - DebugImages* dbg); - - static QRect trimRight(const imageproc::BinaryImage& content, - const imageproc::BinaryImage& content_blocks, - const imageproc::BinaryImage& text_mask, - const QRect& area, - Garbage& garbage, - DebugImages* dbg); + static QRect trimLeft(const imageproc::BinaryImage& content, + const imageproc::BinaryImage& content_blocks, + const imageproc::BinaryImage& text_mask, + const QRect& area, + Garbage& garbage, + DebugImages* dbg); - static QRect trimTop(const imageproc::BinaryImage& content, + static QRect trimRight(const imageproc::BinaryImage& content, const imageproc::BinaryImage& content_blocks, const imageproc::BinaryImage& text_mask, const QRect& area, Garbage& garbage, DebugImages* dbg); - static QRect trimBottom(const imageproc::BinaryImage& content, - const imageproc::BinaryImage& content_blocks, - const imageproc::BinaryImage& text_mask, - const QRect& area, - Garbage& garbage, - DebugImages* dbg); - - static QRect trim(const imageproc::BinaryImage& content, - const imageproc::BinaryImage& content_blocks, - const imageproc::BinaryImage& text_mask, - const QRect& area, - const QRect& new_area, - const QRect& removed_area, - Garbage& garbage, - bool& can_retry_grouped, - DebugImages* dbg); + static QRect trimTop(const imageproc::BinaryImage& content, + const imageproc::BinaryImage& content_blocks, + const imageproc::BinaryImage& text_mask, + const QRect& area, + Garbage& garbage, + DebugImages* dbg); + + static QRect trimBottom(const imageproc::BinaryImage& content, + const imageproc::BinaryImage& content_blocks, + const imageproc::BinaryImage& text_mask, + const QRect& area, + Garbage& garbage, + DebugImages* dbg); + + static QRect trim(const imageproc::BinaryImage& content, + const imageproc::BinaryImage& content_blocks, + const imageproc::BinaryImage& text_mask, + const QRect& area, + const QRect& new_area, + const QRect& removed_area, + Garbage& garbage, + bool& can_retry_grouped, + DebugImages* dbg); }; } // namespace select_content #endif // ifndef SELECT_CONTENT_CONTENTBOXFINDER_H_ diff --git a/filters/select_content/Dependencies.cpp b/filters/select_content/Dependencies.cpp index 6e5873a15..822f17b2a 100644 --- a/filters/select_content/Dependencies.cpp +++ b/filters/select_content/Dependencies.cpp @@ -24,43 +24,132 @@ using namespace imageproc; namespace select_content { -Dependencies::Dependencies() : m_invalid(false) { +Dependencies::Dependencies(const QPolygonF& rotated_page_outline) : m_rotatedPageOutline(rotated_page_outline) {} + +Dependencies::Dependencies(const QPolygonF& rotated_page_outline, + const AutoManualMode content_detection_mode, + const AutoManualMode page_detection_mode, + const bool fine_tune_corners) + : m_rotatedPageOutline(rotated_page_outline), + m_params(content_detection_mode, page_detection_mode, fine_tune_corners) {} + +Dependencies::Dependencies(const QDomElement& deps_el) + : m_rotatedPageOutline(XmlUnmarshaller::polygonF(deps_el.namedItem("rotated-page-outline").toElement())), + m_params(deps_el.namedItem("params").toElement()) {} + +bool Dependencies::compatibleWith(const Dependencies& other) const { + if (!m_params.compatibleWith(other.m_params)) { + return false; + } + + return PolygonUtils::fuzzyCompare(m_rotatedPageOutline, other.m_rotatedPageOutline); } -Dependencies::Dependencies(const QPolygonF& rotated_page_outline) - : m_rotatedPageOutline(rotated_page_outline), m_invalid(false) { +bool Dependencies::compatibleWith(const Dependencies& other, bool* update_content_box, bool* update_page_box) const { + bool is_compatible; + bool need_update_content_box; + bool need_update_page_box; + if (!PolygonUtils::fuzzyCompare(m_rotatedPageOutline, other.m_rotatedPageOutline)) { + is_compatible = false; + need_update_content_box = true; + need_update_page_box = true; + } else { + need_update_content_box = m_params.needUpdateContentBox(other.m_params); + need_update_page_box = m_params.needUpdatePageBox(other.m_params); + is_compatible = !(need_update_content_box || need_update_page_box); + } + + if (update_content_box) { + *update_content_box = need_update_content_box; + } + if (update_page_box) { + *update_page_box = need_update_page_box; + } + + return is_compatible; } -Dependencies::Dependencies(const QDomElement& deps_el) - : m_rotatedPageOutline(XmlUnmarshaller::polygonF(deps_el.namedItem("rotated-page-outline").toElement())), - m_invalid(deps_el.attribute("invalid") == "1") { +QDomElement Dependencies::toXml(QDomDocument& doc, const QString& name) const { + XmlMarshaller marshaller(doc); + + QDomElement el(doc.createElement(name)); + el.appendChild(marshaller.polygonF(m_rotatedPageOutline, "rotated-page-outline")); + el.appendChild(m_params.toXml(doc, "params")); + + return el; } -Dependencies::~Dependencies() = default; +const QPolygonF& Dependencies::rotatedPageOutline() const { + return m_rotatedPageOutline; +} -bool Dependencies::matches(const Dependencies& other) const { - if (m_invalid) { - return false; - } +void Dependencies::setContentDetectionMode(AutoManualMode content_detection_mode) { + m_params.setContentDetectionMode(content_detection_mode); +} - return PolygonUtils::fuzzyCompare(m_rotatedPageOutline, other.m_rotatedPageOutline); +void Dependencies::setPageDetectionMode(AutoManualMode page_detection_mode) { + m_params.setPageDetectionMode(page_detection_mode); } -QDomElement Dependencies::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); +/* ================================= Dependencies::Params ================================= */ + +Dependencies::Params::Params() + : m_contentDetectionMode(MODE_AUTO), m_pageDetectionMode(MODE_DISABLED), m_fineTuneCorners(false) {} + +Dependencies::Params::Params(const AutoManualMode content_detection_mode, + const AutoManualMode page_detection_mode, + const bool fine_tune_corners) + : m_contentDetectionMode(content_detection_mode), + m_pageDetectionMode(page_detection_mode), + m_fineTuneCorners(fine_tune_corners) {} - QDomElement el(doc.createElement(name)); - el.appendChild(marshaller.polygonF(m_rotatedPageOutline, "rotated-page-outline")); - el.setAttribute("invalid", m_invalid ? "1" : "0"); +Dependencies::Params::Params(const QDomElement& el) + : m_contentDetectionMode(stringToAutoManualMode(el.attribute("contentDetectionMode"))), + m_pageDetectionMode(stringToAutoManualMode(el.attribute("pageDetectionMode"))), + m_fineTuneCorners(el.attribute("fineTuneCorners") == "1") {} - return el; +QDomElement Dependencies::Params::toXml(QDomDocument& doc, const QString& name) const { + QDomElement el(doc.createElement(name)); + el.setAttribute("contentDetectionMode", autoManualModeToString(m_contentDetectionMode)); + el.setAttribute("pageDetectionMode", autoManualModeToString(m_pageDetectionMode)); + el.setAttribute("fineTuneCorners", m_fineTuneCorners ? "1" : "0"); + + return el; } -void Dependencies::invalidate() { - m_invalid = true; +bool Dependencies::Params::compatibleWith(const Dependencies::Params& other) const { + if ((m_contentDetectionMode != MODE_MANUAL) && (m_contentDetectionMode != other.m_contentDetectionMode)) { + return false; + } + if ((m_pageDetectionMode != MODE_MANUAL) && (m_pageDetectionMode != other.m_pageDetectionMode)) { + return false; + } + if ((m_pageDetectionMode == MODE_AUTO) && (m_fineTuneCorners != other.m_fineTuneCorners)) { + return false; + } + + return true; } -const QPolygonF& Dependencies::rotatedPageOutline() const { - return m_rotatedPageOutline; +bool Dependencies::Params::needUpdateContentBox(const Dependencies::Params& other) const { + return (m_contentDetectionMode != MODE_MANUAL) && (m_contentDetectionMode != other.m_contentDetectionMode); +} + +bool Dependencies::Params::needUpdatePageBox(const Dependencies::Params& other) const { + if ((m_pageDetectionMode != MODE_MANUAL) && (m_pageDetectionMode != other.m_pageDetectionMode)) { + return true; + } + if ((m_pageDetectionMode == MODE_AUTO) && (m_fineTuneCorners != other.m_fineTuneCorners)) { + return true; + } + return false; +} + +void Dependencies::Params::setContentDetectionMode(AutoManualMode content_detection_mode) { + m_contentDetectionMode = content_detection_mode; +} + +void Dependencies::Params::setPageDetectionMode(AutoManualMode page_detection_mode) { + m_pageDetectionMode = page_detection_mode; } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/Dependencies.h b/filters/select_content/Dependencies.h index 37dcb83e9..659fa388d 100644 --- a/filters/select_content/Dependencies.h +++ b/filters/select_content/Dependencies.h @@ -19,6 +19,7 @@ #ifndef SELECT_CONTENT_DEPENDENCIES_H_ #define SELECT_CONTENT_DEPENDENCIES_H_ +#include #include class QDomDocument; @@ -32,28 +33,62 @@ namespace select_content { * Once dependencies change, the content box is no longer valid. */ class Dependencies { -public: - // Member-wise copying is OK. + // Member-wise copying is OK. + public: + Dependencies() = default; - Dependencies(); + explicit Dependencies(const QPolygonF& rotated_page_outline); - explicit Dependencies(const QPolygonF& rotated_page_outline); + explicit Dependencies(const QPolygonF& rotated_page_outline, + AutoManualMode content_detection_mode, + AutoManualMode page_detection_mode, + bool fine_tune_corners); - explicit Dependencies(const QDomElement& deps_el); + explicit Dependencies(const QDomElement& deps_el); - ~Dependencies(); + ~Dependencies() = default; - const QPolygonF& rotatedPageOutline() const; + const QPolygonF& rotatedPageOutline() const; - bool matches(const Dependencies& other) const; + bool compatibleWith(const Dependencies& other) const; + + bool compatibleWith(const Dependencies& other, bool* update_content_box, bool* update_page_box) const; + + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + void setContentDetectionMode(AutoManualMode content_detection_mode); + + void setPageDetectionMode(AutoManualMode page_detection_mode); + + private: + class Params { + public: + Params(); + + Params(AutoManualMode content_detection_mode, AutoManualMode page_detection_mode, bool fine_tune_corners); + + explicit Params(const QDomElement& el); QDomElement toXml(QDomDocument& doc, const QString& name) const; - void invalidate(); + bool compatibleWith(const Params& other) const; + + bool needUpdateContentBox(const Params& other) const; + + bool needUpdatePageBox(const Params& other) const; + + void setContentDetectionMode(AutoManualMode content_detection_mode); + + void setPageDetectionMode(AutoManualMode page_detection_mode); + + private: + AutoManualMode m_contentDetectionMode; + AutoManualMode m_pageDetectionMode; + bool m_fineTuneCorners; + }; -private: - QPolygonF m_rotatedPageOutline; - bool m_invalid; + QPolygonF m_rotatedPageOutline; + Params m_params; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_DEPENDENCIES_H_ diff --git a/filters/select_content/Filter.cpp b/filters/select_content/Filter.cpp index 83461d6c2..11325cfff 100644 --- a/filters/select_content/Filter.cpp +++ b/filters/select_content/Filter.cpp @@ -17,180 +17,175 @@ */ #include "Filter.h" -#include "FilterUiInterface.h" -#include "OptionsWidget.h" -#include "Task.h" -#include "ProjectReader.h" -#include "ProjectWriter.h" -#include "CacheDrivenTask.h" -#include "OrderByWidthProvider.h" -#include "OrderByHeightProvider.h" -#include -#include -#include #include #include -#include "CommandLine.h" -#include -#include -#include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include "CacheDrivenTask.h" +#include "CommandLine.h" +#include "FilterUiInterface.h" +#include "OptionsWidget.h" +#include "OrderByHeightProvider.h" +#include "OrderByWidthProvider.h" +#include "ProjectReader.h" +#include "ProjectWriter.h" +#include "Task.h" namespace select_content { Filter::Filter(const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(new Settings), m_selectedPageOrder(0) { - if (CommandLine::get().isGui()) { - m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); - } - - typedef PageOrderOption::ProviderPtr ProviderPtr; - - const ProviderPtr default_order; - const auto order_by_width = make_intrusive(m_ptrSettings); - const auto order_by_height = make_intrusive(m_ptrSettings); - const auto order_by_deviation = make_intrusive(m_ptrSettings->deviationProvider()); - m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); - m_pageOrderOptions.emplace_back(tr("Order by increasing width"), order_by_width); - m_pageOrderOptions.emplace_back(tr("Order by increasing height"), order_by_height); - m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); + : m_settings(new Settings), m_selectedPageOrder(0) { + if (CommandLine::get().isGui()) { + m_optionsWidget.reset(new OptionsWidget(m_settings, page_selection_accessor)); + } + + const PageOrderOption::ProviderPtr default_order; + const auto order_by_width = make_intrusive(m_settings); + const auto order_by_height = make_intrusive(m_settings); + const auto order_by_deviation = make_intrusive(m_settings->deviationProvider()); + m_pageOrderOptions.emplace_back(tr("Natural order"), default_order); + m_pageOrderOptions.emplace_back(tr("Order by increasing width"), order_by_width); + m_pageOrderOptions.emplace_back(tr("Order by increasing height"), order_by_height); + m_pageOrderOptions.emplace_back(tr("Order by decreasing deviation"), order_by_deviation); } Filter::~Filter() = default; QString Filter::getName() const { - return tr("Select Content"); + return tr("Select Content"); } PageView Filter::getView() const { - return PAGE_VIEW; + return PAGE_VIEW; } int Filter::selectedPageOrder() const { - return m_selectedPageOrder; + return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { - assert((unsigned) option < m_pageOrderOptions.size()); - m_selectedPageOrder = option; + assert((unsigned) option < m_pageOrderOptions.size()); + m_selectedPageOrder = option; } std::vector Filter::pageOrderOptions() const { - return m_pageOrderOptions; + return m_pageOrderOptions; } void Filter::performRelinking(const AbstractRelinker& relinker) { - m_ptrSettings->performRelinking(relinker); + m_settings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) { - m_ptrOptionsWidget->preUpdateUI(page_info); - ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); + m_optionsWidget->preUpdateUI(page_info); + ui->setOptionsWidget(m_optionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(const ProjectWriter& writer, QDomDocument& doc) const { - QDomElement filter_el(doc.createElement("select-content")); + QDomElement filter_el(doc.createElement("select-content")); - filter_el.appendChild(XmlMarshaller(doc).sizeF(m_ptrSettings->pageDetectionBox(), "page-detection-box")); - filter_el.setAttribute("pageDetectionTolerance", Utils::doubleToString(m_ptrSettings->pageDetectionTolerance())); + filter_el.appendChild(XmlMarshaller(doc).sizeF(m_settings->pageDetectionBox(), "page-detection-box")); + filter_el.setAttribute("pageDetectionTolerance", Utils::doubleToString(m_settings->pageDetectionTolerance())); - writer.enumPages([&](const PageId& page_id, int numeric_id) { - this->writePageSettings(doc, filter_el, page_id, numeric_id); - }); + writer.enumPages( + [&](const PageId& page_id, int numeric_id) { this->writePageSettings(doc, filter_el, page_id, numeric_id); }); - return filter_el; + return filter_el; } void Filter::writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const { - const std::unique_ptr params(m_ptrSettings->getPageParams(page_id)); - if (!params) { - return; - } + const std::unique_ptr params(m_settings->getPageParams(page_id)); + if (!params) { + return; + } - QDomElement page_el(doc.createElement("page")); - page_el.setAttribute("id", numeric_id); - page_el.appendChild(params->toXml(doc, "params")); + QDomElement page_el(doc.createElement("page")); + page_el.setAttribute("id", numeric_id); + page_el.appendChild(params->toXml(doc, "params")); - filter_el.appendChild(page_el); + filter_el.appendChild(page_el); } void Filter::loadSettings(const ProjectReader& reader, const QDomElement& filters_el) { - m_ptrSettings->clear(); - - const QDomElement filter_el(filters_el.namedItem("select-content").toElement()); - - m_ptrSettings->setPageDetectionBox(XmlUnmarshaller::sizeF(filter_el.namedItem("page-detection-box").toElement())); - m_ptrSettings->setPageDetectionTolerance(filter_el.attribute("pageDetectionTolerance", "0.1").toDouble()); - - const QString page_tag_name("page"); - QDomNode node(filter_el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != page_tag_name) { - continue; - } - const QDomElement el(node.toElement()); - - bool ok = true; - const int id = el.attribute("id").toInt(&ok); - if (!ok) { - continue; - } - - const PageId page_id(reader.pageId(id)); - if (page_id.isNull()) { - continue; - } - - const QDomElement params_el(el.namedItem("params").toElement()); - if (params_el.isNull()) { - continue; - } - - const Params params(params_el); - m_ptrSettings->setPageParams(page_id, params); + m_settings->clear(); + + const QDomElement filter_el(filters_el.namedItem("select-content").toElement()); + + m_settings->setPageDetectionBox(XmlUnmarshaller::sizeF(filter_el.namedItem("page-detection-box").toElement())); + m_settings->setPageDetectionTolerance(filter_el.attribute("pageDetectionTolerance", "0.1").toDouble()); + + const QString page_tag_name("page"); + QDomNode node(filter_el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } + if (node.nodeName() != page_tag_name) { + continue; + } + const QDomElement el(node.toElement()); + + bool ok = true; + const int id = el.attribute("id").toInt(&ok); + if (!ok) { + continue; + } + + const PageId page_id(reader.pageId(id)); + if (page_id.isNull()) { + continue; + } + + const QDomElement params_el(el.namedItem("params").toElement()); + if (params_el.isNull()) { + continue; + } + + const Params params(params_el); + m_settings->setPageParams(page_id, params); + } } // Filter::loadSettings intrusive_ptr Filter::createTask(const PageId& page_id, intrusive_ptr next_task, bool batch, bool debug) { - return make_intrusive(intrusive_ptr(this), std::move(next_task), m_ptrSettings, page_id, batch, - debug); + return make_intrusive(intrusive_ptr(this), std::move(next_task), m_settings, page_id, batch, debug); } intrusive_ptr Filter::createCacheDrivenTask(intrusive_ptr next_task) { - return make_intrusive(m_ptrSettings, std::move(next_task)); + return make_intrusive(m_settings, std::move(next_task)); } void Filter::loadDefaultSettings(const PageInfo& page_info) { - if (!m_ptrSettings->isParamsNull(page_info.id())) { - return; - } - const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); - const DefaultParams::SelectContentParams& selectContentParams = defaultParams.getSelectContentParams(); - - const UnitsConverter unitsConverter(page_info.metadata().dpi()); - - const QSizeF& pageRectSize = selectContentParams.getPageRectSize(); - double pageRectWidth = pageRectSize.width(); - double pageRectHeight = pageRectSize.height(); - unitsConverter.convert(pageRectWidth, pageRectHeight, defaultParams.getUnits(), PIXELS); - - m_ptrSettings->setPageParams( - page_info.id(), - Params(QRectF(), QSizeF(), QRectF(QPointF(0, 0), QSizeF(pageRectWidth, pageRectHeight)), Dependencies(), - MODE_AUTO, selectContentParams.getPageDetectMode(), selectContentParams.isContentDetectEnabled(), - selectContentParams.isPageDetectEnabled(), selectContentParams.isFineTuneCorners())); + if (!m_settings->isParamsNull(page_info.id())) { + return; + } + const DefaultParams defaultParams = DefaultParamsProvider::getInstance()->getParams(); + const DefaultParams::SelectContentParams& selectContentParams = defaultParams.getSelectContentParams(); + + const UnitsConverter unitsConverter(page_info.metadata().dpi()); + + const QSizeF& pageRectSize = selectContentParams.getPageRectSize(); + double pageRectWidth = pageRectSize.width(); + double pageRectHeight = pageRectSize.height(); + unitsConverter.convert(pageRectWidth, pageRectHeight, defaultParams.getUnits(), PIXELS); + + m_settings->setPageParams( + page_info.id(), Params(QRectF(), QSizeF(), QRectF(QPointF(0, 0), QSizeF(pageRectWidth, pageRectHeight)), + Dependencies(), selectContentParams.isContentDetectEnabled() ? MODE_AUTO : MODE_DISABLED, + selectContentParams.getPageDetectMode(), selectContentParams.isFineTuneCorners())); } OptionsWidget* Filter::optionsWidget() { - return m_ptrOptionsWidget.get(); + return m_optionsWidget.get(); } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/Filter.h b/filters/select_content/Filter.h index fcefc28a6..8814ed78d 100644 --- a/filters/select_content/Filter.h +++ b/filters/select_content/Filter.h @@ -19,16 +19,16 @@ #ifndef SELECT_CONTENT_FILTER_H_ #define SELECT_CONTENT_FILTER_H_ -#include "NonCopyable.h" +#include +#include #include "AbstractFilter.h" -#include "PageView.h" -#include "intrusive_ptr.h" #include "FilterResult.h" -#include "SafeDeletingQObjectPtr.h" +#include "NonCopyable.h" #include "PageOrderOption.h" +#include "PageView.h" +#include "SafeDeletingQObjectPtr.h" #include "Settings.h" -#include -#include +#include "intrusive_ptr.h" class PageSelectionAccessor; class QString; @@ -45,51 +45,51 @@ class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { - DECLARE_NON_COPYABLE(Filter) + DECLARE_NON_COPYABLE(Filter) - Q_DECLARE_TR_FUNCTIONS(select_content::Filter) -public: - explicit Filter(const PageSelectionAccessor& page_selection_accessor); + Q_DECLARE_TR_FUNCTIONS(select_content::Filter) + public: + explicit Filter(const PageSelectionAccessor& page_selection_accessor); - ~Filter() override; + ~Filter() override; - QString getName() const override; + QString getName() const override; - PageView getView() const override; + PageView getView() const override; - int selectedPageOrder() const override; + int selectedPageOrder() const override; - void selectPageOrder(int option) override; + void selectPageOrder(int option) override; - virtual std::vector pageOrderOptions() const; + virtual std::vector pageOrderOptions() const; - void performRelinking(const AbstractRelinker& relinker) override; + void performRelinking(const AbstractRelinker& relinker) override; - void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; + void preUpdateUI(FilterUiInterface* ui, const PageInfo& page_info) override; - QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; + QDomElement saveSettings(const ProjectWriter& writer, QDomDocument& doc) const override; - void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; + void loadSettings(const ProjectReader& reader, const QDomElement& filters_el) override; - void loadDefaultSettings(const PageInfo& page_info) override; + void loadDefaultSettings(const PageInfo& page_info) override; - intrusive_ptr createTask(const PageId& page_id, - intrusive_ptr next_task, - bool batch, - bool debug); + intrusive_ptr createTask(const PageId& page_id, + intrusive_ptr next_task, + bool batch, + bool debug); - intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); + intrusive_ptr createCacheDrivenTask(intrusive_ptr next_task); - OptionsWidget* optionsWidget(); + OptionsWidget* optionsWidget(); -private: - void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; + private: + void writePageSettings(QDomDocument& doc, QDomElement& filter_el, const PageId& page_id, int numeric_id) const; - intrusive_ptr m_ptrSettings; - SafeDeletingQObjectPtr m_ptrOptionsWidget; - std::vector m_pageOrderOptions; - int m_selectedPageOrder; + intrusive_ptr m_settings; + SafeDeletingQObjectPtr m_optionsWidget; + std::vector m_pageOrderOptions; + int m_selectedPageOrder; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_FILTER_H_ diff --git a/filters/select_content/ImageView.cpp b/filters/select_content/ImageView.cpp index 11d0a272a..2674266e9 100644 --- a/filters/select_content/ImageView.cpp +++ b/filters/select_content/ImageView.cpp @@ -17,20 +17,21 @@ */ #include "ImageView.h" -#include "ImageTransformation.h" -#include "ImagePresentation.h" -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include -#include -#include -#include -#include -#include #include +#include "ImagePresentation.h" +#include "ImageTransformation.h" using namespace imageproc; @@ -41,563 +42,611 @@ ImageView::ImageView(const QImage& image, const ImageTransformation& xform, const QRectF& content_rect, const QRectF& page_rect, - bool page_rect_enabled) - : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), - m_dragHandler(*this), - m_zoomHandler(*this), - m_pNoContentMenu(new QMenu(this)), - m_pHaveContentMenu(new QMenu(this)), - m_contentRect(content_rect), - m_pageRect(page_rect), - m_minBoxSize(10.0, 10.0), - m_pageRectEnabled(page_rect_enabled), - m_pageRectReloadRequested(false) { - setMouseTracking(true); - - interactionState().setDefaultStatusTip( - tr("Use the context menu to enable / disable the content box. Hold Shift to drag a box. Use double-click " - "on content to automatically adjust the content area.")); - - const QString content_rect_drag_tip(tr("Drag lines or corners to resize the content box.")); + const bool page_rect_enabled) + : ImageViewBase(image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea())), + m_dragHandler(*this), + m_zoomHandler(*this), + m_noContentMenu(new QMenu(this)), + m_haveContentMenu(new QMenu(this)), + m_contentRect(content_rect), + m_pageRect(page_rect), + m_minBoxSize(10.0, 10.0), + m_pageRectEnabled(page_rect_enabled), + m_pageRectReloadRequested(false) { + setMouseTracking(true); + + interactionState().setDefaultStatusTip( + tr("Use the context menu to enable / disable the content box. Hold Shift to drag a box. Use double-click " + "on content to automatically adjust the content area.")); + + const QString content_rect_drag_tip(tr("Drag lines or corners to resize the content box.")); + const QString page_rect_drag_tip(tr("Drag lines or corners to resize the page box.")); + static const int masks_by_edge[] = {TOP, RIGHT, BOTTOM, LEFT}; + static const int masks_by_corner[] = {TOP | LEFT, TOP | RIGHT, BOTTOM | RIGHT, BOTTOM | LEFT}; + for (int i = 0; i < 4; ++i) { + // Proximity priority - content rect higher than page, corners higher than edges. + m_contentRectCorners[i].setProximityPriority(4); + m_contentRectEdges[i].setProximityPriority(3); + m_pageRectCorners[i].setProximityPriority(2); + m_pageRectEdges[i].setProximityPriority(1); + // Setup corner drag handlers. - static const int masks_by_corner[] = {TOP | LEFT, TOP | RIGHT, BOTTOM | RIGHT, BOTTOM | LEFT}; - for (int i = 0; i < 4; ++i) { - m_contentRectCorners[i].setPositionCallback( - boost::bind(&ImageView::contentRectCornerPosition, this, masks_by_corner[i])); - m_contentRectCorners[i].setMoveRequestCallback( - boost::bind(&ImageView::contentRectCornerMoveRequest, this, masks_by_corner[i], _1)); - m_contentRectCorners[i].setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); - m_contentRectCornerHandlers[i].setObject(&m_contentRectCorners[i]); - m_contentRectCornerHandlers[i].setProximityStatusTip(content_rect_drag_tip); - Qt::CursorShape cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; - m_contentRectCornerHandlers[i].setProximityCursor(cursor); - m_contentRectCornerHandlers[i].setInteractionCursor(cursor); - makeLastFollower(m_contentRectCornerHandlers[i]); - } - // Setup edge drag handlers. - static const int masks_by_edge[] = {TOP, RIGHT, BOTTOM, LEFT}; - for (int i = 0; i < 4; ++i) { - m_contentRectEdges[i].setPositionCallback( - boost::bind(&ImageView::contentRectEdgePosition, this, masks_by_edge[i])); - m_contentRectEdges[i].setMoveRequestCallback( - boost::bind(&ImageView::contentRectEdgeMoveRequest, this, masks_by_edge[i], _1)); - m_contentRectEdges[i].setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); - m_contentRectEdgeHandlers[i].setObject(&m_contentRectEdges[i]); - m_contentRectEdgeHandlers[i].setProximityStatusTip(content_rect_drag_tip); - Qt::CursorShape cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; - m_contentRectEdgeHandlers[i].setProximityCursor(cursor); - m_contentRectEdgeHandlers[i].setInteractionCursor(cursor); - makeLastFollower(m_contentRectEdgeHandlers[i]); - } + m_contentRectCorners[i].setPositionCallback( + boost::bind(&ImageView::contentRectCornerPosition, this, masks_by_corner[i])); + m_contentRectCorners[i].setMoveRequestCallback( + boost::bind(&ImageView::contentRectCornerMoveRequest, this, masks_by_corner[i], _1)); + m_contentRectCorners[i].setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); + m_contentRectCornerHandlers[i].setObject(&m_contentRectCorners[i]); + m_contentRectCornerHandlers[i].setProximityStatusTip(content_rect_drag_tip); + m_pageRectCorners[i].setPositionCallback(boost::bind(&ImageView::pageRectCornerPosition, this, masks_by_corner[i])); + m_pageRectCorners[i].setMoveRequestCallback( + boost::bind(&ImageView::pageRectCornerMoveRequest, this, masks_by_corner[i], _1)); + m_pageRectCorners[i].setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); + m_pageRectCornerHandlers[i].setObject(&m_pageRectCorners[i]); + m_pageRectCornerHandlers[i].setProximityStatusTip(page_rect_drag_tip); - if (page_rect_enabled) { - const QString page_rect_drag_tip(tr("Drag lines or corners to resize the page box.")); - // Setup corner drag handlers. - for (int i = 0; i < 4; ++i) { - m_pageRectCorners[i].setPositionCallback( - boost::bind(&ImageView::pageRectCornerPosition, this, masks_by_corner[i])); - m_pageRectCorners[i].setMoveRequestCallback( - boost::bind(&ImageView::pageRectCornerMoveRequest, this, masks_by_corner[i], _1)); - m_pageRectCorners[i].setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); - m_pageRectCornerHandlers[i].setObject(&m_pageRectCorners[i]); - m_pageRectCornerHandlers[i].setProximityStatusTip(page_rect_drag_tip); - Qt::CursorShape cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; - m_pageRectCornerHandlers[i].setProximityCursor(cursor); - m_pageRectCornerHandlers[i].setInteractionCursor(cursor); - makeLastFollower(m_pageRectCornerHandlers[i]); - } - // Setup edge drag handlers. - for (int i = 0; i < 4; ++i) { - m_pageRectEdges[i].setPositionCallback( - boost::bind(&ImageView::pageRectEdgePosition, this, masks_by_edge[i])); - m_pageRectEdges[i].setMoveRequestCallback( - boost::bind(&ImageView::pageRectEdgeMoveRequest, this, masks_by_edge[i], _1)); - m_pageRectEdges[i].setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); - m_pageRectEdgeHandlers[i].setObject(&m_pageRectEdges[i]); - m_pageRectEdgeHandlers[i].setProximityStatusTip(page_rect_drag_tip); - Qt::CursorShape cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; - m_pageRectEdgeHandlers[i].setProximityCursor(cursor); - m_pageRectEdgeHandlers[i].setInteractionCursor(cursor); - makeLastFollower(m_pageRectEdgeHandlers[i]); - } + // Setup edge drag handlers. + m_contentRectEdges[i].setPositionCallback(boost::bind(&ImageView::contentRectEdgePosition, this, masks_by_edge[i])); + m_contentRectEdges[i].setMoveRequestCallback( + boost::bind(&ImageView::contentRectEdgeMoveRequest, this, masks_by_edge[i], _1)); + m_contentRectEdges[i].setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); + m_contentRectEdgeHandlers[i].setObject(&m_contentRectEdges[i]); + m_contentRectEdgeHandlers[i].setProximityStatusTip(content_rect_drag_tip); + m_pageRectEdges[i].setPositionCallback(boost::bind(&ImageView::pageRectEdgePosition, this, masks_by_edge[i])); + m_pageRectEdges[i].setMoveRequestCallback( + boost::bind(&ImageView::pageRectEdgeMoveRequest, this, masks_by_edge[i], _1)); + m_pageRectEdges[i].setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); + m_pageRectEdgeHandlers[i].setObject(&m_pageRectEdges[i]); + m_pageRectEdgeHandlers[i].setProximityStatusTip(page_rect_drag_tip); + + Qt::CursorShape corner_cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; + m_contentRectCornerHandlers[i].setProximityCursor(corner_cursor); + m_contentRectCornerHandlers[i].setInteractionCursor(corner_cursor); + m_pageRectCornerHandlers[i].setProximityCursor(corner_cursor); + m_pageRectCornerHandlers[i].setInteractionCursor(corner_cursor); + + Qt::CursorShape edge_cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; + m_contentRectEdgeHandlers[i].setProximityCursor(edge_cursor); + m_contentRectEdgeHandlers[i].setInteractionCursor(edge_cursor); + m_pageRectEdgeHandlers[i].setProximityCursor(edge_cursor); + m_pageRectEdgeHandlers[i].setInteractionCursor(edge_cursor); + + if (m_contentRect.isValid()) { + makeLastFollower(m_contentRectCornerHandlers[i]); + makeLastFollower(m_contentRectEdgeHandlers[i]); } - - { - m_contentRectArea.setPositionCallback(boost::bind(&ImageView::contentRectPosition, this)); - m_contentRectArea.setMoveRequestCallback(boost::bind(&ImageView::contentRectMoveRequest, this, _1)); - m_contentRectArea.setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); - m_contentRectAreaHandler.setObject(&m_contentRectArea); - m_contentRectAreaHandler.setProximityStatusTip(tr("Hold left mouse button to drag the content box.")); - m_contentRectAreaHandler.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); - Qt::CursorShape cursor = Qt::DragMoveCursor; - m_contentRectAreaHandler.setKeyboardModifiers(Qt::ShiftModifier); - m_contentRectAreaHandler.setProximityCursor(cursor); - m_contentRectAreaHandler.setInteractionCursor(cursor); - makeLastFollower(m_contentRectAreaHandler); + if (m_pageRectEnabled) { + makeLastFollower(m_pageRectCornerHandlers[i]); + makeLastFollower(m_pageRectEdgeHandlers[i]); + } + } + + { + // Proximity priority - content rect higher than page + m_contentRectArea.setProximityPriority(2); + m_pageRectArea.setProximityPriority(1); + + // Setup rectangle drag interaction + m_contentRectArea.setPositionCallback(boost::bind(&ImageView::contentRectPosition, this)); + m_contentRectArea.setMoveRequestCallback(boost::bind(&ImageView::contentRectMoveRequest, this, _1)); + m_contentRectArea.setDragFinishedCallback(boost::bind(&ImageView::contentRectDragFinished, this)); + m_contentRectAreaHandler.setObject(&m_contentRectArea); + m_contentRectAreaHandler.setProximityStatusTip(tr("Hold left mouse button to drag the content box.")); + m_contentRectAreaHandler.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); + m_pageRectArea.setPositionCallback(boost::bind(&ImageView::pageRectPosition, this)); + m_pageRectArea.setMoveRequestCallback(boost::bind(&ImageView::pageRectMoveRequest, this, _1)); + m_pageRectArea.setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); + m_pageRectAreaHandler.setObject(&m_pageRectArea); + m_pageRectAreaHandler.setProximityStatusTip(tr("Hold left mouse button to drag the page box.")); + m_pageRectAreaHandler.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); + + Qt::CursorShape cursor = Qt::DragMoveCursor; + m_contentRectAreaHandler.setKeyboardModifiers({Qt::ShiftModifier}); + m_contentRectAreaHandler.setProximityCursor(cursor); + m_contentRectAreaHandler.setInteractionCursor(cursor); + m_pageRectAreaHandler.setKeyboardModifiers({Qt::ShiftModifier}); + m_pageRectAreaHandler.setProximityCursor(cursor); + m_pageRectAreaHandler.setInteractionCursor(cursor); + + if (m_contentRect.isValid()) { + makeLastFollower(m_contentRectAreaHandler); } - - if (page_rect_enabled) { - m_pageRectArea.setPositionCallback(boost::bind(&ImageView::pageRectPosition, this)); - m_pageRectArea.setMoveRequestCallback(boost::bind(&ImageView::pageRectMoveRequest, this, _1)); - m_pageRectArea.setDragFinishedCallback(boost::bind(&ImageView::pageRectDragFinished, this)); - m_pageRectAreaHandler.setObject(&m_pageRectArea); - m_pageRectAreaHandler.setProximityStatusTip(tr("Hold left mouse button to drag the page box.")); - m_pageRectAreaHandler.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); - Qt::CursorShape cursor = Qt::DragMoveCursor; - m_pageRectAreaHandler.setKeyboardModifiers(Qt::ShiftModifier); - m_pageRectAreaHandler.setProximityCursor(cursor); - m_pageRectAreaHandler.setInteractionCursor(cursor); - makeLastFollower(m_pageRectAreaHandler); + if (m_pageRectEnabled) { + makeLastFollower(m_pageRectAreaHandler); } + } - rootInteractionHandler().makeLastFollower(*this); - rootInteractionHandler().makeLastFollower(m_dragHandler); - rootInteractionHandler().makeLastFollower(m_zoomHandler); + rootInteractionHandler().makeLastFollower(*this); + rootInteractionHandler().makeLastFollower(m_dragHandler); + rootInteractionHandler().makeLastFollower(m_zoomHandler); - QAction* create = m_pNoContentMenu->addAction(tr("Create Content Box")); - QAction* remove = m_pHaveContentMenu->addAction(tr("Remove Content Box")); - create->setShortcut(QKeySequence("Ins")); - remove->setShortcut(QKeySequence("Delete")); - addAction(create); - addAction(remove); - connect(create, SIGNAL(triggered(bool)), this, SLOT(createContentBox())); - connect(remove, SIGNAL(triggered(bool)), this, SLOT(removeContentBox())); + QAction* create = m_noContentMenu->addAction(tr("Create Content Box")); + QAction* remove = m_haveContentMenu->addAction(tr("Remove Content Box")); + create->setShortcut(QKeySequence("Ins")); + remove->setShortcut(QKeySequence("Delete")); + addAction(create); + addAction(remove); + connect(create, SIGNAL(triggered(bool)), this, SLOT(createContentBox())); + connect(remove, SIGNAL(triggered(bool)), this, SLOT(removeContentBox())); - buildContentImage(gray_image, xform); + buildContentImage(gray_image, xform); } ImageView::~ImageView() = default; void ImageView::createContentBox() { - if (!m_contentRect.isEmpty()) { - return; - } - if (interactionState().captured()) { - return; - } - - const QRectF virtual_rect(virtualDisplayRect()); - QRectF content_rect(0, 0, virtual_rect.width() * 0.7, virtual_rect.height() * 0.7); - content_rect.moveCenter(virtual_rect.center()); - m_contentRect = content_rect; - update(); - emit manualContentRectSet(m_contentRect); + if (!m_contentRect.isEmpty()) { + return; + } + if (interactionState().captured()) { + return; + } + + const QRectF virtual_rect(virtualDisplayRect()); + QRectF content_rect(0, 0, virtual_rect.width() * 0.7, virtual_rect.height() * 0.7); + content_rect.moveCenter(virtual_rect.center()); + m_contentRect = content_rect; + forcePageRectDescribeContent(); + enableContentRectInteraction(true); + + update(); + emit manualContentRectSet(m_contentRect); } void ImageView::removeContentBox() { - if (m_contentRect.isEmpty()) { - return; - } - if (interactionState().captured()) { - return; - } - - m_contentRect = QRectF(); - update(); - emit manualContentRectSet(m_contentRect); + if (m_contentRect.isEmpty()) { + return; + } + if (interactionState().captured()) { + return; + } + + enableContentRectInteraction(false); + m_contentRect = QRectF(); + + update(); + emit manualContentRectSet(m_contentRect); } void ImageView::onPaint(QPainter& painter, const InteractionState& interaction) { - if (m_contentRect.isNull() && !m_pageRectEnabled) { - return; - } + if (m_contentRect.isNull() && !m_pageRectEnabled) { + return; + } - painter.setRenderHints(QPainter::Antialiasing, true); + painter.setRenderHints(QPainter::Antialiasing, true); - if (m_pageRectEnabled) { - QPen pen(QColor(0xff, 0x7f, 0x00)); - pen.setWidthF(1.0); - pen.setCosmetic(true); - painter.setPen(pen); + if (m_pageRectEnabled) { + // Draw the page bounding box. + QPen pen(QColor(0xff, 0x7f, 0x00)); + pen.setWidthF(2.0); + pen.setCosmetic(true); + painter.setPen(pen); - painter.setBrush(Qt::NoBrush); + painter.setBrush(Qt::NoBrush); - painter.drawRect(m_pageRect); - } + painter.drawRect(m_pageRect); + } - if (m_contentRect.isNull()) { - return; - } + if (m_contentRect.isNull()) { + return; + } - // Draw the content bounding box. - QPen pen(QColor(0x00, 0x00, 0xff)); - pen.setWidthF(1.0); - pen.setCosmetic(true); - painter.setPen(pen); + // Draw the content bounding box. + QPen pen(QColor(0x00, 0x00, 0xff)); + pen.setWidthF(1.0); + pen.setCosmetic(true); + painter.setPen(pen); - painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); + painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); - // Pen strokes will be outside of m_contentRect - that's how drawRect() works. - painter.drawRect(m_contentRect); + // Pen strokes will be outside of m_contentRect - that's how drawRect() works. + painter.drawRect(m_contentRect); } // ImageView::onPaint void ImageView::onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { - if (interaction.captured()) { - // No context menus during resizing. - return; - } - - if (m_contentRect.isEmpty()) { - m_pNoContentMenu->popup(event->globalPos()); - } else { - m_pHaveContentMenu->popup(event->globalPos()); - } + if (interaction.captured()) { + // No context menus during resizing. + return; + } + + if (m_contentRect.isEmpty()) { + m_noContentMenu->popup(event->globalPos()); + } else { + m_haveContentMenu->popup(event->globalPos()); + } } QPointF ImageView::contentRectCornerPosition(int edge_mask) const { - const QRectF rect(virtualToWidget().mapRect(m_contentRect)); - QPointF pt; - - if (edge_mask & TOP) { - pt.setY(rect.top()); - } else if (edge_mask & BOTTOM) { - pt.setY(rect.bottom()); - } - - if (edge_mask & LEFT) { - pt.setX(rect.left()); - } else if (edge_mask & RIGHT) { - pt.setX(rect.right()); - } - - return pt; + const QRectF rect(virtualToWidget().mapRect(m_contentRect)); + QPointF pt; + + if (edge_mask & TOP) { + pt.setY(rect.top()); + } else if (edge_mask & BOTTOM) { + pt.setY(rect.bottom()); + } + + if (edge_mask & LEFT) { + pt.setX(rect.left()); + } else if (edge_mask & RIGHT) { + pt.setX(rect.right()); + } + + return pt; } void ImageView::contentRectCornerMoveRequest(int edge_mask, const QPointF& pos) { - QRectF r(virtualToWidget().mapRect(m_contentRect)); - const qreal minw = m_minBoxSize.width(); - const qreal minh = m_minBoxSize.height(); - - if (edge_mask & TOP) { - r.setTop(std::min(pos.y(), r.bottom() - minh)); - } else if (edge_mask & BOTTOM) { - r.setBottom(std::max(pos.y(), r.top() + minh)); - } + QRectF r(virtualToWidget().mapRect(m_contentRect)); + const qreal minw = m_minBoxSize.width(); + const qreal minh = m_minBoxSize.height(); - if (edge_mask & LEFT) { - r.setLeft(std::min(pos.x(), r.right() - minw)); - } else if (edge_mask & RIGHT) { - r.setRight(std::max(pos.x(), r.left() + minw)); - } + if (edge_mask & TOP) { + r.setTop(std::min(pos.y(), r.bottom() - minh)); + } else if (edge_mask & BOTTOM) { + r.setBottom(std::max(pos.y(), r.top() + minh)); + } - forceInsideImage(r, edge_mask); - m_contentRect = widgetToVirtual().mapRect(r); + if (edge_mask & LEFT) { + r.setLeft(std::min(pos.x(), r.right() - minw)); + } else if (edge_mask & RIGHT) { + r.setRight(std::max(pos.x(), r.left() + minw)); + } - forcePageRectDescribeContent(); + forceInsideImage(r, edge_mask); + m_contentRect = widgetToVirtual().mapRect(r); - update(); + forcePageRectDescribeContent(); + + update(); } QLineF ImageView::contentRectEdgePosition(int edge) const { - const QRectF rect(virtualToWidget().mapRect(m_contentRect)); - - if (edge == TOP) { - return QLineF(rect.topLeft(), rect.topRight()); - } else if (edge == BOTTOM) { - return QLineF(rect.bottomLeft(), rect.bottomRight()); - } else if (edge == LEFT) { - return QLineF(rect.topLeft(), rect.bottomLeft()); - } else { - return QLineF(rect.topRight(), rect.bottomRight()); - } + const QRectF rect(virtualToWidget().mapRect(m_contentRect)); + + if (edge == TOP) { + return QLineF(rect.topLeft(), rect.topRight()); + } else if (edge == BOTTOM) { + return QLineF(rect.bottomLeft(), rect.bottomRight()); + } else if (edge == LEFT) { + return QLineF(rect.topLeft(), rect.bottomLeft()); + } else { + return QLineF(rect.topRight(), rect.bottomRight()); + } } void ImageView::contentRectEdgeMoveRequest(int edge, const QLineF& line) { - contentRectCornerMoveRequest(edge, line.p1()); + contentRectCornerMoveRequest(edge, line.p1()); } void ImageView::contentRectDragFinished() { - emit manualContentRectSet(m_contentRect); - if (m_pageRectReloadRequested) { - emit manualPageRectSet(m_pageRect); - m_pageRectReloadRequested = false; - } + emit manualContentRectSet(m_contentRect); + if (m_pageRectReloadRequested) { + emit manualPageRectSet(m_pageRect); + m_pageRectReloadRequested = false; + } } QPointF ImageView::pageRectCornerPosition(int edge_mask) const { - const QRectF rect(virtualToWidget().mapRect(m_pageRect)); - QPointF pt; - - if (edge_mask & TOP) { - pt.setY(rect.top()); - } else if (edge_mask & BOTTOM) { - pt.setY(rect.bottom()); - } - - if (edge_mask & LEFT) { - pt.setX(rect.left()); - } else if (edge_mask & RIGHT) { - pt.setX(rect.right()); - } - - return pt; + const QRectF rect(virtualToWidget().mapRect(m_pageRect)); + QPointF pt; + + if (edge_mask & TOP) { + pt.setY(rect.top()); + } else if (edge_mask & BOTTOM) { + pt.setY(rect.bottom()); + } + + if (edge_mask & LEFT) { + pt.setX(rect.left()); + } else if (edge_mask & RIGHT) { + pt.setX(rect.right()); + } + + return pt; } void ImageView::pageRectCornerMoveRequest(int edge_mask, const QPointF& pos) { - QRectF r(virtualToWidget().mapRect(m_pageRect)); - const qreal minw = m_minBoxSize.width(); - const qreal minh = m_minBoxSize.height(); - - if (edge_mask & TOP) { - r.setTop(std::min(pos.y(), r.bottom() - minh)); - } else if (edge_mask & BOTTOM) { - r.setBottom(std::max(pos.y(), r.top() + minh)); - } - - if (edge_mask & LEFT) { - r.setLeft(std::min(pos.x(), r.right() - minw)); - } else if (edge_mask & RIGHT) { - r.setRight(std::max(pos.x(), r.left() + minw)); - } - - m_pageRect = widgetToVirtual().mapRect(r); - forcePageRectDescribeContent(); - - update(); - emit pageRectSizeChanged(m_pageRect.size()); + QRectF r(virtualToWidget().mapRect(m_pageRect)); + const qreal minw = m_minBoxSize.width(); + const qreal minh = m_minBoxSize.height(); + + if (edge_mask & TOP) { + r.setTop(std::min(pos.y(), r.bottom() - minh)); + } else if (edge_mask & BOTTOM) { + r.setBottom(std::max(pos.y(), r.top() + minh)); + } + + if (edge_mask & LEFT) { + r.setLeft(std::min(pos.x(), r.right() - minw)); + } else if (edge_mask & RIGHT) { + r.setRight(std::max(pos.x(), r.left() + minw)); + } + + m_pageRect = widgetToVirtual().mapRect(r); + forcePageRectDescribeContent(); + + update(); + emit pageRectSizeChanged(m_pageRect.size()); } QLineF ImageView::pageRectEdgePosition(int edge) const { - const QRectF rect(virtualToWidget().mapRect(m_pageRect)); - - if (edge == TOP) { - return QLineF(rect.topLeft(), rect.topRight()); - } else if (edge == BOTTOM) { - return QLineF(rect.bottomLeft(), rect.bottomRight()); - } else if (edge == LEFT) { - return QLineF(rect.topLeft(), rect.bottomLeft()); - } else { - return QLineF(rect.topRight(), rect.bottomRight()); - } + const QRectF rect(virtualToWidget().mapRect(m_pageRect)); + + if (edge == TOP) { + return QLineF(rect.topLeft(), rect.topRight()); + } else if (edge == BOTTOM) { + return QLineF(rect.bottomLeft(), rect.bottomRight()); + } else if (edge == LEFT) { + return QLineF(rect.topLeft(), rect.bottomLeft()); + } else { + return QLineF(rect.topRight(), rect.bottomRight()); + } } void ImageView::pageRectEdgeMoveRequest(int edge, const QLineF& line) { - pageRectCornerMoveRequest(edge, line.p1()); + pageRectCornerMoveRequest(edge, line.p1()); } void ImageView::pageRectDragFinished() { - emit manualPageRectSet(m_pageRect); + emit manualPageRectSet(m_pageRect); } void ImageView::forceInsideImage(QRectF& widget_rect, const int edge_mask) const { - const qreal minw = m_minBoxSize.width(); - const qreal minh = m_minBoxSize.height(); - const QRectF image_rect(virtualToWidget().mapRect(virtualDisplayRect())); - - if ((edge_mask & LEFT) && (widget_rect.left() < image_rect.left())) { - widget_rect.setLeft(image_rect.left()); - widget_rect.setRight(std::max(widget_rect.right(), widget_rect.left() + minw)); - } - if ((edge_mask & RIGHT) && (widget_rect.right() > image_rect.right())) { - widget_rect.setRight(image_rect.right()); - widget_rect.setLeft(std::min(widget_rect.left(), widget_rect.right() - minw)); - } - if ((edge_mask & TOP) && (widget_rect.top() < image_rect.top())) { - widget_rect.setTop(image_rect.top()); - widget_rect.setBottom(std::max(widget_rect.bottom(), widget_rect.top() + minh)); - } - if ((edge_mask & BOTTOM) && (widget_rect.bottom() > image_rect.bottom())) { - widget_rect.setBottom(image_rect.bottom()); - widget_rect.setTop(std::min(widget_rect.top(), widget_rect.bottom() - minh)); - } + const qreal minw = m_minBoxSize.width(); + const qreal minh = m_minBoxSize.height(); + const QRectF image_rect(virtualToWidget().mapRect(virtualDisplayRect())); + + if ((edge_mask & LEFT) && (widget_rect.left() < image_rect.left())) { + widget_rect.setLeft(image_rect.left()); + widget_rect.setRight(std::max(widget_rect.right(), widget_rect.left() + minw)); + } + if ((edge_mask & RIGHT) && (widget_rect.right() > image_rect.right())) { + widget_rect.setRight(image_rect.right()); + widget_rect.setLeft(std::min(widget_rect.left(), widget_rect.right() - minw)); + } + if ((edge_mask & TOP) && (widget_rect.top() < image_rect.top())) { + widget_rect.setTop(image_rect.top()); + widget_rect.setBottom(std::max(widget_rect.bottom(), widget_rect.top() + minh)); + } + if ((edge_mask & BOTTOM) && (widget_rect.bottom() > image_rect.bottom())) { + widget_rect.setBottom(image_rect.bottom()); + widget_rect.setTop(std::min(widget_rect.top(), widget_rect.bottom() - minh)); + } } void ImageView::forcePageRectDescribeContent() { - const QRectF oldPageRect = m_pageRect; - m_pageRect |= m_contentRect; - if (m_pageRectEnabled && (m_pageRect != oldPageRect)) { - m_pageRectReloadRequested = true; - emit pageRectSizeChanged(m_pageRect.size()); - } + if (!m_contentRect.isValid()) { + return; + } + + const QRectF oldPageRect = m_pageRect; + m_pageRect |= m_contentRect; + if (m_pageRectEnabled && (m_pageRect != oldPageRect)) { + m_pageRectReloadRequested = true; + emit pageRectSizeChanged(m_pageRect.size()); + } } QRectF ImageView::contentRectPosition() const { - return virtualToWidget().mapRect(m_contentRect); + return virtualToWidget().mapRect(m_contentRect); } -void ImageView::contentRectMoveRequest(const QPolygonF& poly_pos) { - QRectF contentRectInWidget(poly_pos.boundingRect()); +void ImageView::contentRectMoveRequest(const QPolygonF& poly_moved) { + QRectF contentRectInWidget(poly_moved.boundingRect()); - const QRectF image_rect(virtualToWidget().mapRect(virtualDisplayRect())); - if (contentRectInWidget.left() < image_rect.left()) { - contentRectInWidget.translate(image_rect.left() - contentRectInWidget.left(), 0); - } - if (contentRectInWidget.right() > image_rect.right()) { - contentRectInWidget.translate(image_rect.right() - contentRectInWidget.right(), 0); - } - if (contentRectInWidget.top() < image_rect.top()) { - contentRectInWidget.translate(0, image_rect.top() - contentRectInWidget.top()); - } - if (contentRectInWidget.bottom() > image_rect.bottom()) { - contentRectInWidget.translate(0, image_rect.bottom() - contentRectInWidget.bottom()); - } + const QRectF image_rect(virtualToWidget().mapRect(virtualDisplayRect())); + if (contentRectInWidget.left() < image_rect.left()) { + contentRectInWidget.translate(image_rect.left() - contentRectInWidget.left(), 0); + } + if (contentRectInWidget.right() > image_rect.right()) { + contentRectInWidget.translate(image_rect.right() - contentRectInWidget.right(), 0); + } + if (contentRectInWidget.top() < image_rect.top()) { + contentRectInWidget.translate(0, image_rect.top() - contentRectInWidget.top()); + } + if (contentRectInWidget.bottom() > image_rect.bottom()) { + contentRectInWidget.translate(0, image_rect.bottom() - contentRectInWidget.bottom()); + } - m_contentRect = widgetToVirtual().mapRect(contentRectInWidget); + m_contentRect = widgetToVirtual().mapRect(contentRectInWidget); - forcePageRectDescribeContent(); + forcePageRectDescribeContent(); - update(); + update(); } QRectF ImageView::pageRectPosition() const { - return virtualToWidget().mapRect(m_pageRect); + return virtualToWidget().mapRect(m_pageRect); } -void ImageView::pageRectMoveRequest(const QPolygonF& poly_pos) { - QRectF pageRectInWidget(poly_pos.boundingRect()); +void ImageView::pageRectMoveRequest(const QPolygonF& poly_moved) { + QRectF pageRectInWidget(poly_moved.boundingRect()); + if (m_contentRect.isValid()) { const QRectF content_rect(virtualToWidget().mapRect(m_contentRect)); if (pageRectInWidget.left() > content_rect.left()) { - pageRectInWidget.translate(content_rect.left() - pageRectInWidget.left(), 0); + pageRectInWidget.translate(content_rect.left() - pageRectInWidget.left(), 0); } if (pageRectInWidget.right() < content_rect.right()) { - pageRectInWidget.translate(content_rect.right() - pageRectInWidget.right(), 0); + pageRectInWidget.translate(content_rect.right() - pageRectInWidget.right(), 0); } if (pageRectInWidget.top() > content_rect.top()) { - pageRectInWidget.translate(0, content_rect.top() - pageRectInWidget.top()); + pageRectInWidget.translate(0, content_rect.top() - pageRectInWidget.top()); } if (pageRectInWidget.bottom() < content_rect.bottom()) { - pageRectInWidget.translate(0, content_rect.bottom() - pageRectInWidget.bottom()); + pageRectInWidget.translate(0, content_rect.bottom() - pageRectInWidget.bottom()); } + } - m_pageRect = widgetToVirtual().mapRect(pageRectInWidget); + m_pageRect = widgetToVirtual().mapRect(pageRectInWidget); - update(); + update(); } void ImageView::pageRectSetExternally(const QRectF& pageRect) { - if (!m_pageRectEnabled) { - return; - } - m_pageRect = pageRect; - forcePageRectDescribeContent(); - - update(); - emit manualPageRectSet(m_pageRect); + if (!m_pageRectEnabled) { + return; + } + m_pageRect = pageRect; + forcePageRectDescribeContent(); + + update(); + emit manualPageRectSet(m_pageRect); } void ImageView::buildContentImage(const GrayImage& gray_image, const ImageTransformation& xform) { - ImageTransformation xform_150dpi(xform); - xform_150dpi.preScaleToDpi(Dpi(150, 150)); - - if (xform_150dpi.resultingRect().toRect().isEmpty()) { - return; - } + ImageTransformation xform_150dpi(xform); + xform_150dpi.preScaleToDpi(Dpi(150, 150)); - QImage gray150(transformToGray(gray_image, xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), - OutsidePixels::assumeColor(Qt::white))); + if (xform_150dpi.resultingRect().toRect().isEmpty()) { + return; + } - m_contentImage = binarizeWolf(gray150, QSize(51, 51), 50); + QImage gray150(transformToGray(gray_image, xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), + OutsidePixels::assumeColor(Qt::white))); - PolygonRasterizer::fillExcept(m_contentImage, WHITE, xform_150dpi.resultingPreCropArea(), Qt::WindingFill); + m_contentImage = binarizeWolf(gray150, QSize(51, 51), 50); - class EmptyTaskStatus : public TaskStatus { - void cancel() override { - } + PolygonRasterizer::fillExcept(m_contentImage, WHITE, xform_150dpi.resultingPreCropArea(), Qt::WindingFill); - bool isCancelled() const override { - return false; - } + Despeckle::despeckleInPlace(m_contentImage, Dpi(150, 150), Despeckle::NORMAL, EmptyTaskStatus()); - void throwIfCancelled() const override { - } - } status; - - Despeckle::despeckleInPlace(m_contentImage, Dpi(150, 150), Despeckle::NORMAL, status); - - m_originalToContentImage = xform_150dpi.transform(); - m_contentImageToOriginal = m_originalToContentImage.inverted(); + m_originalToContentImage = xform_150dpi.transform(); + m_contentImageToOriginal = m_originalToContentImage.inverted(); } void ImageView::onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() == Qt::LeftButton) { - if (!m_contentRect.isEmpty() && !m_contentImage.isNull()) { - correctContentBox(event->pos()); - } + if (event->button() == Qt::LeftButton) { + if (!m_contentRect.isEmpty() && !m_contentImage.isNull()) { + correctContentBox(QPointF(0.5, 0.5) + event->pos()); } + } } void ImageView::correctContentBox(const QPointF& pos) { - const QTransform widget_to_content_image(widgetToImage() * m_originalToContentImage); - const QTransform content_image_to_virtual(m_contentImageToOriginal * imageToVirtual()); + const QTransform widget_to_content_image(widgetToImage() * m_originalToContentImage); + const QTransform content_image_to_virtual(m_contentImageToOriginal * imageToVirtual()); + + const QPointF content_pos = widget_to_content_image.map(pos); + + QRect finding_area((content_pos - QPointF(15, 15)).toPoint(), QSize(30, 30)); + finding_area = finding_area.intersected(m_contentImage.rect()); + if (finding_area.isEmpty()) { + return; + } + + QRect found_area = findContentInArea(finding_area); + if (found_area.isEmpty()) { + return; + } + + const QPointF pos_in_virtual = widgetToVirtual().map(pos); + const QRectF found_area_in_virtual + = content_image_to_virtual.mapRect(QRectF(found_area)).intersected(virtualDisplayRect()); + if (found_area_in_virtual.isEmpty()) { + return; + } + + // If click position is inside the content rect, adjust the nearest side of the rect, + // else include the content at the position into the content rect. + if (!m_contentRect.contains(pos_in_virtual)) { + m_contentRect |= found_area_in_virtual; + forcePageRectDescribeContent(); + } else { + std::map distanceMap; + distanceMap[pos_in_virtual.y() - m_contentRect.top()] = TOP; + distanceMap[pos_in_virtual.x() - m_contentRect.left()] = LEFT; + distanceMap[m_contentRect.bottom() - pos_in_virtual.y()] = BOTTOM; + distanceMap[m_contentRect.right() - pos_in_virtual.x()] = RIGHT; + + const Edge edge = distanceMap.begin()->second; + QPointF movePoint; + switch (edge) { + case TOP: + case LEFT: + movePoint = QPointF(found_area_in_virtual.left(), found_area_in_virtual.top()); + break; + case BOTTOM: + case RIGHT: + movePoint = QPointF(found_area_in_virtual.right(), found_area_in_virtual.bottom()); + break; + } + + contentRectCornerMoveRequest(edge, virtualToWidget().map(movePoint)); + } + + update(); + contentRectDragFinished(); +} - const QPointF content_pos = widget_to_content_image.map(QPointF(0.5, 0.5) + pos); +QRect ImageView::findContentInArea(const QRect& area) const { + const uint32_t* image_line = m_contentImage.data(); + const int image_stride = m_contentImage.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + int top = std::numeric_limits::max(); + int left = std::numeric_limits::max(); + int bottom = std::numeric_limits::min(); + int right = std::numeric_limits::min(); + + image_line += area.top() * image_stride; + for (int y = area.top(); y <= area.bottom(); ++y) { + for (int x = area.left(); x <= area.right(); ++x) { + if (image_line[x >> 5] & (msb >> (x & 31))) { + top = std::min(top, y); + left = std::min(left, x); + bottom = std::max(bottom, y); + right = std::max(right, x); + } + } + image_line += image_stride; + } + + if (top > bottom) { + return QRect(); + } + + QRect found_area = QRect(left, top, right - left + 1, bottom - top + 1); + found_area.adjust(-1, -1, 1, 1); + + return found_area; +} - QRect finding_area((content_pos - QPointF(15, 15)).toPoint(), QSize(30, 30)); - finding_area = finding_area.intersected(m_contentImage.rect()); - if (finding_area.isEmpty()) { - return; - } +void ImageView::setPageRectEnabled(const bool state) { + m_pageRectEnabled = state; + enablePageRectInteraction(state); + update(); +} - QRect found_area = findContentInArea(finding_area); - if (found_area.isEmpty()) { - return; +void ImageView::enableContentRectInteraction(const bool state) { + if (state) { + for (int i = 0; i < 4; ++i) { + makeLastFollower(m_contentRectCornerHandlers[i]); + makeLastFollower(m_contentRectEdgeHandlers[i]); } - - // If click position is inside the content rect, adjust the nearest side of the rect, - // else include the content at the position into the content rect. - const QPointF pos_in_virtual = widgetToVirtual().map(QPointF(0.5, 0.5) + pos); - const QRectF found_area_in_virtual = content_image_to_virtual.mapRect(QRectF(found_area)); - if (!m_contentRect.contains(pos_in_virtual)) { - m_contentRect |= found_area_in_virtual; - forcePageRectDescribeContent(); - } else { - std::map distanceMap; - distanceMap[pos_in_virtual.y() - m_contentRect.top()] = TOP; - distanceMap[pos_in_virtual.x() - m_contentRect.left()] = LEFT; - distanceMap[m_contentRect.bottom() - pos_in_virtual.y()] = BOTTOM; - distanceMap[m_contentRect.right() - pos_in_virtual.x()] = RIGHT; - - const Edge edge = distanceMap.begin()->second; - QPointF movePoint; - switch (edge) { - case TOP: - case LEFT: - movePoint = QPointF(found_area_in_virtual.left(), found_area_in_virtual.top()); - break; - case BOTTOM: - case RIGHT: - movePoint = QPointF(found_area_in_virtual.right(), found_area_in_virtual.bottom()); - break; - } - - contentRectCornerMoveRequest(edge, virtualToWidget().map(movePoint)); + makeLastFollower(m_contentRectAreaHandler); + } else { + for (int i = 0; i < 4; ++i) { + m_contentRectCornerHandlers[i].unlink(); + m_contentRectEdgeHandlers[i].unlink(); } - - update(); - contentRectDragFinished(); + m_contentRectAreaHandler.unlink(); + } } -QRect ImageView::findContentInArea(const QRect& area) { - const uint32_t* image_line = m_contentImage.data(); - const int image_stride = m_contentImage.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - int top = std::numeric_limits::max(); - int left = std::numeric_limits::max(); - int bottom = std::numeric_limits::min(); - int right = std::numeric_limits::min(); - - image_line += area.top() * image_stride; - for (int y = area.top(); y <= area.bottom(); ++y) { - for (int x = area.left(); x <= area.right(); ++x) { - if (image_line[x >> 5] & (msb >> (x & 31))) { - top = std::min(top, y); - left = std::min(left, x); - bottom = std::max(bottom, y); - right = std::max(right, x); - } - } - image_line += image_stride; +void ImageView::enablePageRectInteraction(const bool state) { + if (state) { + for (int i = 0; i < 4; ++i) { + makeLastFollower(m_pageRectCornerHandlers[i]); + makeLastFollower(m_pageRectEdgeHandlers[i]); } - - if (top > bottom) { - return QRect(); + makeLastFollower(m_pageRectAreaHandler); + } else { + for (int i = 0; i < 4; ++i) { + m_pageRectCornerHandlers[i].unlink(); + m_pageRectEdgeHandlers[i].unlink(); } - - QRect found_area = QRect(left, top, right - left + 1, bottom - top + 1); - found_area.adjust(-1, -1, 1, 1); - - return found_area; + m_pageRectAreaHandler.unlink(); + } } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/ImageView.h b/filters/select_content/ImageView.h index 8ebc5cf19..dde127ebd 100644 --- a/filters/select_content/ImageView.h +++ b/filters/select_content/ImageView.h @@ -19,17 +19,17 @@ #ifndef SELECT_CONTENT_IMAGEVIEW_H_ #define SELECT_CONTENT_IMAGEVIEW_H_ -#include "ImageViewBase.h" -#include "DragHandler.h" -#include "ZoomHandler.h" -#include "DraggablePoint.h" -#include "DraggableLineSegment.h" -#include "ObjectDragHandler.h" +#include +#include #include #include #include -#include -#include +#include "DragHandler.h" +#include "DraggableLineSegment.h" +#include "DraggablePoint.h" +#include "ImageViewBase.h" +#include "ObjectDragHandler.h" +#include "ZoomHandler.h" class ImageTransformation; class QMenu; @@ -40,133 +40,138 @@ class GrayImage; namespace select_content { class ImageView : public ImageViewBase, private InteractionHandler { - Q_OBJECT -public: - /** - * \p content_rect is in virtual image coordinates. - */ - ImageView(const QImage& image, - const QImage& downscaled_image, - const imageproc::GrayImage& gray_image, - const ImageTransformation& xform, - const QRectF& content_rect, - const QRectF& page_rect, - bool page_rect_enabled); + Q_OBJECT + public: + /** + * \p content_rect is in virtual image coordinates. + */ + ImageView(const QImage& image, + const QImage& downscaled_image, + const imageproc::GrayImage& gray_image, + const ImageTransformation& xform, + const QRectF& content_rect, + const QRectF& page_rect, + bool page_rect_enabled); + + ~ImageView() override; + + signals: + + void manualContentRectSet(const QRectF& content_rect); - ~ImageView() override; + void manualPageRectSet(const QRectF& page_rect); -signals: + void pageRectSizeChanged(const QSizeF& size); - void manualContentRectSet(const QRectF& content_rect); + public slots: - void manualPageRectSet(const QRectF& page_rect); + void pageRectSetExternally(const QRectF& pageRect); - void pageRectSizeChanged(const QSizeF& size); + void setPageRectEnabled(bool state); -public slots: + private slots: - void pageRectSetExternally(const QRectF& pageRect); + void createContentBox(); -protected: - void onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) override; + void removeContentBox(); -private slots: + private: + enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; - void createContentBox(); + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void removeContentBox(); + void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) override; -private: - enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; + void onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) override; - void onPaint(QPainter& painter, const InteractionState& interaction) override; + QPointF contentRectCornerPosition(int edge_mask) const; - void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) override; + void contentRectCornerMoveRequest(int edge_mask, const QPointF& pos); - QPointF contentRectCornerPosition(int edge_mask) const; + QLineF contentRectEdgePosition(int edge) const; - void contentRectCornerMoveRequest(int edge_mask, const QPointF& pos); + void contentRectEdgeMoveRequest(int edge, const QLineF& line); - QLineF contentRectEdgePosition(int edge) const; + void contentRectDragFinished(); - void contentRectEdgeMoveRequest(int edge, const QLineF& line); + QPointF pageRectCornerPosition(int edge_mask) const; - void contentRectDragFinished(); + void pageRectCornerMoveRequest(int edge_mask, const QPointF& pos); - QPointF pageRectCornerPosition(int edge_mask) const; + QLineF pageRectEdgePosition(int edge) const; - void pageRectCornerMoveRequest(int edge_mask, const QPointF& pos); + void pageRectEdgeMoveRequest(int edge, const QLineF& line); - QLineF pageRectEdgePosition(int edge) const; + void pageRectDragFinished(); - void pageRectEdgeMoveRequest(int edge, const QLineF& line); + void forceInsideImage(QRectF& widget_rect, int edge_mask) const; - void pageRectDragFinished(); + void forcePageRectDescribeContent(); - void forceInsideImage(QRectF& widget_rect, int edge_mask) const; + QRectF contentRectPosition() const; - void forcePageRectDescribeContent(); + void contentRectMoveRequest(const QPolygonF& poly_moved); - QRectF contentRectPosition() const; + QRectF pageRectPosition() const; - void contentRectMoveRequest(const QPolygonF& pos); + void pageRectMoveRequest(const QPolygonF& poly_moved); - QRectF pageRectPosition() const; + void buildContentImage(const imageproc::GrayImage& gray_image, const ImageTransformation& xform); - void pageRectMoveRequest(const QPolygonF& pos); + void correctContentBox(const QPointF& pos); - void buildContentImage(const imageproc::GrayImage& gray_image, const ImageTransformation& xform); + QRect findContentInArea(const QRect& area) const; - void correctContentBox(const QPointF& pos); + void enableContentRectInteraction(bool state); - QRect findContentInArea(const QRect& area); + void enablePageRectInteraction(bool state); - DraggablePoint m_contentRectCorners[4]; - ObjectDragHandler m_contentRectCornerHandlers[4]; + DraggablePoint m_contentRectCorners[4]; + ObjectDragHandler m_contentRectCornerHandlers[4]; - DraggableLineSegment m_contentRectEdges[4]; - ObjectDragHandler m_contentRectEdgeHandlers[4]; + DraggableLineSegment m_contentRectEdges[4]; + ObjectDragHandler m_contentRectEdgeHandlers[4]; - DraggablePolygon m_contentRectArea; - ObjectDragHandler m_contentRectAreaHandler; + DraggablePolygon m_contentRectArea; + ObjectDragHandler m_contentRectAreaHandler; - DraggablePoint m_pageRectCorners[4]; - ObjectDragHandler m_pageRectCornerHandlers[4]; + DraggablePoint m_pageRectCorners[4]; + ObjectDragHandler m_pageRectCornerHandlers[4]; - DraggableLineSegment m_pageRectEdges[4]; - ObjectDragHandler m_pageRectEdgeHandlers[4]; + DraggableLineSegment m_pageRectEdges[4]; + ObjectDragHandler m_pageRectEdgeHandlers[4]; - DraggablePolygon m_pageRectArea; - ObjectDragHandler m_pageRectAreaHandler; + DraggablePolygon m_pageRectArea; + ObjectDragHandler m_pageRectAreaHandler; - DragHandler m_dragHandler; - ZoomHandler m_zoomHandler; + DragHandler m_dragHandler; + ZoomHandler m_zoomHandler; - /** - * The context menu to be shown if there is no content box. - */ - QMenu* m_pNoContentMenu; + /** + * The context menu to be shown if there is no content box. + */ + QMenu* m_noContentMenu; - /** - * The context menu to be shown if there exists a content box. - */ - QMenu* m_pHaveContentMenu; + /** + * The context menu to be shown if there exists a content box. + */ + QMenu* m_haveContentMenu; - /** - * Content box in virtual image coordinates. - */ - QRectF m_contentRect; - QRectF m_pageRect; + /** + * Content box in virtual image coordinates. + */ + QRectF m_contentRect; + QRectF m_pageRect; - bool m_pageRectEnabled; - bool m_pageRectReloadRequested; + bool m_pageRectEnabled; + bool m_pageRectReloadRequested; - QSizeF m_minBoxSize; + QSizeF m_minBoxSize; - imageproc::BinaryImage m_contentImage; - QTransform m_originalToContentImage; - QTransform m_contentImageToOriginal; + imageproc::BinaryImage m_contentImage; + QTransform m_originalToContentImage; + QTransform m_contentImageToOriginal; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_IMAGEVIEW_H_ diff --git a/filters/select_content/OptionsWidget.cpp b/filters/select_content/OptionsWidget.cpp index b78c9c708..b50aa253a 100644 --- a/filters/select_content/OptionsWidget.cpp +++ b/filters/select_content/OptionsWidget.cpp @@ -18,456 +18,386 @@ #include "OptionsWidget.h" #include "ApplyDialog.h" -#include "Settings.h" #include "ScopedIncDec.h" +#include "Settings.h" +#include +#include #include #include -#include namespace select_content { OptionsWidget::OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor) - : m_ptrSettings(std::move(settings)), - m_pageSelectionAccessor(page_selection_accessor), - m_ignorePageSizeChanges(0) { - setupUi(this); + : m_settings(std::move(settings)), m_pageSelectionAccessor(page_selection_accessor), m_ignorePageSizeChanges(0) { + setupUi(this); - setupUiConnections(); + setupUiConnections(); } OptionsWidget::~OptionsWidget() = default; void OptionsWidget::preUpdateUI(const PageInfo& page_info) { - removeUiConnections(); + removeUiConnections(); + + m_pageId = page_info.id(); + m_dpi = page_info.metadata().dpi(); - m_pageId = page_info.id(); - m_dpi = page_info.metadata().dpi(); - contentDetectAutoBtn->setEnabled(false); - contentDetectManualBtn->setEnabled(false); - contentDetectDisableBtn->setEnabled(false); - pageDetectAutoBtn->setEnabled(false); - pageDetectManualBtn->setEnabled(false); - pageDetectDisableBtn->setEnabled(false); + contentBoxGroup->setEnabled(false); + pageBoxGroup->setEnabled(false); - updatePageDetectOptionsDisplay(); - updateUnits(UnitsProvider::getInstance()->getUnits()); + pageDetectOptions->setVisible(false); + fineTuneBtn->setVisible(false); + dimensionsWidget->setVisible(false); - setupUiConnections(); + updateUnits(UnitsProvider::getInstance()->getUnits()); + + setupUiConnections(); } void OptionsWidget::postUpdateUI(const UiData& ui_data) { - removeUiConnections(); + removeUiConnections(); - m_uiData = ui_data; + m_uiData = ui_data; - updateContentModeIndication(ui_data.contentDetectionMode()); - updatePageModeIndication(ui_data.pageDetectionMode()); + updateContentModeIndication(ui_data.contentDetectionMode()); + updatePageModeIndication(ui_data.pageDetectionMode()); - contentDetectAutoBtn->setEnabled(true); - contentDetectManualBtn->setEnabled(true); - contentDetectDisableBtn->setEnabled(true); - pageDetectAutoBtn->setEnabled(true); - pageDetectManualBtn->setEnabled(true); - pageDetectDisableBtn->setEnabled(true); + contentBoxGroup->setEnabled(true); + pageBoxGroup->setEnabled(true); - updatePageDetectOptionsDisplay(); - updatePageRectSize(m_uiData.pageRect().size()); + updatePageDetectOptionsDisplay(); + updatePageRectSize(m_uiData.pageRect().size()); - setupUiConnections(); + setupUiConnections(); } void OptionsWidget::manualContentRectSet(const QRectF& content_rect) { - m_uiData.setContentRect(content_rect); - m_uiData.setContentDetectionMode(MODE_MANUAL); - m_uiData.setContentDetectionEnabled(true); - - updateContentModeIndication(MODE_MANUAL); - - if (m_uiData.isPageDetectionEnabled() && (m_uiData.pageDetectionMode() == MODE_AUTO)) { - m_uiData.setPageDetectionMode(MODE_MANUAL); - updatePageModeIndication(MODE_MANUAL); - updatePageDetectOptionsDisplay(); - } + m_uiData.setContentRect(content_rect); + m_uiData.setContentDetectionMode(MODE_MANUAL); + updateContentModeIndication(MODE_MANUAL); - commitCurrentParams(); + commitCurrentParams(); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::manualPageRectSet(const QRectF& page_rect) { - m_uiData.setPageRect(page_rect); - m_uiData.setPageDetectionMode(MODE_MANUAL); - m_uiData.setPageDetectionEnabled(true); - - updatePageModeIndication(MODE_MANUAL); - - if (m_uiData.isContentDetectionEnabled() && (m_uiData.contentDetectionMode() == MODE_AUTO)) { - m_uiData.setContentDetectionMode(MODE_MANUAL); - updateContentModeIndication(MODE_MANUAL); - } - - updatePageDetectOptionsDisplay(); - updatePageRectSize(page_rect.size()); + m_uiData.setPageRect(page_rect); + m_uiData.setPageDetectionMode(MODE_MANUAL); + updatePageModeIndication(MODE_MANUAL); + updatePageDetectOptionsDisplay(); + updatePageRectSize(page_rect.size()); - commitCurrentParams(); + commitCurrentParams(); - emit invalidateThumbnail(m_pageId); + emit invalidateThumbnail(m_pageId); } void OptionsWidget::updatePageRectSize(const QSizeF& size) { - const ScopedIncDec ignore_scope(m_ignorePageSizeChanges); - - double width = size.width(); - double height = size.height(); - UnitsProvider::getInstance()->convertFrom(width, height, PIXELS, m_dpi); - - widthSpinBox->setValue(width); - heightSpinBox->setValue(height); -} + const ScopedIncDec ignore_scope(m_ignorePageSizeChanges); -void OptionsWidget::contentDetectAutoToggled() { - m_uiData.setContentDetectionMode(MODE_AUTO); - m_uiData.setContentDetectionEnabled(true); + double width = size.width(); + double height = size.height(); + UnitsProvider::getInstance()->convertFrom(width, height, PIXELS, m_dpi); - commitCurrentParams(); - emit reloadRequested(); + widthSpinBox->setValue(width); + heightSpinBox->setValue(height); } -void OptionsWidget::contentDetectManualToggled() { - m_uiData.setContentDetectionMode(MODE_MANUAL); - m_uiData.setContentDetectionEnabled(true); - - if (m_uiData.isPageDetectionEnabled() && (m_uiData.pageDetectionMode() == MODE_AUTO)) { - m_uiData.setPageDetectionMode(MODE_MANUAL); - updatePageModeIndication(MODE_MANUAL); - updatePageDetectOptionsDisplay(); - } - - commitCurrentParams(); -} +void OptionsWidget::contentDetectToggled(const AutoManualMode mode) { + m_uiData.setContentDetectionMode(mode); + commitCurrentParams(); -void OptionsWidget::contentDetectDisableToggled() { - m_uiData.setContentDetectionEnabled(false); - commitCurrentParams(); - contentDetectDisableBtn->setChecked(true); + if (mode != MODE_MANUAL) { emit reloadRequested(); + } } -void OptionsWidget::pageDetectAutoToggled() { - m_uiData.setPageDetectionMode(MODE_AUTO); - m_uiData.setPageDetectionEnabled(true); - updatePageDetectOptionsDisplay(); - commitCurrentParams(); - emit reloadRequested(); -} - -void OptionsWidget::pageDetectManualToggled() { - const bool need_reload = !m_uiData.isPageDetectionEnabled(); - - m_uiData.setPageDetectionMode(MODE_MANUAL); - m_uiData.setPageDetectionEnabled(true); - if (m_uiData.isContentDetectionEnabled() && (m_uiData.contentDetectionMode() == MODE_AUTO)) { - m_uiData.setContentDetectionMode(MODE_MANUAL); - updateContentModeIndication(MODE_MANUAL); - } - updatePageDetectOptionsDisplay(); +void OptionsWidget::pageDetectToggled(const AutoManualMode mode) { + const bool need_update_state = ((mode == MODE_MANUAL) && (m_uiData.pageDetectionMode() == MODE_DISABLED)); - commitCurrentParams(); - if (need_reload) { - emit reloadRequested(); - } -} + m_uiData.setPageDetectionMode(mode); + updatePageDetectOptionsDisplay(); + commitCurrentParams(); -void OptionsWidget::pageDetectDisableToggled() { - m_uiData.setPageDetectionEnabled(false); - updatePageDetectOptionsDisplay(); - commitCurrentParams(); - pageDetectDisableBtn->setChecked(true); + if (mode != MODE_MANUAL) { emit reloadRequested(); + } else if (need_update_state) { + emit pageRectStateChanged(true); + emit invalidateThumbnail(m_pageId); + } } void OptionsWidget::fineTuningChanged(bool checked) { - m_uiData.setFineTuneCornersEnabled(checked); - commitCurrentParams(); - if (m_uiData.isPageDetectionEnabled()) { - emit reloadRequested(); - } + m_uiData.setFineTuneCornersEnabled(checked); + commitCurrentParams(); + if (m_uiData.pageDetectionMode() == MODE_AUTO) { + emit reloadRequested(); + } } void OptionsWidget::updateContentModeIndication(const AutoManualMode mode) { - if (!m_uiData.isContentDetectionEnabled()) { - contentDetectDisableBtn->setChecked(true); - } else { - if (mode == MODE_AUTO) { - contentDetectAutoBtn->setChecked(true); - } else { - contentDetectManualBtn->setChecked(true); - } - } + switch (mode) { + case MODE_AUTO: + contentDetectAutoBtn->setChecked(true); + break; + case MODE_MANUAL: + contentDetectManualBtn->setChecked(true); + break; + case MODE_DISABLED: + contentDetectDisableBtn->setChecked(true); + break; + } } void OptionsWidget::updatePageModeIndication(const AutoManualMode mode) { - if (!m_uiData.isPageDetectionEnabled()) { - pageDetectDisableBtn->setChecked(true); - } else { - if (mode == MODE_AUTO) { - pageDetectAutoBtn->setChecked(true); - } else { - pageDetectManualBtn->setChecked(true); - } - } + switch (mode) { + case MODE_AUTO: + pageDetectAutoBtn->setChecked(true); + break; + case MODE_MANUAL: + pageDetectManualBtn->setChecked(true); + break; + case MODE_DISABLED: + pageDetectDisableBtn->setChecked(true); + break; + } } void OptionsWidget::updatePageDetectOptionsDisplay() { - fineTuneBtn->setChecked(m_uiData.isFineTuningCornersEnabled()); - pageDetectOptions->setVisible(m_uiData.isPageDetectionEnabled()); - fineTuneBtn->setVisible(m_uiData.isPageDetectionEnabled() && (m_uiData.pageDetectionMode() == MODE_AUTO)); - dimensionsWidget->setVisible(m_uiData.isPageDetectionEnabled() && (m_uiData.pageDetectionMode() == MODE_MANUAL)); + fineTuneBtn->setChecked(m_uiData.isFineTuningCornersEnabled()); + pageDetectOptions->setVisible(m_uiData.pageDetectionMode() != MODE_DISABLED); + fineTuneBtn->setVisible(m_uiData.pageDetectionMode() == MODE_AUTO); + dimensionsWidget->setVisible(m_uiData.pageDetectionMode() == MODE_MANUAL); } void OptionsWidget::dimensionsChangedLocally(double) { - if (m_ignorePageSizeChanges) { - return; - } + if (m_ignorePageSizeChanges) { + return; + } - double widthSpinBoxValue = widthSpinBox->value(); - double heightSpinBoxValue = heightSpinBox->value(); - UnitsProvider::getInstance()->convertTo(widthSpinBoxValue, heightSpinBoxValue, PIXELS, m_dpi); + double widthSpinBoxValue = widthSpinBox->value(); + double heightSpinBoxValue = heightSpinBox->value(); + UnitsProvider::getInstance()->convertTo(widthSpinBoxValue, heightSpinBoxValue, PIXELS, m_dpi); - QRectF newPageRect = m_uiData.pageRect(); - newPageRect.setSize(QSizeF(widthSpinBoxValue, heightSpinBoxValue)); + QRectF newPageRect = m_uiData.pageRect(); + newPageRect.setSize(QSizeF(widthSpinBoxValue, heightSpinBoxValue)); - emit pageRectChangedLocally(newPageRect); + emit pageRectChangedLocally(newPageRect); } void OptionsWidget::commitCurrentParams() { - Dependencies deps(m_uiData.dependencies()); - // we need recalculate the boxes on switching to auto mode or if content box disabled - if ((!m_uiData.isContentDetectionEnabled() || m_uiData.contentDetectionMode() == MODE_AUTO) - || (m_uiData.isPageDetectionEnabled() && m_uiData.pageDetectionMode() == MODE_AUTO)) { - deps.invalidate(); - } - // if page detection has been disabled its recalculation required - if (!m_uiData.isPageDetectionEnabled()) { - const std::unique_ptr old_params = m_ptrSettings->getPageParams(m_pageId); - if ((old_params != nullptr) && old_params->isPageDetectionEnabled()) { - deps.invalidate(); - } - } + updateDependenciesIfNecessary(); + + Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), m_uiData.dependencies(), + m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(), m_uiData.isFineTuningCornersEnabled()); + m_settings->setPageParams(m_pageId, params); +} - Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), deps, - m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(), m_uiData.isContentDetectionEnabled(), - m_uiData.isPageDetectionEnabled(), m_uiData.isFineTuningCornersEnabled()); - m_ptrSettings->setPageParams(m_pageId, params); +void OptionsWidget::updateDependenciesIfNecessary() { + // On switching to manual mode the page dependencies isn't updated + // as Task::process isn't called, so we need to update it manually. + if (!(m_uiData.contentDetectionMode() == MODE_MANUAL || m_uiData.pageDetectionMode() == MODE_MANUAL)) { + return; + } + + Dependencies deps = m_uiData.dependencies(); + if (m_uiData.contentDetectionMode() == MODE_MANUAL) { + deps.setContentDetectionMode(MODE_MANUAL); + } + if (m_uiData.pageDetectionMode() == MODE_MANUAL) { + deps.setPageDetectionMode(MODE_MANUAL); + } + m_uiData.setDependencies(deps); } void OptionsWidget::showApplyToDialog() { - auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); - dialog->setAttribute(Qt::WA_DeleteOnClose); - connect(dialog, SIGNAL(applySelection(const std::set&, bool, bool)), this, - SLOT(applySelection(const std::set&, bool, bool))); - dialog->show(); + auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(applySelection(const std::set&, bool, bool)), this, + SLOT(applySelection(const std::set&, bool, bool))); + dialog->show(); } void OptionsWidget::applySelection(const std::set& pages, const bool apply_content_box, const bool apply_page_box) { - if (pages.empty()) { - return; - } + if (pages.empty()) { + return; + } - Dependencies deps(m_uiData.dependencies()); - // we need recalculate the boxes on switching to auto mode or if content box disabled - if ((!m_uiData.isContentDetectionEnabled() || m_uiData.contentDetectionMode() == MODE_AUTO) - || (m_uiData.isPageDetectionEnabled() && m_uiData.pageDetectionMode() == MODE_AUTO)) { - deps.invalidate(); - } + const Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), Dependencies(), + m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(), + m_uiData.isFineTuningCornersEnabled()); - const Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), deps, - m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(), - m_uiData.isContentDetectionEnabled(), m_uiData.isPageDetectionEnabled(), - m_uiData.isFineTuningCornersEnabled()); + for (const PageId& page_id : pages) { + if (m_pageId == page_id) { + continue; + } - for (const PageId& page_id : pages) { - if (m_pageId == page_id) { - continue; + Params new_params(params); + std::unique_ptr old_params = m_settings->getPageParams(page_id); + if (old_params) { + if (new_params.pageDetectionMode() == MODE_MANUAL) { + if (!apply_page_box) { + new_params.setPageRect(old_params->pageRect()); + } else { + QRectF corrected_page_rect = new_params.pageRect(); + const QRectF source_image_rect = new_params.dependencies().rotatedPageOutline().boundingRect(); + const QRectF current_image_rect = old_params->dependencies().rotatedPageOutline().boundingRect(); + if (source_image_rect.isValid() && current_image_rect.isValid()) { + corrected_page_rect.translate((current_image_rect.width() - source_image_rect.width()) / 2, + (current_image_rect.height() - source_image_rect.height()) / 2); + new_params.setPageRect(corrected_page_rect); + } } - - Params new_params(params); - - std::unique_ptr old_params = m_ptrSettings->getPageParams(page_id); - if (old_params != nullptr) { - if (new_params.isContentDetectionEnabled() && (new_params.contentDetectionMode() == MODE_MANUAL)) { - if (!apply_content_box) { - new_params.setContentRect(old_params->contentRect()); - new_params.setContentSizeMM(old_params->contentSizeMM()); - } - } - - if (!new_params.isPageDetectionEnabled() || (new_params.pageDetectionMode() == MODE_MANUAL)) { - // if page detection has been disabled its recalculation required - if (!new_params.isPageDetectionEnabled() && old_params->isPageDetectionEnabled()) { - Dependencies new_deps(new_params.dependencies()); - new_deps.invalidate(); - new_params.setDependencies(new_deps); - } - - if (new_params.isPageDetectionEnabled() && !apply_page_box) { - new_params.setPageRect(old_params->pageRect()); - } - } + } + if (new_params.contentDetectionMode() == MODE_MANUAL) { + if (!apply_content_box) { + new_params.setContentRect(old_params->contentRect()); + } else if (!new_params.contentRect().isEmpty()) { + QRectF corrected_content_rect = new_params.contentRect(); + const QRectF& source_page_rect = m_uiData.pageRect(); + const QRectF& new_page_rect = new_params.pageRect(); + corrected_content_rect.translate(new_page_rect.x() - source_page_rect.x(), + new_page_rect.y() - source_page_rect.y()); + new_params.setContentRect(corrected_content_rect); } - - m_ptrSettings->setPageParams(page_id, new_params); + } } - if (pages.size() > 1) { - emit invalidateAllThumbnails(); - } else { - for (const PageId& page_id : pages) { - emit invalidateThumbnail(page_id); - } + m_settings->setPageParams(page_id, new_params); + } + + if (pages.size() > 1) { + emit invalidateAllThumbnails(); + } else { + for (const PageId& page_id : pages) { + emit invalidateThumbnail(page_id); } + } - emit reloadRequested(); + emit reloadRequested(); } // OptionsWidget::applySelection void OptionsWidget::updateUnits(Units units) { - removeUiConnections(); - - int decimals; - double step; - switch (units) { - case PIXELS: - case MILLIMETRES: - decimals = 1; - step = 1.0; - break; - default: - decimals = 2; - step = 0.01; - break; - } + removeUiConnections(); + + int decimals; + double step; + switch (units) { + case PIXELS: + case MILLIMETRES: + decimals = 1; + step = 1.0; + break; + default: + decimals = 2; + step = 0.01; + break; + } - widthSpinBox->setDecimals(decimals); - widthSpinBox->setSingleStep(step); - heightSpinBox->setDecimals(decimals); - heightSpinBox->setSingleStep(step); + widthSpinBox->setDecimals(decimals); + widthSpinBox->setSingleStep(step); + heightSpinBox->setDecimals(decimals); + heightSpinBox->setSingleStep(step); - updatePageRectSize(m_uiData.pageRect().size()); + updatePageRectSize(m_uiData.pageRect().size()); - setupUiConnections(); + setupUiConnections(); } +#define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__)); + void OptionsWidget::setupUiConnections() { - connect(widthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); - connect(heightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); - connect(contentDetectAutoBtn, SIGNAL(pressed()), this, SLOT(contentDetectAutoToggled())); - connect(contentDetectManualBtn, SIGNAL(pressed()), this, SLOT(contentDetectManualToggled())); - connect(contentDetectDisableBtn, SIGNAL(pressed()), this, SLOT(contentDetectDisableToggled())); - connect(pageDetectAutoBtn, SIGNAL(pressed()), this, SLOT(pageDetectAutoToggled())); - connect(pageDetectManualBtn, SIGNAL(pressed()), this, SLOT(pageDetectManualToggled())); - connect(pageDetectDisableBtn, SIGNAL(pressed()), this, SLOT(pageDetectDisableToggled())); - connect(fineTuneBtn, SIGNAL(toggled(bool)), this, SLOT(fineTuningChanged(bool))); - connect(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); -} + CONNECT(widthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); + CONNECT(heightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); + CONNECT(contentDetectAutoBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_AUTO)); + CONNECT(contentDetectManualBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_MANUAL)); + CONNECT(contentDetectDisableBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_DISABLED)); + CONNECT(pageDetectAutoBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_AUTO)); + CONNECT(pageDetectManualBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_MANUAL)); + CONNECT(pageDetectDisableBtn, &QPushButton::pressed, this, + boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_DISABLED)); + CONNECT(fineTuneBtn, SIGNAL(toggled(bool)), this, SLOT(fineTuningChanged(bool))); + CONNECT(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); +} + +#undef CONNECT void OptionsWidget::removeUiConnections() { - disconnect(widthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); - disconnect(heightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double))); - disconnect(contentDetectAutoBtn, SIGNAL(pressed()), this, SLOT(contentDetectAutoToggled())); - disconnect(contentDetectManualBtn, SIGNAL(pressed()), this, SLOT(contentDetectManualToggled())); - disconnect(contentDetectDisableBtn, SIGNAL(pressed()), this, SLOT(contentDetectDisableToggled())); - disconnect(pageDetectAutoBtn, SIGNAL(pressed()), this, SLOT(pageDetectAutoToggled())); - disconnect(pageDetectManualBtn, SIGNAL(pressed()), this, SLOT(pageDetectManualToggled())); - disconnect(pageDetectDisableBtn, SIGNAL(pressed()), this, SLOT(pageDetectDisableToggled())); - disconnect(fineTuneBtn, SIGNAL(toggled(bool)), this, SLOT(fineTuningChanged(bool))); - disconnect(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); + for (const auto& connection : m_connectionList) { + disconnect(connection); + } + m_connectionList.clear(); } /*========================= OptionsWidget::UiData ======================*/ OptionsWidget::UiData::UiData() - : m_contentDetectionMode(MODE_AUTO), - m_contentDetectionEnabled(true), - m_pageDetectionEnabled(false), - m_pageDetectionMode(MODE_AUTO), - m_fineTuneCornersEnabled(false) { -} + : m_contentDetectionMode(MODE_AUTO), m_pageDetectionMode(MODE_DISABLED), m_fineTuneCornersEnabled(false) {} OptionsWidget::UiData::~UiData() = default; void OptionsWidget::UiData::setSizeCalc(const PhysSizeCalc& calc) { - m_sizeCalc = calc; + m_sizeCalc = calc; } void OptionsWidget::UiData::setContentRect(const QRectF& content_rect) { - m_contentRect = content_rect; + m_contentRect = content_rect; } const QRectF& OptionsWidget::UiData::contentRect() const { - return m_contentRect; + return m_contentRect; } void OptionsWidget::UiData::setPageRect(const QRectF& page_rect) { - m_pageRect = page_rect; + m_pageRect = page_rect; } const QRectF& OptionsWidget::UiData::pageRect() const { - return m_pageRect; + return m_pageRect; } QSizeF OptionsWidget::UiData::contentSizeMM() const { - return m_sizeCalc.sizeMM(m_contentRect); + return m_sizeCalc.sizeMM(m_contentRect); } void OptionsWidget::UiData::setDependencies(const Dependencies& deps) { - m_deps = deps; + m_deps = deps; } const Dependencies& OptionsWidget::UiData::dependencies() const { - return m_deps; + return m_deps; } void OptionsWidget::UiData::setContentDetectionMode(AutoManualMode mode) { - m_contentDetectionMode = mode; + m_contentDetectionMode = mode; } AutoManualMode OptionsWidget::UiData::contentDetectionMode() const { - return m_contentDetectionMode; + return m_contentDetectionMode; } void OptionsWidget::UiData::setPageDetectionMode(AutoManualMode mode) { - m_pageDetectionMode = mode; + m_pageDetectionMode = mode; } AutoManualMode OptionsWidget::UiData::pageDetectionMode() const { - return m_pageDetectionMode; + return m_pageDetectionMode; } - -void OptionsWidget::UiData::setContentDetectionEnabled(bool detect) { - m_contentDetectionEnabled = detect; -} - -void OptionsWidget::UiData::setPageDetectionEnabled(bool detect) { - m_pageDetectionEnabled = detect; -} - void OptionsWidget::UiData::setFineTuneCornersEnabled(bool fine_tune) { - m_fineTuneCornersEnabled = fine_tune; -} - -bool OptionsWidget::UiData::isContentDetectionEnabled() const { - return m_contentDetectionEnabled; -} - -bool OptionsWidget::UiData::isPageDetectionEnabled() const { - return m_pageDetectionEnabled; + m_fineTuneCornersEnabled = fine_tune; } bool OptionsWidget::UiData::isFineTuningCornersEnabled() const { - return m_fineTuneCornersEnabled; + return m_fineTuneCornersEnabled; } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/OptionsWidget.h b/filters/select_content/OptionsWidget.h index f6a9883da..7b62136d6 100644 --- a/filters/select_content/OptionsWidget.h +++ b/filters/select_content/OptionsWidget.h @@ -19,145 +19,134 @@ #ifndef SELECT_CONTENT_OPTIONSWIDGET_H_ #define SELECT_CONTENT_OPTIONSWIDGET_H_ -#include "ui_SelectContentOptionsWidget.h" -#include "FilterOptionsWidget.h" -#include "intrusive_ptr.h" -#include "AutoManualMode.h" +#include +#include +#include +#include +#include +#include #include "Dependencies.h" -#include "PhysSizeCalc.h" +#include "FilterOptionsWidget.h" #include "PageId.h" #include "PageSelectionAccessor.h" #include "Params.h" -#include -#include -#include -#include +#include "PhysSizeCalc.h" +#include "intrusive_ptr.h" +#include "ui_SelectContentOptionsWidget.h" namespace select_content { class Settings; class OptionsWidget : public FilterOptionsWidget, public UnitsObserver, private Ui::SelectContentOptionsWidget { - Q_OBJECT -public: - class UiData { - // Member-wise copying is OK. - public: - UiData(); - - ~UiData(); - - void setSizeCalc(const PhysSizeCalc& calc); - - void setContentRect(const QRectF& content_rect); - - void setPageRect(const QRectF& content_rect); - - const QRectF& contentRect() const; + Q_OBJECT + public: + class UiData { + // Member-wise copying is OK. + public: + UiData(); - const QRectF& pageRect() const; + ~UiData(); - QSizeF contentSizeMM() const; + void setSizeCalc(const PhysSizeCalc& calc); - void setDependencies(const Dependencies& deps); + void setContentRect(const QRectF& content_rect); - const Dependencies& dependencies() const; + void setPageRect(const QRectF& content_rect); - void setContentDetectionMode(AutoManualMode mode); + const QRectF& contentRect() const; - void setPageDetectionMode(AutoManualMode mode); + const QRectF& pageRect() const; - bool isContentDetectionEnabled() const; + QSizeF contentSizeMM() const; - bool isPageDetectionEnabled() const; + void setDependencies(const Dependencies& deps); - bool isFineTuningCornersEnabled() const; + const Dependencies& dependencies() const; - void setContentDetectionEnabled(bool detect); + void setContentDetectionMode(AutoManualMode mode); - void setPageDetectionEnabled(bool detect); + void setPageDetectionMode(AutoManualMode mode); - void setFineTuneCornersEnabled(bool fine_tune); + bool isFineTuningCornersEnabled() const; - AutoManualMode contentDetectionMode() const; + void setFineTuneCornersEnabled(bool fine_tune); - AutoManualMode pageDetectionMode() const; + AutoManualMode contentDetectionMode() const; - private: - QRectF m_contentRect; // In virtual image coordinates. - QRectF m_pageRect; - PhysSizeCalc m_sizeCalc; - Dependencies m_deps; - AutoManualMode m_contentDetectionMode; - AutoManualMode m_pageDetectionMode; - bool m_contentDetectionEnabled; - bool m_pageDetectionEnabled; - bool m_fineTuneCornersEnabled; - }; + AutoManualMode pageDetectionMode() const; + private: + QRectF m_contentRect; // In virtual image coordinates. + QRectF m_pageRect; + PhysSizeCalc m_sizeCalc; + Dependencies m_deps; + AutoManualMode m_contentDetectionMode; + AutoManualMode m_pageDetectionMode; + bool m_fineTuneCornersEnabled; + }; - OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); - ~OptionsWidget() override; + OptionsWidget(intrusive_ptr settings, const PageSelectionAccessor& page_selection_accessor); - void preUpdateUI(const PageInfo& page_info); + ~OptionsWidget() override; - void postUpdateUI(const UiData& ui_data); + void preUpdateUI(const PageInfo& page_info); - void updateUnits(Units units) override; + void postUpdateUI(const UiData& ui_data); -public slots: + void updateUnits(Units units) override; - void manualContentRectSet(const QRectF& content_rect); + public slots: - void manualPageRectSet(const QRectF& page_rect); + void manualContentRectSet(const QRectF& content_rect); - void updatePageRectSize(const QSizeF& size); + void manualPageRectSet(const QRectF& page_rect); -signals: + void updatePageRectSize(const QSizeF& size); - void pageRectChangedLocally(const QRectF& pageRect); + signals: -private slots: + void pageRectChangedLocally(const QRectF& pageRect); - void showApplyToDialog(); + void pageRectStateChanged(bool state); - void applySelection(const std::set& pages, bool apply_content_box, bool apply_page_box); + private slots: - void contentDetectAutoToggled(); + void showApplyToDialog(); - void contentDetectManualToggled(); + void applySelection(const std::set& pages, bool apply_content_box, bool apply_page_box); - void contentDetectDisableToggled(); + void contentDetectToggled(AutoManualMode mode); - void pageDetectAutoToggled(); + void pageDetectToggled(AutoManualMode mode); - void pageDetectManualToggled(); + void fineTuningChanged(bool checked); - void pageDetectDisableToggled(); + void dimensionsChangedLocally(double); - void fineTuningChanged(bool checked); + private: + void updateContentModeIndication(AutoManualMode mode); - void dimensionsChangedLocally(double); + void updatePageModeIndication(AutoManualMode mode); -private: - void updateContentModeIndication(AutoManualMode mode); + void updatePageDetectOptionsDisplay(); - void updatePageModeIndication(AutoManualMode mode); + void commitCurrentParams(); - void updatePageDetectOptionsDisplay(); + void updateDependenciesIfNecessary(); - void commitCurrentParams(); + void setupUiConnections(); - void setupUiConnections(); + void removeUiConnections(); - void removeUiConnections(); + intrusive_ptr m_settings; + UiData m_uiData; + PageSelectionAccessor m_pageSelectionAccessor; + PageId m_pageId; + Dpi m_dpi; + int m_ignorePageSizeChanges; - intrusive_ptr m_ptrSettings; - UiData m_uiData; - PageSelectionAccessor m_pageSelectionAccessor; - PageId m_pageId; - Dpi m_dpi; - int m_ignorePageSizeChanges; + std::list m_connectionList; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_OPTIONSWIDGET_H_ diff --git a/filters/select_content/OrderByHeightProvider.cpp b/filters/select_content/OrderByHeightProvider.cpp index 47cb5091c..d82d12b0a 100644 --- a/filters/select_content/OrderByHeightProvider.cpp +++ b/filters/select_content/OrderByHeightProvider.cpp @@ -21,33 +21,32 @@ #include namespace select_content { -OrderByHeightProvider::OrderByHeightProvider(intrusive_ptr settings) : m_ptrSettings(std::move(settings)) { -} +OrderByHeightProvider::OrderByHeightProvider(intrusive_ptr settings) : m_settings(std::move(settings)) {} bool OrderByHeightProvider::precedes(const PageId& lhs_page, const bool lhs_incomplete, const PageId& rhs_page, const bool rhs_incomplete) const { - const std::unique_ptr lhs_params(m_ptrSettings->getPageParams(lhs_page)); - const std::unique_ptr rhs_params(m_ptrSettings->getPageParams(rhs_page)); - - QSizeF lhs_size; - if (lhs_params) { - lhs_size = lhs_params->contentSizeMM(); - } - QSizeF rhs_size; - if (rhs_params) { - rhs_size = rhs_params->contentSizeMM(); - } - - const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); - const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); - - if (lhs_valid != rhs_valid) { - // Invalid (unknown) sizes go to the back. - return lhs_valid; - } - - return lhs_size.height() < rhs_size.height(); + const std::unique_ptr lhs_params(m_settings->getPageParams(lhs_page)); + const std::unique_ptr rhs_params(m_settings->getPageParams(rhs_page)); + + QSizeF lhs_size; + if (lhs_params) { + lhs_size = lhs_params->contentSizeMM(); + } + QSizeF rhs_size; + if (rhs_params) { + rhs_size = rhs_params->contentSizeMM(); + } + + const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); + const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); + + if (lhs_valid != rhs_valid) { + // Invalid (unknown) sizes go to the back. + return lhs_valid; + } + + return lhs_size.height() < rhs_size.height(); } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/OrderByHeightProvider.h b/filters/select_content/OrderByHeightProvider.h index 8829b30da..2313656a4 100644 --- a/filters/select_content/OrderByHeightProvider.h +++ b/filters/select_content/OrderByHeightProvider.h @@ -19,22 +19,22 @@ #ifndef SELECT_CONTENT_ORDER_BY_HEIGHT_PROVIDER_H_ #define SELECT_CONTENT_ORDER_BY_HEIGHT_PROVIDER_H_ +#include "PageOrderProvider.h" #include "Settings.h" #include "intrusive_ptr.h" -#include "PageOrderProvider.h" namespace select_content { class OrderByHeightProvider : public PageOrderProvider { -public: - explicit OrderByHeightProvider(intrusive_ptr settings); + public: + explicit OrderByHeightProvider(intrusive_ptr settings); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_settings; }; } // namespace select_content #endif diff --git a/filters/select_content/OrderByWidthProvider.cpp b/filters/select_content/OrderByWidthProvider.cpp index 572fdafb9..2e5150723 100644 --- a/filters/select_content/OrderByWidthProvider.cpp +++ b/filters/select_content/OrderByWidthProvider.cpp @@ -21,33 +21,32 @@ #include namespace select_content { -OrderByWidthProvider::OrderByWidthProvider(intrusive_ptr settings) : m_ptrSettings(std::move(settings)) { -} +OrderByWidthProvider::OrderByWidthProvider(intrusive_ptr settings) : m_settings(std::move(settings)) {} bool OrderByWidthProvider::precedes(const PageId& lhs_page, const bool lhs_incomplete, const PageId& rhs_page, const bool rhs_incomplete) const { - const std::unique_ptr lhs_params(m_ptrSettings->getPageParams(lhs_page)); - const std::unique_ptr rhs_params(m_ptrSettings->getPageParams(rhs_page)); - - QSizeF lhs_size; - if (lhs_params) { - lhs_size = lhs_params->contentSizeMM(); - } - QSizeF rhs_size; - if (rhs_params) { - rhs_size = rhs_params->contentSizeMM(); - } - - const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); - const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); - - if (lhs_valid != rhs_valid) { - // Invalid (unknown) sizes go to the back. - return lhs_valid; - } - - return lhs_size.width() < rhs_size.width(); + const std::unique_ptr lhs_params(m_settings->getPageParams(lhs_page)); + const std::unique_ptr rhs_params(m_settings->getPageParams(rhs_page)); + + QSizeF lhs_size; + if (lhs_params) { + lhs_size = lhs_params->contentSizeMM(); + } + QSizeF rhs_size; + if (rhs_params) { + rhs_size = rhs_params->contentSizeMM(); + } + + const bool lhs_valid = !lhs_incomplete && lhs_size.isValid(); + const bool rhs_valid = !rhs_incomplete && rhs_size.isValid(); + + if (lhs_valid != rhs_valid) { + // Invalid (unknown) sizes go to the back. + return lhs_valid; + } + + return lhs_size.width() < rhs_size.width(); } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/OrderByWidthProvider.h b/filters/select_content/OrderByWidthProvider.h index 4a386db16..9aefd0908 100644 --- a/filters/select_content/OrderByWidthProvider.h +++ b/filters/select_content/OrderByWidthProvider.h @@ -19,22 +19,22 @@ #ifndef SELECT_CONTENT_ORDER_BY_WIDTH_PROVIDER_H_ #define SELECT_CONTENT_ORDER_BY_WIDTH_PROVIDER_H_ +#include "PageOrderProvider.h" #include "Settings.h" #include "intrusive_ptr.h" -#include "PageOrderProvider.h" namespace select_content { class OrderByWidthProvider : public PageOrderProvider { -public: - explicit OrderByWidthProvider(intrusive_ptr settings); + public: + explicit OrderByWidthProvider(intrusive_ptr settings); - bool precedes(const PageId& lhs_page, - bool lhs_incomplete, - const PageId& rhs_page, - bool rhs_incomplete) const override; + bool precedes(const PageId& lhs_page, + bool lhs_incomplete, + const PageId& rhs_page, + bool rhs_incomplete) const override; -private: - intrusive_ptr m_ptrSettings; + private: + intrusive_ptr m_settings; }; } // namespace select_content #endif diff --git a/filters/select_content/PageFinder.cpp b/filters/select_content/PageFinder.cpp index 7332136b4..6016ec0df 100644 --- a/filters/select_content/PageFinder.cpp +++ b/filters/select_content/PageFinder.cpp @@ -21,11 +21,11 @@ #include "DebugImages.h" #include "FilterData.h" -#include "imageproc/BinaryImage.h" +#include "TaskStatus.h" #include "imageproc/Binarize.h" -#include "imageproc/Transform.h" +#include "imageproc/BinaryImage.h" #include "imageproc/GrayRasterOp.h" -#include "TaskStatus.h" +#include "imageproc/Transform.h" #include @@ -38,173 +38,174 @@ QRectF PageFinder::findPageBox(const TaskStatus& status, const QSizeF& box, double tolerance, DebugImages* dbg) { - ImageTransformation xform_150dpi(data.xform()); - xform_150dpi.preScaleToDpi(Dpi(150, 150)); + ImageTransformation xform_150dpi(data.xform()); + xform_150dpi.preScaleToDpi(Dpi(150, 150)); - if (xform_150dpi.resultingRect().toRect().isEmpty()) { - return QRectF(); - } + if (xform_150dpi.resultingRect().toRect().isEmpty()) { + return QRectF(); + } - double to150 = 150.0 / 25.4; - auto exp_width = int(to150 * box.width()); - auto exp_height = int(to150 * box.height()); + double to150 = 150.0 / 25.4; + auto exp_width = int(to150 * box.width()); + auto exp_height = int(to150 * box.height()); #ifdef DEBUG - std::cout << "dpi: " << data.xform().origDpi().horizontal() << std::endl; - std::cout << "tolerance: " << tolerance << std::endl; - std::cout << "exp_width = " << exp_width << "; exp_height" << exp_height << std::endl; + std::cout << "dpi: " << data.xform().origDpi().horizontal() << std::endl; + std::cout << "tolerance: " << tolerance << std::endl; + std::cout << "exp_width = " << exp_width << "; exp_height" << exp_height << std::endl; #endif - const uint8_t darkest_gray_level = darkestGrayLevel(data.grayImage()); - const QColor outside_color(darkest_gray_level, darkest_gray_level, darkest_gray_level); - - QImage gray150(transformToGray(data.grayImage(), xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), - OutsidePixels::assumeColor(outside_color))); - if (dbg) { - dbg->add(gray150, "gray150"); + const GrayImage dataGrayImage = data.isBlackOnWhite() ? data.grayImage() : data.grayImage().inverted(); + const uint8_t darkest_gray_level = darkestGrayLevel(dataGrayImage); + const QColor outside_color(darkest_gray_level, darkest_gray_level, darkest_gray_level); + + QImage gray150(transformToGray(dataGrayImage, xform_150dpi.transform(), xform_150dpi.resultingRect().toRect(), + OutsidePixels::assumeColor(outside_color))); + if (dbg) { + dbg->add(gray150, "gray150"); + } + + + std::vector rects; + std::vector bwimages; + + + bwimages.push_back(peakThreshold(gray150)); + bwimages.push_back(binarizeOtsu(gray150)); + bwimages.push_back(binarizeMokji(gray150)); + bwimages.push_back(binarizeSauvola(gray150, gray150.size())); + bwimages.push_back(binarizeWolf(gray150, gray150.size())); + if (dbg) { + dbg->add(bwimages[0], "peakThreshold"); + dbg->add(bwimages[1], "OtsuThreshold"); + dbg->add(bwimages[2], "MokjiThreshold"); + dbg->add(bwimages[3], "SauvolaThreshold"); + dbg->add(bwimages[4], "WolfThreshold"); + } + + QRect content_rect(0, 0, 0, 0); + double err_width = 1.0; + double err_height = 1.0; + + if (box.isEmpty()) { + QImage bwimg(bwimages[3].toQImage()); + content_rect = detectBorders(bwimg); + if (fine_tune) { + fineTuneCorners(bwimg, content_rect, QSize(0, 0), 1.0); } - - - std::vector rects; - std::vector bwimages; - - - bwimages.push_back(peakThreshold(gray150)); - bwimages.push_back(binarizeOtsu(gray150)); - bwimages.push_back(binarizeMokji(gray150)); - bwimages.push_back(binarizeSauvola(gray150, gray150.size())); - bwimages.push_back(binarizeWolf(gray150, gray150.size())); - if (dbg) { - dbg->add(bwimages[0], "peakThreshold"); - dbg->add(bwimages[1], "OtsuThreshold"); - dbg->add(bwimages[2], "MokjiThreshold"); - dbg->add(bwimages[3], "SauvolaThreshold"); - dbg->add(bwimages[4], "WolfThreshold"); - } - - QRect content_rect(0, 0, 0, 0); - double err_width = 1.0; - double err_height = 1.0; - - if (box.isEmpty()) { - QImage bwimg(bwimages[3].toQImage()); - content_rect = detectBorders(bwimg); - if (fine_tune) { - fineTuneCorners(bwimg, content_rect, QSize(0, 0), 1.0); - } - } else { - for (int i = 0; i < bwimages.size(); ++i) { - QImage bwimg(bwimages[i].toQImage()); - rects.push_back(QRect(detectBorders(bwimg))); - if (fine_tune) { - fineTuneCorners(bwimg, rects[i], QSize(exp_width, exp_height), tolerance); - } + } else { + for (int i = 0; i < bwimages.size(); ++i) { + QImage bwimg(bwimages[i].toQImage()); + rects.push_back(QRect(detectBorders(bwimg))); + if (fine_tune) { + fineTuneCorners(bwimg, rects[i], QSize(exp_width, exp_height), tolerance); + } #ifdef DEBUG - std::cout << "width = " << rects[i].width() << "; height=" << rects[i].height() << std::endl; + std::cout << "width = " << rects[i].width() << "; height=" << rects[i].height() << std::endl; #endif - double err_w = double(std::abs(exp_width - rects[i].width())) / double(exp_width); - double err_h = double(std::abs(exp_height - rects[i].height())) / double(exp_height); + double err_w = double(std::abs(exp_width - rects[i].width())) / double(exp_width); + double err_h = double(std::abs(exp_height - rects[i].height())) / double(exp_height); #ifdef DEBUG - std::cout << "err_w=" << err_w << "; err_h" << err_h << std::endl; + std::cout << "err_w=" << err_w << "; err_h" << err_h << std::endl; #endif - if (err_w < err_width) { - content_rect.setLeft(rects[i].left()); - content_rect.setRight(rects[i].right()); - err_width = err_w; - } - if (err_h < err_height) { - content_rect.setTop(rects[i].top()); - content_rect.setBottom(rects[i].bottom()); - err_height = err_h; - } - } + if (err_w < err_width) { + content_rect.setLeft(rects[i].left()); + content_rect.setRight(rects[i].right()); + err_width = err_w; + } + if (err_h < err_height) { + content_rect.setTop(rects[i].top()); + content_rect.setBottom(rects[i].bottom()); + err_height = err_h; + } } + } #ifdef DEBUG - std::cout << "width = " << content_rect.width() << "; height=" << content_rect.height() << std::endl; + std::cout << "width = " << content_rect.width() << "; height=" << content_rect.height() << std::endl; #endif - QTransform combined_xform(xform_150dpi.transform().inverted()); - combined_xform *= data.xform().transform(); - QRectF result = combined_xform.map(QRectF(content_rect)).boundingRect(); + QTransform combined_xform(xform_150dpi.transform().inverted()); + combined_xform *= data.xform().transform(); + QRectF result = combined_xform.map(QRectF(content_rect)).boundingRect(); - return result; + return result; } // PageFinder::findPageBox QRect PageFinder::detectBorders(const QImage& img) { - int l = 0, t = 0, r = img.width() - 1, b = img.height() - 1; - int xmid = r / 2; - int ymid = b / 2; + int l = 0, t = 0, r = img.width() - 1, b = img.height() - 1; + int xmid = r / 2; + int ymid = b / 2; - l = detectEdge(img, l, r, 1, ymid, Qt::Horizontal); - t = detectEdge(img, t, b, 1, xmid, Qt::Vertical); - r = detectEdge(img, r, 0, -1, ymid, Qt::Horizontal); - b = detectEdge(img, b, t, -1, xmid, Qt::Vertical); + l = detectEdge(img, l, r, 1, ymid, Qt::Horizontal); + t = detectEdge(img, t, b, 1, xmid, Qt::Vertical); + r = detectEdge(img, r, 0, -1, ymid, Qt::Horizontal); + b = detectEdge(img, b, t, -1, xmid, Qt::Vertical); - return QRect(l, t, r - l + 1, b - t + 1); + return QRect(l, t, r - l + 1, b - t + 1); } /** * shift edge while points around mid are black */ int PageFinder::detectEdge(const QImage& img, int start, int end, int inc, int mid, Qt::Orientation orient) { - int min_size = 10; - int gap = 0; - int i = start, edge = start; - int ms = 0; - int me = 2 * mid; - auto min_bp = int(double(me - ms) * 0.95); - Qt::GlobalColor black = Qt::color1; - - while (i != end) { - int black_pixels = 0; - int old_gap = gap; - - for (int j = ms; j != me; j++) { - int x = i, y = j; - if (orient == Qt::Vertical) { - x = j; - y = i; - } - int pixel = img.pixelIndex(x, y); - if (pixel == black) { - ++black_pixels; - } - } - - if (black_pixels < min_bp) { - ++gap; - } else { - gap = 0; - edge = i; - } - - if (gap > min_size) { - break; - } - - i += inc; + int min_size = 10; + int gap = 0; + int i = start, edge = start; + int ms = 0; + int me = 2 * mid; + auto min_bp = int(double(me - ms) * 0.95); + Qt::GlobalColor black = Qt::color1; + + while (i != end) { + int black_pixels = 0; + int old_gap = gap; + + for (int j = ms; j != me; j++) { + int x = i, y = j; + if (orient == Qt::Vertical) { + x = j; + y = i; + } + int pixel = img.pixelIndex(x, y); + if (pixel == black) { + ++black_pixels; + } } - return edge; -} // PageFinder::detectEdge + if (black_pixels < min_bp) { + ++gap; + } else { + gap = 0; + edge = i; + } -void PageFinder::fineTuneCorners(const QImage& img, QRect& rect, const QSize& size, double tolerance) { - int l = rect.left(), t = rect.top(), r = rect.right(), b = rect.bottom(); - bool done = false; - - while (!done) { - done = fineTuneCorner(img, l, t, r, b, 1, 1, size, tolerance); - done &= fineTuneCorner(img, r, t, l, b, -1, 1, size, tolerance); - done &= fineTuneCorner(img, l, b, r, t, 1, -1, size, tolerance); - done &= fineTuneCorner(img, r, b, l, t, -1, -1, size, tolerance); + if (gap > min_size) { + break; } - rect.setLeft(l); - rect.setTop(t); - rect.setRight(r); - rect.setBottom(b); + i += inc; + } + + return edge; +} // PageFinder::detectEdge + +void PageFinder::fineTuneCorners(const QImage& img, QRect& rect, const QSize& size, double tolerance) { + int l = rect.left(), t = rect.top(), r = rect.right(), b = rect.bottom(); + bool done = false; + + while (!done) { + done = fineTuneCorner(img, l, t, r, b, 1, 1, size, tolerance); + done &= fineTuneCorner(img, r, t, l, b, -1, 1, size, tolerance); + done &= fineTuneCorner(img, l, b, r, t, 1, -1, size, tolerance); + done &= fineTuneCorner(img, r, b, l, t, -1, -1, size, tolerance); + } + + rect.setLeft(l); + rect.setTop(t); + rect.setRight(r); + rect.setBottom(b); } /** @@ -219,25 +220,25 @@ bool PageFinder::fineTuneCorner(const QImage& img, int inc_y, const QSize& size, double tolerance) { - auto width_t = static_cast(size.width() * (1.0 - tolerance)); - auto height_t = static_cast(size.height() * (1.0 - tolerance)); - - Qt::GlobalColor black = Qt::color1; - int pixel = img.pixelIndex(x, y); - int tx = x + inc_x; - int ty = y + inc_y; - int w = std::abs(max_x - x); - int h = std::abs(max_y - y); - - if ((!size.isEmpty()) && ((w < width_t) || (h < height_t))) { - return true; - } - if ((pixel != black) || (tx < 0) || (tx > (img.width() - 1)) || (ty < 0) || (ty > (img.height() - 1))) { - return true; - } - x = tx; - y = ty; - - return false; + auto width_t = static_cast(size.width() * (1.0 - tolerance)); + auto height_t = static_cast(size.height() * (1.0 - tolerance)); + + Qt::GlobalColor black = Qt::color1; + int pixel = img.pixelIndex(x, y); + int tx = x + inc_x; + int ty = y + inc_y; + int w = std::abs(max_x - x); + int h = std::abs(max_y - y); + + if ((!size.isEmpty()) && ((w < width_t) || (h < height_t))) { + return true; + } + if ((pixel != black) || (tx < 0) || (tx > (img.width() - 1)) || (ty < 0) || (ty > (img.height() - 1))) { + return true; + } + x = tx; + y = ty; + + return false; } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/PageFinder.h b/filters/select_content/PageFinder.h index f1f1e5f42..4c0a8d516 100644 --- a/filters/select_content/PageFinder.h +++ b/filters/select_content/PageFinder.h @@ -20,8 +20,8 @@ #ifndef SELECT_CONTENT_PAGEFINDER_H_ #define SELECT_CONTENT_PAGEFINDER_H_ -#include "imageproc/BinaryThreshold.h" #include "Margins.h" +#include "imageproc/BinaryThreshold.h" #include @@ -40,30 +40,30 @@ class BinaryImage; namespace select_content { class PageFinder { -public: - static QRectF findPageBox(const TaskStatus& status, - const FilterData& data, - bool fine_tune, - const QSizeF& box, - double tolerance, - DebugImages* dbg = nullptr); + public: + static QRectF findPageBox(const TaskStatus& status, + const FilterData& data, + bool fine_tune, + const QSizeF& box, + double tolerance, + DebugImages* dbg = nullptr); -private: - static QRect detectBorders(const QImage& img); + private: + static QRect detectBorders(const QImage& img); - static int detectEdge(const QImage& img, int start, int end, int inc, int mid, Qt::Orientation orient); + static int detectEdge(const QImage& img, int start, int end, int inc, int mid, Qt::Orientation orient); - static void fineTuneCorners(const QImage& img, QRect& rect, const QSize& size, double tolerance); + static void fineTuneCorners(const QImage& img, QRect& rect, const QSize& size, double tolerance); - static bool fineTuneCorner(const QImage& img, - int& x, - int& y, - int max_x, - int max_y, - int inc_x, - int inc_y, - const QSize& size, - double tolerance); + static bool fineTuneCorner(const QImage& img, + int& x, + int& y, + int max_x, + int max_y, + int inc_x, + int inc_y, + const QSize& size, + double tolerance); }; } // namespace select_content #endif // ifndef SELECT_CONTENT_PAGEFINDER_H_ diff --git a/filters/select_content/Params.cpp b/filters/select_content/Params.cpp index af668bdd8..10491bb1c 100644 --- a/filters/select_content/Params.cpp +++ b/filters/select_content/Params.cpp @@ -22,13 +22,7 @@ namespace select_content { Params::Params(const Dependencies& deps) - : m_deps(deps), - m_contentDetectionMode(MODE_AUTO), - m_pageDetectionMode(MODE_AUTO), - m_contentDetectEnabled(true), - m_pageDetectEnabled(false), - m_fineTuneCorners(false) { -} + : m_deps(deps), m_contentDetectionMode(MODE_AUTO), m_pageDetectionMode(MODE_DISABLED), m_fineTuneCorners(false) {} Params::Params(const QRectF& content_rect, const QSizeF& content_size_mm, @@ -36,120 +30,94 @@ Params::Params(const QRectF& content_rect, const Dependencies& deps, const AutoManualMode content_detection_mode, const AutoManualMode page_detection_mode, - const bool contentDetect, - const bool pageDetect, - const bool fineTuning) - : m_contentRect(content_rect), - m_pageRect(page_rect), - m_contentSizeMM(content_size_mm), - m_deps(deps), - m_contentDetectionMode(content_detection_mode), - m_pageDetectionMode(page_detection_mode), - m_contentDetectEnabled(contentDetect), - m_pageDetectEnabled(pageDetect), - m_fineTuneCorners(fineTuning) { -} + const bool fine_tune_corners) + : m_contentRect(content_rect), + m_pageRect(page_rect), + m_contentSizeMM(content_size_mm), + m_deps(deps), + m_contentDetectionMode(content_detection_mode), + m_pageDetectionMode(page_detection_mode), + m_fineTuneCorners(fine_tune_corners) {} Params::Params(const QDomElement& filter_el) - : m_contentRect(XmlUnmarshaller::rectF(filter_el.namedItem("content-rect").toElement())), - m_pageRect(XmlUnmarshaller::rectF(filter_el.namedItem("page-rect").toElement())), - m_contentSizeMM(XmlUnmarshaller::sizeF(filter_el.namedItem("content-size-mm").toElement())), - m_deps(filter_el.namedItem("dependencies").toElement()), - m_contentDetectionMode(filter_el.attribute("contentDetectionMode") == "manual" ? MODE_MANUAL : MODE_AUTO), - m_pageDetectionMode(filter_el.attribute("pageDetectionMode") == "manual" ? MODE_MANUAL : MODE_AUTO), - m_contentDetectEnabled(filter_el.attribute("content-detect") == "1"), - m_pageDetectEnabled(filter_el.attribute("page-detect") == "1"), - m_fineTuneCorners(filter_el.attribute("fine-tune-corners") == "1") { -} + : m_contentRect(XmlUnmarshaller::rectF(filter_el.namedItem("content-rect").toElement())), + m_pageRect(XmlUnmarshaller::rectF(filter_el.namedItem("page-rect").toElement())), + m_contentSizeMM(XmlUnmarshaller::sizeF(filter_el.namedItem("content-size-mm").toElement())), + m_deps(filter_el.namedItem("dependencies").toElement()), + m_contentDetectionMode(stringToAutoManualMode(filter_el.attribute("contentDetectionMode"))), + m_pageDetectionMode(stringToAutoManualMode(filter_el.attribute("pageDetectionMode"))), + m_fineTuneCorners(filter_el.attribute("fineTuneCorners") == "1") {} Params::~Params() = default; QDomElement Params::toXml(QDomDocument& doc, const QString& name) const { - XmlMarshaller marshaller(doc); - - QDomElement el(doc.createElement(name)); - el.setAttribute("contentDetectionMode", (m_contentDetectionMode == MODE_AUTO) ? "auto" : "manual"); - el.setAttribute("pageDetectionMode", (m_pageDetectionMode == MODE_AUTO) ? "auto" : "manual"); - el.setAttribute("content-detect", m_contentDetectEnabled ? "1" : "0"); - el.setAttribute("page-detect", m_pageDetectEnabled ? "1" : "0"); - el.setAttribute("fine-tune-corners", m_fineTuneCorners ? "1" : "0"); - el.appendChild(marshaller.rectF(m_contentRect, "content-rect")); - el.appendChild(marshaller.rectF(m_pageRect, "page-rect")); - el.appendChild(marshaller.sizeF(m_contentSizeMM, "content-size-mm")); - el.appendChild(m_deps.toXml(doc, "dependencies")); - - return el; + XmlMarshaller marshaller(doc); + + QDomElement el(doc.createElement(name)); + el.setAttribute("contentDetectionMode", autoManualModeToString(m_contentDetectionMode)); + el.setAttribute("pageDetectionMode", autoManualModeToString(m_pageDetectionMode)); + el.setAttribute("fineTuneCorners", m_fineTuneCorners ? "1" : "0"); + el.appendChild(marshaller.rectF(m_contentRect, "content-rect")); + el.appendChild(marshaller.rectF(m_pageRect, "page-rect")); + el.appendChild(marshaller.sizeF(m_contentSizeMM, "content-size-mm")); + el.appendChild(m_deps.toXml(doc, "dependencies")); + + return el; } const QRectF& Params::contentRect() const { - return m_contentRect; + return m_contentRect; } const QRectF& Params::pageRect() const { - return m_pageRect; + return m_pageRect; } const QSizeF& Params::contentSizeMM() const { - return m_contentSizeMM; + return m_contentSizeMM; } const Dependencies& Params::dependencies() const { - return m_deps; + return m_deps; } AutoManualMode Params::contentDetectionMode() const { - return m_contentDetectionMode; + return m_contentDetectionMode; } AutoManualMode Params::pageDetectionMode() const { - return m_pageDetectionMode; -} - -bool Params::isContentDetectionEnabled() const { - return m_contentDetectEnabled; -} - -bool Params::isPageDetectionEnabled() const { - return m_pageDetectEnabled; + return m_pageDetectionMode; } bool Params::isFineTuningEnabled() const { - return m_fineTuneCorners; + return m_fineTuneCorners; } -void Params::setContentDetectionMode(const AutoManualMode& mode) { - m_contentDetectionMode = mode; +void Params::setContentDetectionMode(const AutoManualMode mode) { + m_contentDetectionMode = mode; } -void Params::setPageDetectionMode(const AutoManualMode& mode) { - m_pageDetectionMode = mode; +void Params::setPageDetectionMode(const AutoManualMode mode) { + m_pageDetectionMode = mode; } void Params::setContentRect(const QRectF& rect) { - m_contentRect = rect; + m_contentRect = rect; } void Params::setPageRect(const QRectF& rect) { - m_pageRect = rect; + m_pageRect = rect; } void Params::setContentSizeMM(const QSizeF& size) { - m_contentSizeMM = size; + m_contentSizeMM = size; } void Params::setDependencies(const Dependencies& deps) { - m_deps = deps; -} - -void Params::setContentDetect(bool detect) { - m_contentDetectEnabled = detect; -} - -void Params::setPageDetect(bool detect) { - m_pageDetectEnabled = detect; + m_deps = deps; } -void Params::setFineTuneCorners(bool fine_tune) { - m_fineTuneCorners = fine_tune; +void Params::setFineTuneCornersEnabled(bool fine_tune_corners) { + m_fineTuneCorners = fine_tune_corners; } } // namespace select_content diff --git a/filters/select_content/Params.h b/filters/select_content/Params.h index e2de46219..1719d1430 100644 --- a/filters/select_content/Params.h +++ b/filters/select_content/Params.h @@ -19,12 +19,12 @@ #ifndef SELECT_CONTENT_PARAMS_H_ #define SELECT_CONTENT_PARAMS_H_ -#include "Dependencies.h" -#include "AutoManualMode.h" -#include "Margins.h" +#include #include #include #include +#include "Dependencies.h" +#include "Margins.h" class QDomDocument; class QDomElement; @@ -32,73 +32,61 @@ class QString; namespace select_content { class Params { -public: - // Member-wise copying is OK. - - explicit Params(const Dependencies& deps); - - Params(const QRectF& content_rect, - const QSizeF& size_mm, - const QRectF& page_rect, - const Dependencies& deps, - AutoManualMode content_detection_mode, - AutoManualMode page_detection_mode, - bool contentDetect, - bool pageDetect, - bool fineTuning); - - explicit Params(const QDomElement& filter_el); - - QDomElement toXml(QDomDocument& doc, const QString& name) const; + public: + // Member-wise copying is OK. - ~Params(); + explicit Params(const Dependencies& deps); - const QRectF& contentRect() const; + Params(const QRectF& content_rect, + const QSizeF& size_mm, + const QRectF& page_rect, + const Dependencies& deps, + AutoManualMode content_detection_mode, + AutoManualMode page_detection_mode, + bool fine_tune_corners); - const QRectF& pageRect() const; + explicit Params(const QDomElement& filter_el); - const QSizeF& contentSizeMM() const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - const Dependencies& dependencies() const; + ~Params(); - AutoManualMode contentDetectionMode() const; + const QRectF& contentRect() const; - AutoManualMode pageDetectionMode() const; + const QRectF& pageRect() const; - bool isContentDetectionEnabled() const; + const QSizeF& contentSizeMM() const; - bool isPageDetectionEnabled() const; + const Dependencies& dependencies() const; - bool isFineTuningEnabled() const; + AutoManualMode contentDetectionMode() const; - void setContentDetectionMode(const AutoManualMode& mode); + AutoManualMode pageDetectionMode() const; - void setPageDetectionMode(const AutoManualMode& mode); + bool isFineTuningEnabled() const; - void setContentRect(const QRectF& rect); + void setContentDetectionMode(AutoManualMode mode); - void setPageRect(const QRectF& rect); + void setPageDetectionMode(AutoManualMode mode); - void setContentSizeMM(const QSizeF& size); + void setContentRect(const QRectF& rect); - void setDependencies(const Dependencies& deps); + void setPageRect(const QRectF& rect); - void setContentDetect(bool detect); + void setContentSizeMM(const QSizeF& size); - void setPageDetect(bool detect); + void setDependencies(const Dependencies& deps); - void setFineTuneCorners(bool fine_tune); + void setFineTuneCornersEnabled(bool fine_tune_corners); -private: - QRectF m_contentRect; - QRectF m_pageRect; - QSizeF m_contentSizeMM; - Dependencies m_deps; - AutoManualMode m_contentDetectionMode; - AutoManualMode m_pageDetectionMode; - bool m_contentDetectEnabled; - bool m_pageDetectEnabled; - bool m_fineTuneCorners; + private: + QRectF m_contentRect; + QRectF m_pageRect; + QSizeF m_contentSizeMM; + Dependencies m_deps; + AutoManualMode m_contentDetectionMode; + AutoManualMode m_pageDetectionMode; + bool m_fineTuneCorners; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_PARAMS_H_ diff --git a/filters/select_content/PhysSizeCalc.cpp b/filters/select_content/PhysSizeCalc.cpp index f69c55036..1db8befb7 100644 --- a/filters/select_content/PhysSizeCalc.cpp +++ b/filters/select_content/PhysSizeCalc.cpp @@ -16,21 +16,20 @@ along with this program. If not, see . */ -#include #include "PhysSizeCalc.h" +#include #include "ImageTransformation.h" namespace select_content { PhysSizeCalc::PhysSizeCalc() = default; PhysSizeCalc::PhysSizeCalc(const ImageTransformation& xform) - : m_virtToPhys(xform.transformBack() * UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)) { -} + : m_virtToPhys(xform.transformBack() * UnitsConverter(xform.origDpi()).transform(PIXELS, MILLIMETRES)) {} QSizeF PhysSizeCalc::sizeMM(const QRectF& rect_px) const { - const QPolygonF poly_mm(m_virtToPhys.map(rect_px)); - const QSizeF size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[1], poly_mm[2]).length()); + const QPolygonF poly_mm(m_virtToPhys.map(rect_px)); + const QSizeF size_mm(QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[1], poly_mm[2]).length()); - return size_mm; + return size_mm; } } // namespace select_content diff --git a/filters/select_content/PhysSizeCalc.h b/filters/select_content/PhysSizeCalc.h index a4f91add2..a0ff6adea 100644 --- a/filters/select_content/PhysSizeCalc.h +++ b/filters/select_content/PhysSizeCalc.h @@ -19,24 +19,24 @@ #ifndef SELECT_CONTENT_PHYS_SIZE_CALC_H_ #define SELECT_CONTENT_PHYS_SIZE_CALC_H_ -#include -#include #include +#include +#include class ImageTransformation; namespace select_content { class PhysSizeCalc { - // Member-wise copying is OK. -public: - PhysSizeCalc(); + // Member-wise copying is OK. + public: + PhysSizeCalc(); - explicit PhysSizeCalc(const ImageTransformation& xform); + explicit PhysSizeCalc(const ImageTransformation& xform); - QSizeF sizeMM(const QRectF& rect_px) const; + QSizeF sizeMM(const QRectF& rect_px) const; -private: - QTransform m_virtToPhys; + private: + QTransform m_virtToPhys; }; } // namespace select_content #endif diff --git a/filters/select_content/Settings.cpp b/filters/select_content/Settings.cpp index 75cba9148..cb0edf39a 100644 --- a/filters/select_content/Settings.cpp +++ b/filters/select_content/Settings.cpp @@ -17,100 +17,100 @@ */ #include "Settings.h" -#include "Utils.h" -#include "RelinkablePath.h" -#include "AbstractRelinker.h" #include +#include "AbstractRelinker.h" +#include "RelinkablePath.h" +#include "Utils.h" namespace select_content { Settings::Settings() : m_pageDetectionBox(0.0, 0.0), m_pageDetectionTolerance(0.1) { - m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { - auto it(m_pageParams.find(pageId)); - if (it != m_pageParams.end()) { - const Params& params = it->second; - const QSizeF& contentSizeMM = params.contentSizeMM(); - - return std::sqrt(contentSizeMM.width() * contentSizeMM.height() / 4 / 25.4); - } else { - return .0; - }; - }); + m_deviationProvider.setComputeValueByKey([this](const PageId& pageId) -> double { + auto it(m_pageParams.find(pageId)); + if (it != m_pageParams.end()) { + const Params& params = it->second; + const QSizeF& contentSizeMM = params.contentSizeMM(); + + return std::sqrt(contentSizeMM.width() * contentSizeMM.height() / 4 / 25.4); + } else { + return .0; + }; + }); } Settings::~Settings() = default; void Settings::clear() { - QMutexLocker locker(&m_mutex); - m_pageParams.clear(); - m_deviationProvider.clear(); + QMutexLocker locker(&m_mutex); + m_pageParams.clear(); + m_deviationProvider.clear(); } void Settings::performRelinking(const AbstractRelinker& relinker) { - QMutexLocker locker(&m_mutex); - PageParams new_params; - - for (const PageParams::value_type& kv : m_pageParams) { - const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); - PageId new_page_id(kv.first); - new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); - new_params.insert(PageParams::value_type(new_page_id, kv.second)); - } - - m_pageParams.swap(new_params); - - m_deviationProvider.clear(); - for (const PageParams::value_type& kv : m_pageParams) { - m_deviationProvider.addOrUpdate(kv.first); - } + QMutexLocker locker(&m_mutex); + PageParams new_params; + + for (const PageParams::value_type& kv : m_pageParams) { + const RelinkablePath old_path(kv.first.imageId().filePath(), RelinkablePath::File); + PageId new_page_id(kv.first); + new_page_id.imageId().setFilePath(relinker.substitutionPathFor(old_path)); + new_params.insert(PageParams::value_type(new_page_id, kv.second)); + } + + m_pageParams.swap(new_params); + + m_deviationProvider.clear(); + for (const PageParams::value_type& kv : m_pageParams) { + m_deviationProvider.addOrUpdate(kv.first); + } } void Settings::setPageParams(const PageId& page_id, const Params& params) { - QMutexLocker locker(&m_mutex); - Utils::mapSetValue(m_pageParams, page_id, params); - m_deviationProvider.addOrUpdate(page_id); + QMutexLocker locker(&m_mutex); + Utils::mapSetValue(m_pageParams, page_id, params); + m_deviationProvider.addOrUpdate(page_id); } void Settings::clearPageParams(const PageId& page_id) { - QMutexLocker locker(&m_mutex); - m_pageParams.erase(page_id); - m_deviationProvider.remove(page_id); + QMutexLocker locker(&m_mutex); + m_pageParams.erase(page_id); + m_deviationProvider.remove(page_id); } std::unique_ptr Settings::getPageParams(const PageId& page_id) const { - QMutexLocker locker(&m_mutex); - - const auto it(m_pageParams.find(page_id)); - if (it != m_pageParams.end()) { - return std::make_unique(it->second); - } else { - return nullptr; - } + QMutexLocker locker(&m_mutex); + + const auto it(m_pageParams.find(page_id)); + if (it != m_pageParams.end()) { + return std::make_unique(it->second); + } else { + return nullptr; + } } bool Settings::isParamsNull(const PageId& page_id) const { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - return (m_pageParams.find(page_id) == m_pageParams.end()); + return (m_pageParams.find(page_id) == m_pageParams.end()); } QSizeF Settings::pageDetectionBox() const { - return m_pageDetectionBox; + return m_pageDetectionBox; } void Settings::setPageDetectionBox(QSizeF size) { - m_pageDetectionBox = size; + m_pageDetectionBox = size; } double Settings::pageDetectionTolerance() const { - return m_pageDetectionTolerance; + return m_pageDetectionTolerance; } void Settings::setPageDetectionTolerance(double tolerance) { - m_pageDetectionTolerance = tolerance; + m_pageDetectionTolerance = tolerance; } const DeviationProvider& Settings::deviationProvider() const { - return m_deviationProvider; + return m_deviationProvider; } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/Settings.h b/filters/select_content/Settings.h index bbfea2f1a..d2a4058d2 100644 --- a/filters/select_content/Settings.h +++ b/filters/select_content/Settings.h @@ -19,56 +19,56 @@ #ifndef SELECT_CONTENT_SETTINGS_H_ #define SELECT_CONTENT_SETTINGS_H_ -#include "ref_countable.h" -#include "NonCopyable.h" -#include "PageId.h" -#include "Params.h" +#include #include #include #include -#include +#include "NonCopyable.h" +#include "PageId.h" +#include "Params.h" +#include "ref_countable.h" class AbstractRelinker; namespace select_content { class Settings : public ref_countable { - DECLARE_NON_COPYABLE(Settings) + DECLARE_NON_COPYABLE(Settings) -public: - Settings(); + public: + Settings(); - ~Settings() override; + ~Settings() override; - void clear(); + void clear(); - void performRelinking(const AbstractRelinker& relinker); + void performRelinking(const AbstractRelinker& relinker); - void setPageParams(const PageId& page_id, const Params& params); + void setPageParams(const PageId& page_id, const Params& params); - void clearPageParams(const PageId& page_id); + void clearPageParams(const PageId& page_id); - std::unique_ptr getPageParams(const PageId& page_id) const; + std::unique_ptr getPageParams(const PageId& page_id) const; - bool isParamsNull(const PageId& page_id) const; + bool isParamsNull(const PageId& page_id) const; - QSizeF pageDetectionBox() const; + QSizeF pageDetectionBox() const; - void setPageDetectionBox(QSizeF size); + void setPageDetectionBox(QSizeF size); - double pageDetectionTolerance() const; + double pageDetectionTolerance() const; - void setPageDetectionTolerance(double tolerance); + void setPageDetectionTolerance(double tolerance); - const DeviationProvider& deviationProvider() const; + const DeviationProvider& deviationProvider() const; -private: - typedef std::unordered_map PageParams; + private: + typedef std::unordered_map PageParams; - mutable QMutex m_mutex; - PageParams m_pageParams; - QSizeF m_pageDetectionBox; - double m_pageDetectionTolerance; - DeviationProvider m_deviationProvider; + mutable QMutex m_mutex; + PageParams m_pageParams; + QSizeF m_pageDetectionBox; + double m_pageDetectionTolerance; + DeviationProvider m_deviationProvider; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_SETTINGS_H_ diff --git a/filters/select_content/Task.cpp b/filters/select_content/Task.cpp index 4ba6b0581..b41984706 100644 --- a/filters/select_content/Task.cpp +++ b/filters/select_content/Task.cpp @@ -17,52 +17,50 @@ */ #include "Task.h" +#include "ContentBoxFinder.h" +#include "DebugImages.h" #include "Filter.h" #include "FilterData.h" -#include "DebugImages.h" -#include "OptionsWidget.h" -#include "TaskStatus.h" -#include "ContentBoxFinder.h" -#include "PageFinder.h" #include "FilterUiInterface.h" #include "ImageView.h" +#include "OptionsWidget.h" +#include "PageFinder.h" +#include "TaskStatus.h" #include "filters/page_layout/Task.h" +#include #include #include -#include #include "Dpm.h" using namespace imageproc; namespace select_content { class Task::UiUpdater : public FilterResult { -public: - UiUpdater(intrusive_ptr filter, - const PageId& page_id, - std::unique_ptr dbg, - const QImage& image, - const ImageTransformation& xform, - const GrayImage& gray_image, - const OptionsWidget::UiData& ui_data, - bool batch); - - void updateUI(FilterUiInterface* ui) override; - - intrusive_ptr filter() override { - return m_ptrFilter; - } - -private: - intrusive_ptr m_ptrFilter; - PageId m_pageId; - std::unique_ptr m_ptrDbg; - QImage m_image; - QImage m_downscaledImage; - GrayImage m_grayImage; - ImageTransformation m_xform; - OptionsWidget::UiData m_uiData; - bool m_batchProcessing; + public: + UiUpdater(intrusive_ptr filter, + const PageId& page_id, + std::unique_ptr dbg, + const QImage& image, + const ImageTransformation& xform, + const GrayImage& gray_image, + const OptionsWidget::UiData& ui_data, + bool batch); + + void updateUI(FilterUiInterface* ui) override; + + intrusive_ptr filter() override { return m_filter; } + + private: + intrusive_ptr m_filter; + PageId m_pageId; + std::unique_ptr m_dbg; + QImage m_image; + QImage m_downscaledImage; + GrayImage m_grayImage; + ImageTransformation m_xform; + OptionsWidget::UiData m_uiData; + bool m_batchProcessing; }; @@ -72,123 +70,98 @@ Task::Task(intrusive_ptr filter, const PageId& page_id, const bool batch, const bool debug) - : m_ptrFilter(std::move(filter)), - m_ptrNextTask(std::move(next_task)), - m_ptrSettings(std::move(settings)), - m_pageId(page_id), - m_batchProcessing(batch) { - if (debug) { - m_ptrDbg = std::make_unique(); - } + : m_filter(std::move(filter)), + m_nextTask(std::move(next_task)), + m_settings(std::move(settings)), + m_pageId(page_id), + m_batchProcessing(batch) { + if (debug) { + m_dbg = std::make_unique(); + } } Task::~Task() = default; FilterResultPtr Task::process(const TaskStatus& status, const FilterData& data) { - status.throwIfCancelled(); - - const Dependencies deps(data.xform().resultingPreCropArea()); - - OptionsWidget::UiData ui_data; - ui_data.setSizeCalc(PhysSizeCalc(data.xform())); - - std::unique_ptr params(m_ptrSettings->getPageParams(m_pageId)); - - Params new_params(deps); - if (params) { - new_params = *params; - new_params.setDependencies(deps); - } - - if (!params || !params->dependencies().matches(deps)) { - QRectF page_rect(data.xform().resultingRect()); - QRectF content_rect(page_rect); - - if (new_params.isPageDetectionEnabled() && (new_params.pageDetectionMode() == MODE_AUTO)) { - page_rect = PageFinder::findPageBox(status, data, new_params.isFineTuningEnabled(), - m_ptrSettings->pageDetectionBox(), - m_ptrSettings->pageDetectionTolerance(), m_ptrDbg.get()); - } else if (new_params.isPageDetectionEnabled() && (new_params.pageDetectionMode() == MODE_MANUAL)) { - // shifting page rect for skewed pages correcting - QRectF corrected_page_rect(new_params.pageRect()); - if (params && new_params.pageRect().isValid() && !params->dependencies().matches(deps) - && params->dependencies().rotatedPageOutline().boundingRect().isValid()) { - const QRectF new_page_rect = new_params.dependencies().rotatedPageOutline().boundingRect(); - const QRectF old_page_rect = params->dependencies().rotatedPageOutline().boundingRect(); - corrected_page_rect.translate((new_page_rect.width() - old_page_rect.width()) / 2, - (new_page_rect.height() - old_page_rect.height()) / 2); - } - - // allow the page box to be out of the page bounds but checking intersecting with the page - if (corrected_page_rect.isValid() - && corrected_page_rect.intersected(data.xform().resultingRect()).isValid()) { - page_rect = corrected_page_rect; - } else { - page_rect = data.xform().resultingRect(); - } - } else { - page_rect = data.xform().resultingRect(); - } - - if (new_params.isContentDetectionEnabled() && (new_params.contentDetectionMode() == MODE_AUTO)) { - content_rect = ContentBoxFinder::findContentBox(status, data, page_rect, m_ptrDbg.get()); - } else if ((new_params.isContentDetectionEnabled() && (new_params.contentDetectionMode() == MODE_MANUAL)) - || new_params.contentRect().isEmpty()) { - if (!new_params.contentRect().isEmpty()) { - // shifting content rect for skewed pages correcting - QRectF corrected_content_rect(new_params.contentRect()); - if (params && new_params.contentRect().isValid() && !params->dependencies().matches(deps) - && params->dependencies().rotatedPageOutline().boundingRect().isValid()) { - const QRectF new_page_rect = new_params.dependencies().rotatedPageOutline().boundingRect(); - const QRectF old_page_rect = params->dependencies().rotatedPageOutline().boundingRect(); - corrected_content_rect.translate((new_page_rect.width() - old_page_rect.width()) / 2, - (new_page_rect.height() - old_page_rect.height()) / 2); - // we don't want the content box to be out of the page box so use intersecting - corrected_content_rect = corrected_content_rect.intersected(page_rect); - } - - if (corrected_content_rect.isValid()) { - content_rect = corrected_content_rect; - } else { - content_rect = page_rect; - } - } - } else { - content_rect = page_rect; - } - - if (content_rect.isValid()) { - page_rect |= content_rect; - } - - new_params.setPageRect(page_rect); - new_params.setContentRect(content_rect); + status.throwIfCancelled(); + + std::unique_ptr params(m_settings->getPageParams(m_pageId)); + const Dependencies deps = (params) ? Dependencies(data.xform().resultingPreCropArea(), params->contentDetectionMode(), + params->pageDetectionMode(), params->isFineTuningEnabled()) + : Dependencies(data.xform().resultingPreCropArea()); + + Params new_params(deps); + if (params) { + new_params = *params; + new_params.setDependencies(deps); + } + + const PhysSizeCalc phys_size_calc(data.xform()); + + bool need_update_content_box = false; + bool need_update_page_box = false; + + if (!params || !deps.compatibleWith(params->dependencies(), &need_update_content_box, &need_update_page_box)) { + QRectF page_rect(new_params.pageRect()); + QRectF content_rect(new_params.contentRect()); + + if (need_update_page_box) { + if (new_params.pageDetectionMode() == MODE_AUTO) { + page_rect + = PageFinder::findPageBox(status, data, new_params.isFineTuningEnabled(), m_settings->pageDetectionBox(), + m_settings->pageDetectionTolerance(), m_dbg.get()); + } else if (new_params.pageDetectionMode() == MODE_DISABLED) { + page_rect = data.xform().resultingRect(); + } + + if (!data.xform().resultingRect().intersected(page_rect).isValid()) { + page_rect = data.xform().resultingRect(); + } + + // Force update the content box if it doesn't fit into the page box updated. + if (content_rect.isValid() && (content_rect.intersected(page_rect)) != content_rect) { + need_update_content_box = true; + } + + new_params.setPageRect(page_rect); } - ui_data.setContentRect(new_params.contentRect()); - ui_data.setPageRect(new_params.pageRect()); - ui_data.setDependencies(deps); - ui_data.setContentDetectionMode(new_params.contentDetectionMode()); - ui_data.setContentDetectionEnabled(new_params.isContentDetectionEnabled()); - ui_data.setPageDetectionMode(new_params.pageDetectionMode()); - ui_data.setPageDetectionEnabled(new_params.isPageDetectionEnabled()); - ui_data.setFineTuneCornersEnabled(new_params.isFineTuningEnabled()); - - if (!params || !params->dependencies().matches(deps)) { - new_params.setContentSizeMM(ui_data.contentSizeMM()); - } - - m_ptrSettings->setPageParams(m_pageId, new_params); + if (need_update_content_box) { + if (new_params.contentDetectionMode() == MODE_AUTO) { + content_rect = ContentBoxFinder::findContentBox(status, data, page_rect, m_dbg.get()); + } else if (new_params.contentDetectionMode() == MODE_DISABLED) { + content_rect = page_rect; + } - status.throwIfCancelled(); + if (content_rect.isValid()) { + content_rect &= page_rect; + } - if (m_ptrNextTask) { - return m_ptrNextTask->process(status, FilterData(data, data.xform()), ui_data.pageRect(), - ui_data.contentRect()); - } else { - return make_intrusive(m_ptrFilter, m_pageId, std::move(m_ptrDbg), data.origImage(), data.xform(), - data.grayImage(), ui_data, m_batchProcessing); + new_params.setContentRect(content_rect); + new_params.setContentSizeMM(phys_size_calc.sizeMM(content_rect)); } + } + + OptionsWidget::UiData ui_data; + ui_data.setSizeCalc(phys_size_calc); + ui_data.setContentRect(new_params.contentRect()); + ui_data.setPageRect(new_params.pageRect()); + ui_data.setDependencies(deps); + ui_data.setContentDetectionMode(new_params.contentDetectionMode()); + ui_data.setPageDetectionMode(new_params.pageDetectionMode()); + ui_data.setFineTuneCornersEnabled(new_params.isFineTuningEnabled()); + + m_settings->setPageParams(m_pageId, new_params); + + status.throwIfCancelled(); + + if (m_nextTask) { + return m_nextTask->process(status, FilterData(data, data.xform()), ui_data.pageRect(), ui_data.contentRect()); + } else { + return make_intrusive(m_filter, m_pageId, std::move(m_dbg), data.origImage(), data.xform(), + data.isBlackOnWhite() ? data.grayImage() : data.grayImage().inverted(), ui_data, + m_batchProcessing); + } } // Task::process /*============================ Task::UiUpdater ==========================*/ @@ -201,40 +174,39 @@ Task::UiUpdater::UiUpdater(intrusive_ptr filter, const GrayImage& gray_image, const OptionsWidget::UiData& ui_data, const bool batch) - : m_ptrFilter(std::move(filter)), - m_pageId(page_id), - m_ptrDbg(std::move(dbg)), - m_image(image), - m_downscaledImage(ImageView::createDownscaledImage(image)), - m_xform(xform), - m_grayImage(gray_image), - m_uiData(ui_data), - m_batchProcessing(batch) { -} + : m_filter(std::move(filter)), + m_pageId(page_id), + m_dbg(std::move(dbg)), + m_image(image), + m_downscaledImage(ImageView::createDownscaledImage(image)), + m_xform(xform), + m_grayImage(gray_image), + m_uiData(ui_data), + m_batchProcessing(batch) {} void Task::UiUpdater::updateUI(FilterUiInterface* ui) { - // This function is executed from the GUI thread. - OptionsWidget* const opt_widget = m_ptrFilter->optionsWidget(); - opt_widget->postUpdateUI(m_uiData); - ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); - - ui->invalidateThumbnail(m_pageId); - - if (m_batchProcessing) { - return; - } - - auto* view = new ImageView(m_image, m_downscaledImage, m_grayImage, m_xform, m_uiData.contentRect(), - m_uiData.pageRect(), m_uiData.isPageDetectionEnabled()); - ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); - - QObject::connect(view, SIGNAL(manualContentRectSet(const QRectF&)), opt_widget, - SLOT(manualContentRectSet(const QRectF&))); - QObject::connect(view, SIGNAL(manualPageRectSet(const QRectF&)), opt_widget, - SLOT(manualPageRectSet(const QRectF&))); - QObject::connect(view, SIGNAL(pageRectSizeChanged(const QSizeF&)), opt_widget, - SLOT(updatePageRectSize(const QSizeF&))); - QObject::connect(opt_widget, SIGNAL(pageRectChangedLocally(const QRectF&)), view, - SLOT(pageRectSetExternally(const QRectF&))); + // This function is executed from the GUI thread. + OptionsWidget* const opt_widget = m_filter->optionsWidget(); + opt_widget->postUpdateUI(m_uiData); + ui->setOptionsWidget(opt_widget, ui->KEEP_OWNERSHIP); + + ui->invalidateThumbnail(m_pageId); + + if (m_batchProcessing) { + return; + } + + auto* view = new ImageView(m_image, m_downscaledImage, m_grayImage, m_xform, m_uiData.contentRect(), + m_uiData.pageRect(), m_uiData.pageDetectionMode() != MODE_DISABLED); + ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_dbg.get()); + + QObject::connect(view, SIGNAL(manualContentRectSet(const QRectF&)), opt_widget, + SLOT(manualContentRectSet(const QRectF&))); + QObject::connect(view, SIGNAL(manualPageRectSet(const QRectF&)), opt_widget, SLOT(manualPageRectSet(const QRectF&))); + QObject::connect(view, SIGNAL(pageRectSizeChanged(const QSizeF&)), opt_widget, + SLOT(updatePageRectSize(const QSizeF&))); + QObject::connect(opt_widget, SIGNAL(pageRectChangedLocally(const QRectF&)), view, + SLOT(pageRectSetExternally(const QRectF&))); + QObject::connect(opt_widget, SIGNAL(pageRectStateChanged(bool)), view, SLOT(setPageRectEnabled(bool))); } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/Task.h b/filters/select_content/Task.h index e6162b32b..f1e012f30 100644 --- a/filters/select_content/Task.h +++ b/filters/select_content/Task.h @@ -19,13 +19,13 @@ #ifndef SELECT_CONTENT_TASK_H_ #define SELECT_CONTENT_TASK_H_ -#include "NonCopyable.h" -#include "ref_countable.h" -#include "FilterResult.h" -#include "PageId.h" -#include #include +#include #include +#include "FilterResult.h" +#include "NonCopyable.h" +#include "PageId.h" +#include "ref_countable.h" class TaskStatus; class FilterData; @@ -42,29 +42,29 @@ class Filter; class Settings; class Task : public ref_countable { - DECLARE_NON_COPYABLE(Task) + DECLARE_NON_COPYABLE(Task) -public: - Task(intrusive_ptr filter, - intrusive_ptr next_task, - intrusive_ptr settings, - const PageId& page_id, - bool batch, - bool debug); + public: + Task(intrusive_ptr filter, + intrusive_ptr next_task, + intrusive_ptr settings, + const PageId& page_id, + bool batch, + bool debug); - ~Task() override; + ~Task() override; - FilterResultPtr process(const TaskStatus& status, const FilterData& data); + FilterResultPtr process(const TaskStatus& status, const FilterData& data); -private: - class UiUpdater; + private: + class UiUpdater; - intrusive_ptr m_ptrFilter; - intrusive_ptr m_ptrNextTask; - intrusive_ptr m_ptrSettings; - std::unique_ptr m_ptrDbg; - PageId m_pageId; - bool m_batchProcessing; + intrusive_ptr m_filter; + intrusive_ptr m_nextTask; + intrusive_ptr m_settings; + std::unique_ptr m_dbg; + PageId m_pageId; + bool m_batchProcessing; }; } // namespace select_content #endif // ifndef SELECT_CONTENT_TASK_H_ diff --git a/filters/select_content/Thumbnail.cpp b/filters/select_content/Thumbnail.cpp index 95f6dc59b..944ffebb0 100644 --- a/filters/select_content/Thumbnail.cpp +++ b/filters/select_content/Thumbnail.cpp @@ -29,50 +29,49 @@ Thumbnail::Thumbnail(intrusive_ptr thumbnail_cache, const QRectF& page_rect, bool page_rect_enabled, bool deviant) - : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), - m_contentRect(content_rect), - m_pageRect(page_rect), - m_pageRectEnabled(page_rect_enabled), - m_deviant(deviant) { -} + : ThumbnailBase(std::move(thumbnail_cache), max_size, image_id, xform), + m_contentRect(content_rect), + m_pageRect(page_rect), + m_pageRectEnabled(page_rect_enabled), + m_deviant(deviant) {} void Thumbnail::paintOverImage(QPainter& painter, const QTransform& image_to_display, const QTransform& thumb_to_display) { - if (!m_contentRect.isNull()) { - QRectF page_rect(virtToThumb().mapRect(m_pageRect)); + if (!m_contentRect.isNull()) { + QRectF page_rect(virtToThumb().mapRect(m_pageRect)); - painter.setRenderHint(QPainter::Antialiasing, false); + painter.setRenderHint(QPainter::Antialiasing, false); - if (m_pageRectEnabled) { - QPen pen(QColor(0xff, 0x7f, 0x00)); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); + if (m_pageRectEnabled) { + QPen pen(QColor(0xff, 0x7f, 0x00)); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); - painter.setBrush(Qt::NoBrush); + painter.setBrush(Qt::NoBrush); - painter.drawRect(page_rect); - } + painter.drawRect(page_rect); + } - QPen pen(QColor(0x00, 0x00, 0xff)); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); + QPen pen(QColor(0x00, 0x00, 0xff)); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); - painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); + painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); - QRectF content_rect(virtToThumb().mapRect(m_contentRect)); + QRectF content_rect(virtToThumb().mapRect(m_contentRect)); - // Adjust to compensate for pen width. - content_rect.adjust(-1, -1, 1, 1); - content_rect = content_rect.intersected(page_rect); + // Adjust to compensate for pen width. + content_rect.adjust(-1, -1, 1, 1); + content_rect = content_rect.intersected(page_rect); - painter.drawRect(content_rect); - } + painter.drawRect(content_rect); + } - if (m_deviant) { - paintDeviant(painter); - } + if (m_deviant) { + paintDeviant(painter); + } } } // namespace select_content \ No newline at end of file diff --git a/filters/select_content/Thumbnail.h b/filters/select_content/Thumbnail.h index 725175aa4..f2079982b 100644 --- a/filters/select_content/Thumbnail.h +++ b/filters/select_content/Thumbnail.h @@ -19,8 +19,8 @@ #ifndef SELECT_CONTENT_THUMBNAIL_H_ #define SELECT_CONTENT_THUMBNAIL_H_ -#include "ThumbnailBase.h" #include +#include "ThumbnailBase.h" class QSizeF; class ThumbnailPixmapCache; @@ -29,25 +29,25 @@ class ImageTransformation; namespace select_content { class Thumbnail : public ThumbnailBase { -public: - Thumbnail(intrusive_ptr thumbnail_cache, - const QSizeF& max_size, - const ImageId& image_id, - const ImageTransformation& xform, - const QRectF& content_rect, - const QRectF& page_rect, - bool page_rect_enabled, - bool deviant); - - void paintOverImage(QPainter& painter, - const QTransform& image_to_display, - const QTransform& thumb_to_display) override; - -private: - QRectF m_contentRect; - QRectF m_pageRect; - bool m_pageRectEnabled; - bool m_deviant; + public: + Thumbnail(intrusive_ptr thumbnail_cache, + const QSizeF& max_size, + const ImageId& image_id, + const ImageTransformation& xform, + const QRectF& content_rect, + const QRectF& page_rect, + bool page_rect_enabled, + bool deviant); + + void paintOverImage(QPainter& painter, + const QTransform& image_to_display, + const QTransform& thumb_to_display) override; + + private: + QRectF m_contentRect; + QRectF m_pageRect; + bool m_pageRectEnabled; + bool m_deviant; }; } // namespace select_content #endif diff --git a/filters/select_content/ui/SelectContentOptionsWidget.ui b/filters/select_content/ui/SelectContentOptionsWidget.ui index fc25a9fc9..a1ede3180 100644 --- a/filters/select_content/ui/SelectContentOptionsWidget.ui +++ b/filters/select_content/ui/SelectContentOptionsWidget.ui @@ -19,6 +19,9 @@ Page Box + + Qt::AlignCenter + @@ -195,6 +198,9 @@ Content Box + + Qt::AlignCenter + diff --git a/foundation/AlignedArray.h b/foundation/AlignedArray.h index 34003eb3f..b2952ca28 100644 --- a/foundation/AlignedArray.h +++ b/foundation/AlignedArray.h @@ -19,9 +19,9 @@ #ifndef ALIGNED_ARRAY_H_ #define ALIGNED_ARRAY_H_ -#include "NonCopyable.h" #include #include +#include "NonCopyable.h" /** * \brief An array of elements starting at address with a specified alignment. @@ -29,69 +29,58 @@ * The alignment is specified not in terms of bytes, but in terms of units, * where bytes = units * sizeof(T) */ -template +template class AlignedArray { - DECLARE_NON_COPYABLE(AlignedArray) + DECLARE_NON_COPYABLE(AlignedArray) -public: - /** - * \brief Constructs a null array. - */ - AlignedArray() : m_pAlignedData(0), m_pStorage(0) { - } + public: + /** + * \brief Constructs a null array. + */ + AlignedArray() : m_alignedData(0), m_storage(0) {} - explicit AlignedArray(size_t size); + explicit AlignedArray(size_t size); - ~AlignedArray() { - delete[] m_pStorage; - } + ~AlignedArray() { delete[] m_storage; } - T* data() { - return m_pAlignedData; - } + T* data() { return m_alignedData; } - const T* data() const { - return m_pAlignedData; - } + const T* data() const { return m_alignedData; } - T& operator[](size_t idx) { - return m_pAlignedData[idx]; - } + T& operator[](size_t idx) { return m_alignedData[idx]; } - const T& operator[](size_t idx) const { - return m_pAlignedData[idx]; - } + const T& operator[](size_t idx) const { return m_alignedData[idx]; } - void swap(AlignedArray& other); + void swap(AlignedArray& other); -private: - T* m_pAlignedData; - T* m_pStorage; + private: + T* m_alignedData; + T* m_storage; }; -template +template inline void swap(AlignedArray& o1, AlignedArray& o2) { - o1.swap(o2); + o1.swap(o2); } -template +template AlignedArray::AlignedArray(size_t size) { - const int a = static_cast(alignment_in_units > 1 ? alignment_in_units : 1); - const int am1 = a - 1; - m_pStorage = new T[size + am1]; - m_pAlignedData = m_pStorage + ((a - ((uintptr_t(m_pStorage) / sizeof(T)) & am1)) & am1); + const int a = static_cast(alignment_in_units > 1 ? alignment_in_units : 1); + const int am1 = a - 1; + m_storage = new T[size + am1]; + m_alignedData = m_storage + ((a - ((uintptr_t(m_storage) / sizeof(T)) & am1)) & am1); } -template +template void AlignedArray::swap(AlignedArray& other) { - T* temp = m_pAlignedData; - m_pAlignedData = other.m_pAlignedData; - other.m_pAlignedData = temp; + T* temp = m_alignedData; + m_alignedData = other.m_alignedData; + other.m_alignedData = temp; - temp = m_pStorage; - m_pStorage = other.m_pStorage; - other.m_pStorage = temp; + temp = m_storage; + m_storage = other.m_storage; + other.m_storage = temp; } #endif // ifndef ALIGNED_ARRAY_H_ diff --git a/foundation/AutoRemovingFile.cpp b/foundation/AutoRemovingFile.cpp index c23225cfc..2cd1eeaf5 100644 --- a/foundation/AutoRemovingFile.cpp +++ b/foundation/AutoRemovingFile.cpp @@ -21,46 +21,43 @@ AutoRemovingFile::AutoRemovingFile() = default; -AutoRemovingFile::AutoRemovingFile(const QString& file_path) : m_file(file_path) { -} +AutoRemovingFile::AutoRemovingFile(const QString& file_path) : m_file(file_path) {} -AutoRemovingFile::AutoRemovingFile(AutoRemovingFile& other) : m_file(other.release()) { -} +AutoRemovingFile::AutoRemovingFile(AutoRemovingFile& other) : m_file(other.release()) {} -AutoRemovingFile::AutoRemovingFile(CopyHelper other) : m_file(other.obj->release()) { -} +AutoRemovingFile::AutoRemovingFile(CopyHelper other) : m_file(other.obj->release()) {} AutoRemovingFile::~AutoRemovingFile() { - if (!m_file.isEmpty()) { - QFile::remove(m_file); - } + if (!m_file.isEmpty()) { + QFile::remove(m_file); + } } AutoRemovingFile& AutoRemovingFile::operator=(AutoRemovingFile& other) { - m_file = other.release(); + m_file = other.release(); - return *this; + return *this; } AutoRemovingFile& AutoRemovingFile::operator=(CopyHelper other) { - m_file = other.obj->release(); + m_file = other.obj->release(); - return *this; + return *this; } void AutoRemovingFile::reset(const QString& file) { - const QString& old_file(file); + const QString& old_file(file); - m_file = file; + m_file = file; - if (!old_file.isEmpty()) { - QFile::remove(old_file); - } + if (!old_file.isEmpty()) { + QFile::remove(old_file); + } } QString AutoRemovingFile::release() { - QString saved(m_file); - m_file = QString(); + QString saved(m_file); + m_file = QString(); - return saved; + return saved; } diff --git a/foundation/AutoRemovingFile.h b/foundation/AutoRemovingFile.h index 5db16731e..e9cb0edfb 100644 --- a/foundation/AutoRemovingFile.h +++ b/foundation/AutoRemovingFile.h @@ -28,43 +28,38 @@ * this class deletes a file. unique_ptr's copying semantics is also preserved. */ class AutoRemovingFile { -private: - struct CopyHelper { - AutoRemovingFile* obj; + private: + struct CopyHelper { + AutoRemovingFile* obj; - explicit CopyHelper(AutoRemovingFile* o) : obj(o) { - } - }; + explicit CopyHelper(AutoRemovingFile* o) : obj(o) {} + }; -public: - AutoRemovingFile(); + public: + AutoRemovingFile(); - explicit AutoRemovingFile(const QString& file_path); + explicit AutoRemovingFile(const QString& file_path); - AutoRemovingFile(AutoRemovingFile& other); + AutoRemovingFile(AutoRemovingFile& other); - AutoRemovingFile(CopyHelper other); + AutoRemovingFile(CopyHelper other); - ~AutoRemovingFile(); + ~AutoRemovingFile(); - AutoRemovingFile& operator=(AutoRemovingFile& other); + AutoRemovingFile& operator=(AutoRemovingFile& other); - AutoRemovingFile& operator=(CopyHelper other); + AutoRemovingFile& operator=(CopyHelper other); - operator CopyHelper() { - return CopyHelper(this); - } + operator CopyHelper() { return CopyHelper(this); } - const QString& get() const { - return m_file; - } + const QString& get() const { return m_file; } - void reset(const QString& file); + void reset(const QString& file); - QString release(); + QString release(); -private: - QString m_file; + private: + QString m_file; }; diff --git a/foundation/CMakeLists.txt b/foundation/CMakeLists.txt index c8d651303..008546acb 100644 --- a/foundation/CMakeLists.txt +++ b/foundation/CMakeLists.txt @@ -1,36 +1,36 @@ -PROJECT("Foundation library") +project("Foundation library") -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_BINARY_DIR}") -SET( - sources - NonCopyable.h intrusive_ptr.h ref_countable.h - AlignedArray.h - FastQueue.h - SafeDeletingQObjectPtr.h - ScopedIncDec.h ScopedDecInc.h - Span.h VirtualFunction.h FlagOps.h - AutoRemovingFile.cpp AutoRemovingFile.h - Proximity.cpp Proximity.h - Property.h - PropertyFactory.cpp PropertyFactory.h - PropertySet.cpp PropertySet.h - PerformanceTimer.cpp PerformanceTimer.h - QtSignalForwarder.cpp QtSignalForwarder.h - GridLineTraverser.cpp GridLineTraverser.h - StaticPool.h - DynamicPool.h - NumericTraits.h - VecNT.h - VecT.h - MatMNT.h - MatT.h - PriorityQueue.h - Grid.h - ValueConv.h - Hashes.h) -SOURCE_GROUP("Sources" FILES ${sources}) +set( + sources + NonCopyable.h intrusive_ptr.h ref_countable.h + AlignedArray.h + FastQueue.h + SafeDeletingQObjectPtr.h + ScopedIncDec.h ScopedDecInc.h + Span.h VirtualFunction.h FlagOps.h + AutoRemovingFile.cpp AutoRemovingFile.h + Proximity.cpp Proximity.h + Property.h + PropertyFactory.cpp PropertyFactory.h + PropertySet.cpp PropertySet.h + PerformanceTimer.cpp PerformanceTimer.h + QtSignalForwarder.cpp QtSignalForwarder.h + GridLineTraverser.cpp GridLineTraverser.h + StaticPool.h + DynamicPool.h + NumericTraits.h + VecNT.h + VecT.h + MatMNT.h + MatT.h + PriorityQueue.h + Grid.h + ValueConv.h + Hashes.h) +source_group("Sources" FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(foundation STATIC ${sources}) +add_library(foundation STATIC ${sources}) diff --git a/foundation/DynamicPool.h b/foundation/DynamicPool.h index a843438c7..1f0ef30b4 100644 --- a/foundation/DynamicPool.h +++ b/foundation/DynamicPool.h @@ -19,10 +19,10 @@ #ifndef DYNAMIC_POOL_H_ #define DYNAMIC_POOL_H_ -#include "NonCopyable.h" #include #include #include +#include "NonCopyable.h" /** * \brief Allocates objects from the heap. @@ -30,101 +30,97 @@ * There is no way of freeing the allocated objects * besides destroying the whole pool. */ -template +template class DynamicPool { - DECLARE_NON_COPYABLE(DynamicPool) + DECLARE_NON_COPYABLE(DynamicPool) -public: - DynamicPool() { - } + public: + DynamicPool() {} - ~DynamicPool(); + ~DynamicPool(); - /** - * \brief Allocates a sequence of objects. - * - * If T is a POD type, the returned objects are uninitialized, - * otherwise they are default-constructed. - */ - T* alloc(size_t num_elements); + /** + * \brief Allocates a sequence of objects. + * + * If T is a POD type, the returned objects are uninitialized, + * otherwise they are default-constructed. + */ + T* alloc(size_t num_elements); -private: - enum { OVERALLOCATION_FACTOR = 3 }; + private: + enum { OVERALLOCATION_FACTOR = 3 }; - /**< Allocate 3 times the requested size. */ - enum { OVERALLOCATION_LIMIT = 256 }; + /**< Allocate 3 times the requested size. */ + enum { OVERALLOCATION_LIMIT = 256 }; - /**< Don't overallocate too much. */ + /**< Don't overallocate too much. */ - struct Chunk : public boost::intrusive::list_base_hook<> { - boost::scoped_array storage; - T* pData; - size_t remainingElements; + struct Chunk : public boost::intrusive::list_base_hook<> { + boost::scoped_array storage; + T* pData; + size_t remainingElements; - Chunk() : pData(0), remainingElements(0) { - } + Chunk() : pData(0), remainingElements(0) {} - void init(boost::scoped_array& data, size_t size) { - data.swap(storage); - pData = storage.get(); - remainingElements = size; - } - }; + void init(boost::scoped_array& data, size_t size) { + data.swap(storage); + pData = storage.get(); + remainingElements = size; + } + }; - struct DeleteDisposer { - void operator()(Chunk* chunk) { - delete chunk; - } - }; + struct DeleteDisposer { + void operator()(Chunk* chunk) { delete chunk; } + }; - typedef boost::intrusive::list> ChunkList; + typedef boost::intrusive::list> ChunkList; - static size_t adviseChunkSize(size_t num_elements); + static size_t adviseChunkSize(size_t num_elements); - ChunkList m_chunkList; + ChunkList m_chunkList; }; -template +template DynamicPool::~DynamicPool() { - m_chunkList.clear_and_dispose(DeleteDisposer()); + m_chunkList.clear_and_dispose(DeleteDisposer()); } -template +template T* DynamicPool::alloc(size_t num_elements) { - Chunk* chunk = 0; + Chunk* chunk = 0; - if (!m_chunkList.empty()) { - chunk = &m_chunkList.back(); - if (chunk->remainingElements < num_elements) { - chunk = 0; - } + if (!m_chunkList.empty()) { + chunk = &m_chunkList.back(); + if (chunk->remainingElements < num_elements) { + chunk = 0; } - - if (!chunk) { - // Create a new chunk. - const size_t chunk_size = adviseChunkSize(num_elements); - boost::scoped_array data(new T[chunk_size]); - chunk = &*m_chunkList.insert(m_chunkList.end(), *new Chunk); - chunk->init(data, chunk_size); - } - - // Allocate from chunk. - T* data = chunk->pData; - chunk->pData += num_elements; - chunk->remainingElements -= num_elements; - - return data; + } + + if (!chunk) { + // Create a new chunk. + const size_t chunk_size = adviseChunkSize(num_elements); + boost::scoped_array data(new T[chunk_size]); + chunk = &*m_chunkList.insert(m_chunkList.end(), *new Chunk); + chunk->init(data, chunk_size); + } + + // Allocate from chunk. + T* data = chunk->pData; + chunk->pData += num_elements; + chunk->remainingElements -= num_elements; + + return data; } -template +template size_t DynamicPool::adviseChunkSize(size_t num_elements) { - size_t factor = OVERALLOCATION_LIMIT / num_elements; - if (factor > (size_t) OVERALLOCATION_FACTOR) { - factor = OVERALLOCATION_FACTOR; - } + size_t factor = OVERALLOCATION_LIMIT / num_elements; + if (factor > (size_t) OVERALLOCATION_FACTOR) { + factor = OVERALLOCATION_FACTOR; + } - return num_elements * (factor + 1); + return num_elements * (factor + 1); } #endif // ifndef DYNAMIC_POOL_H_ diff --git a/foundation/FastQueue.h b/foundation/FastQueue.h index 60e9b2010..07407f39c 100644 --- a/foundation/FastQueue.h +++ b/foundation/FastQueue.h @@ -19,156 +19,145 @@ #ifndef FAST_QUEUE_H_ #define FAST_QUEUE_H_ -#include "NonCopyable.h" +#include #include #include -#include +#include #include #include -#include +#include "NonCopyable.h" -template +template class FastQueue { -public: - FastQueue() : m_chunkCapacity(defaultChunkCapacity()) { - } + public: + FastQueue() : m_chunkCapacity(defaultChunkCapacity()) {} - FastQueue(const FastQueue& other); + FastQueue(const FastQueue& other); - ~FastQueue() { - m_chunkList.clear_and_dispose(ChunkDisposer()); - } + ~FastQueue() { m_chunkList.clear_and_dispose(ChunkDisposer()); } + + FastQueue& operator=(const FastQueue& other); + + const bool empty() const { return m_chunkList.empty(); } + + T& front() { return *m_chunkList.front().pBegin; } - FastQueue& operator=(const FastQueue& other); + const T& front() const { return *m_chunkList.front().pBegin; } - const bool empty() const { - return m_chunkList.empty(); + void push(const T& t); + + void pop(); + + void swap(FastQueue& other); + + private: + struct Chunk : public boost::intrusive::list_base_hook<> { + DECLARE_NON_COPYABLE(Chunk) + + public: + explicit Chunk(size_t capacity) { + const uintptr_t p = (uintptr_t)(this + 1); + const size_t alignment = boost::alignment_of::value; + pBegin = (T*) (((p + alignment - 1) / alignment) * alignment); + pEnd = pBegin; + pBufferEnd = pBegin + capacity; + assert(size_t((char*) pBufferEnd - (char*) this) <= storageRequirement(capacity)); } - T& front() { - return *m_chunkList.front().pBegin; + ~Chunk() { + for (; pBegin != pEnd; ++pBegin) { + pBegin->~T(); + } } - const T& front() const { - return *m_chunkList.front().pBegin; + static size_t storageRequirement(size_t capacity) { + return sizeof(Chunk) + boost::alignment_of::value - 1 + capacity * sizeof(T); } - void push(const T& t); - - void pop(); - - void swap(FastQueue& other); - -private: - struct Chunk : public boost::intrusive::list_base_hook<> { - DECLARE_NON_COPYABLE(Chunk) - - public: - explicit Chunk(size_t capacity) { - const uintptr_t p = (uintptr_t)(this + 1); - const size_t alignment = boost::alignment_of::value; - pBegin = (T*) (((p + alignment - 1) / alignment) * alignment); - pEnd = pBegin; - pBufferEnd = pBegin + capacity; - assert(size_t((char*) pBufferEnd - (char*) this) <= storageRequirement(capacity)); - } - - ~Chunk() { - for (; pBegin != pEnd; ++pBegin) { - pBegin->~T(); - } - } - - static size_t storageRequirement(size_t capacity) { - return sizeof(Chunk) + boost::alignment_of::value - 1 + capacity * sizeof(T); - } - - T* pBegin; - T* pEnd; - T* pBufferEnd; - // An implicit array of T follows. - }; - - struct ChunkDisposer { - void operator()(Chunk* chunk) { - chunk->~Chunk(); - delete[](char*) chunk; - } - }; - - typedef boost::intrusive::list> ChunkList; - - static size_t defaultChunkCapacity() { - return (sizeof(T) >= 4096) ? 1 : 4096 / sizeof(T); + T* pBegin; + T* pEnd; + T* pBufferEnd; + // An implicit array of T follows. + }; + + struct ChunkDisposer { + void operator()(Chunk* chunk) { + chunk->~Chunk(); + delete[](char*) chunk; } + }; - ChunkList m_chunkList; - size_t m_chunkCapacity; + typedef boost::intrusive::list> ChunkList; + + static size_t defaultChunkCapacity() { return (sizeof(T) >= 4096) ? 1 : 4096 / sizeof(T); } + + ChunkList m_chunkList; + size_t m_chunkCapacity; }; -template +template FastQueue::FastQueue(const FastQueue& other) : m_chunkCapacity(other.m_chunkCapacity) { - for (Chunk& chunk : other.m_chunkList) { - for (const T* obj = chunk->pBegin; obj != chunk->pEnd; ++obj) { - push(*obj); - } + for (Chunk& chunk : other.m_chunkList) { + for (const T* obj = chunk->pBegin; obj != chunk->pEnd; ++obj) { + push(*obj); } + } } -template +template FastQueue& FastQueue::operator=(const FastQueue& other) { - FastQueue(other).swap(*this); + FastQueue(other).swap(*this); - return *this; + return *this; } -template +template void FastQueue::push(const T& t) { - Chunk* chunk = 0; + Chunk* chunk = 0; - if (!m_chunkList.empty()) { - chunk = &m_chunkList.back(); - if (chunk->pEnd == chunk->pBufferEnd) { - chunk = 0; - } + if (!m_chunkList.empty()) { + chunk = &m_chunkList.back(); + if (chunk->pEnd == chunk->pBufferEnd) { + chunk = 0; } - - if (!chunk) { - // Create a new chunk. - char* buf = new char[Chunk::storageRequirement(m_chunkCapacity)]; - chunk = new (buf) Chunk(m_chunkCapacity); - m_chunkList.push_back(*chunk); - } - // Push to chunk. - new (chunk->pEnd) T(t); - ++chunk->pEnd; + } + + if (!chunk) { + // Create a new chunk. + char* buf = new char[Chunk::storageRequirement(m_chunkCapacity)]; + chunk = new (buf) Chunk(m_chunkCapacity); + m_chunkList.push_back(*chunk); + } + // Push to chunk. + new (chunk->pEnd) T(t); + ++chunk->pEnd; } -template +template void FastQueue::pop() { - assert(!empty()); - - Chunk* chunk = &m_chunkList.front(); - chunk->pBegin->~T(); - ++chunk->pBegin; - if (chunk->pBegin == chunk->pEnd) { - m_chunkList.pop_front(); - ChunkDisposer()(chunk); - } + assert(!empty()); + + Chunk* chunk = &m_chunkList.front(); + chunk->pBegin->~T(); + ++chunk->pBegin; + if (chunk->pBegin == chunk->pEnd) { + m_chunkList.pop_front(); + ChunkDisposer()(chunk); + } } -template +template void FastQueue::swap(FastQueue& other) { - m_chunkList.swap(other.m_chunkList); - const size_t tmp = m_chunkCapacity; - m_chunkCapacity = other.m_chunkCapacity; - other.m_chunkCapacity = tmp; + m_chunkList.swap(other.m_chunkList); + const size_t tmp = m_chunkCapacity; + m_chunkCapacity = other.m_chunkCapacity; + other.m_chunkCapacity = tmp; } -template +template inline void swap(FastQueue& o1, FastQueue& o2) { - o1.swap(o2); + o1.swap(o2); } #endif // ifndef FAST_QUEUE_H_ diff --git a/foundation/FlagOps.h b/foundation/FlagOps.h index 5151c2026..5e3c01486 100644 --- a/foundation/FlagOps.h +++ b/foundation/FlagOps.h @@ -23,36 +23,28 @@ #include #endif -#define DEFINE_FLAG_OPS(type) \ - inline type operator&(type lhs, type rhs) { \ - return type(unsigned(lhs) & unsigned(rhs)); \ - } \ - \ - inline type operator|(type lhs, type rhs) { \ - return type(unsigned(lhs) | unsigned(rhs)); \ - } \ - \ - inline type operator^(type lhs, type rhs) { \ - return type(unsigned(lhs) ^ unsigned(rhs)); \ - } \ - \ - inline type operator~(type val) { \ - return type(~unsigned(val)); \ - } \ - \ - inline type& operator&=(type& lhs, type rhs) { \ - lhs = lhs & rhs; \ - return lhs; \ - } \ - \ - inline type& operator|=(type& lhs, type rhs) { \ - lhs = lhs | rhs; \ - return lhs; \ - } \ - \ - inline type& operator^=(type& lhs, type rhs) { \ - lhs = lhs ^ rhs; \ - return lhs; \ - } +#define DEFINE_FLAG_OPS(type) \ + inline type operator&(type lhs, type rhs) { return type(unsigned(lhs) & unsigned(rhs)); } \ + \ + inline type operator|(type lhs, type rhs) { return type(unsigned(lhs) | unsigned(rhs)); } \ + \ + inline type operator^(type lhs, type rhs) { return type(unsigned(lhs) ^ unsigned(rhs)); } \ + \ + inline type operator~(type val) { return type(~unsigned(val)); } \ + \ + inline type& operator&=(type& lhs, type rhs) { \ + lhs = lhs & rhs; \ + return lhs; \ + } \ + \ + inline type& operator|=(type& lhs, type rhs) { \ + lhs = lhs | rhs; \ + return lhs; \ + } \ + \ + inline type& operator^=(type& lhs, type rhs) { \ + lhs = lhs ^ rhs; \ + return lhs; \ + } #endif // ifndef FLAGOPS_H_ diff --git a/foundation/Grid.h b/foundation/Grid.h index acc82bcb8..2b634a81d 100644 --- a/foundation/Grid.h +++ b/foundation/Grid.h @@ -21,195 +21,175 @@ #include -template +template class Grid { -public: - /** - * Creates a null grid. - */ - Grid(); - - /** - * \brief Creates a width x height grid with specified padding on each side. - */ - Grid(int width, int height, int padding); - - /** - * \brief Creates a deep copy of another grid including padding. - * - * Stride is also preserved. - */ - Grid(const Grid& other); - - bool isNull() const { - return m_width <= 0 || m_height <= 0; - } - - void initPadding(const Node& padding_node); - - void initInterior(const Node& interior_node); - - /** - * \brief Returns a pointer to the beginning of unpadded data. - */ - Node* data() { - return m_pData; - } - - /** - * \brief Returns a pointer to the beginning of unpadded data. - */ - const Node* data() const { - return m_pData; - } - - /** - * \brief Returns a pointer to the beginning of padded data. - */ - Node* paddedData() { - return m_storage.get(); - } - - /** - * \brief Returns a pointer to the beginning of padded data. - */ - const Node* paddedData() const { - return m_storage.get(); - } - - /** - * Returns the number of nodes in a row, including padding nodes. - */ - int stride() const { - return m_stride; - } - - /** - * Returns the number of nodes in a row, excluding padding nodes. - */ - int width() const { - return m_width; - } - - /** - * Returns the number of nodes in a column, excluding padding nodes. - */ - int height() const { - return m_height; - } - - /** - * Returns the number of padding layers from each side. - */ - int padding() const { - return m_padding; - } - - void swap(Grid& other); - -private: - template - static void basicSwap(T& o1, T& o2) { - // Just to avoid incoduing the heavy header. - T tmp(o1); - o1 = o2; - o2 = tmp; - } - - boost::scoped_array m_storage; - Node* m_pData; - int m_width; - int m_height; - int m_stride; - int m_padding; + public: + /** + * Creates a null grid. + */ + Grid(); + + /** + * \brief Creates a width x height grid with specified padding on each side. + */ + Grid(int width, int height, int padding); + + /** + * \brief Creates a deep copy of another grid including padding. + * + * Stride is also preserved. + */ + Grid(const Grid& other); + + bool isNull() const { return m_width <= 0 || m_height <= 0; } + + void initPadding(const Node& padding_node); + + void initInterior(const Node& interior_node); + + /** + * \brief Returns a pointer to the beginning of unpadded data. + */ + Node* data() { return m_data; } + + /** + * \brief Returns a pointer to the beginning of unpadded data. + */ + const Node* data() const { return m_data; } + + /** + * \brief Returns a pointer to the beginning of padded data. + */ + Node* paddedData() { return m_storage.get(); } + + /** + * \brief Returns a pointer to the beginning of padded data. + */ + const Node* paddedData() const { return m_storage.get(); } + + /** + * Returns the number of nodes in a row, including padding nodes. + */ + int stride() const { return m_stride; } + + /** + * Returns the number of nodes in a row, excluding padding nodes. + */ + int width() const { return m_width; } + + /** + * Returns the number of nodes in a column, excluding padding nodes. + */ + int height() const { return m_height; } + + /** + * Returns the number of padding layers from each side. + */ + int padding() const { return m_padding; } + + void swap(Grid& other); + + private: + template + static void basicSwap(T& o1, T& o2) { + // Just to avoid incoduing the heavy header. + T tmp(o1); + o1 = o2; + o2 = tmp; + } + + boost::scoped_array m_storage; + Node* m_data; + int m_width; + int m_height; + int m_stride; + int m_padding; }; -template -Grid::Grid() : m_pData(0), m_width(0), m_height(0), m_stride(0), m_padding(0) { -} +template +Grid::Grid() : m_data(0), m_width(0), m_height(0), m_stride(0), m_padding(0) {} -template +template Grid::Grid(int width, int height, int padding) - : m_storage(new Node[(width + padding * 2) * (height + padding * 2)]), - m_pData(m_storage.get() + (width + padding * 2) * padding + padding), - m_width(width), - m_height(height), - m_stride(width + padding * 2), - m_padding(padding) { -} - -template + : m_storage(new Node[(width + padding * 2) * (height + padding * 2)]), + m_data(m_storage.get() + (width + padding * 2) * padding + padding), + m_width(width), + m_height(height), + m_stride(width + padding * 2), + m_padding(padding) {} + +template Grid::Grid(const Grid& other) - : m_storage(new Node[(other.stride() * (other.height() + other.padding() * 2))]), - m_pData(m_storage.get() + other.stride() * other.padding() + other.padding()), - m_width(other.width()), - m_height(other.height()), - m_stride(other.stride()), - m_padding(other.padding()) { - const int len = m_stride * (m_height + m_padding * 2); - for (int i = 0; i < len; ++i) { - m_storage[i] = other.m_storage[i]; - } + : m_storage(new Node[(other.stride() * (other.height() + other.padding() * 2))]), + m_data(m_storage.get() + other.stride() * other.padding() + other.padding()), + m_width(other.width()), + m_height(other.height()), + m_stride(other.stride()), + m_padding(other.padding()) { + const int len = m_stride * (m_height + m_padding * 2); + for (int i = 0; i < len; ++i) { + m_storage[i] = other.m_storage[i]; + } } -template +template void Grid::initPadding(const Node& padding_node) { - if (m_padding == 0) { - // No padding. - return; - } + if (m_padding == 0) { + // No padding. + return; + } - Node* line = m_storage.get(); - for (int row = 0; row < m_padding; ++row) { - for (int x = 0; x < m_stride; ++x) { - line[x] = padding_node; - } - line += m_stride; + Node* line = m_storage.get(); + for (int row = 0; row < m_padding; ++row) { + for (int x = 0; x < m_stride; ++x) { + line[x] = padding_node; } + line += m_stride; + } - for (int y = 0; y < m_height; ++y) { - for (int col = 0; col < m_padding; ++col) { - line[col] = padding_node; - } - for (int col = m_stride - m_padding; col < m_stride; ++col) { - line[col] = padding_node; - } - line += m_stride; + for (int y = 0; y < m_height; ++y) { + for (int col = 0; col < m_padding; ++col) { + line[col] = padding_node; + } + for (int col = m_stride - m_padding; col < m_stride; ++col) { + line[col] = padding_node; } + line += m_stride; + } - for (int row = 0; row < m_padding; ++row) { - for (int x = 0; x < m_stride; ++x) { - line[x] = padding_node; - } - line += m_stride; + for (int row = 0; row < m_padding; ++row) { + for (int x = 0; x < m_stride; ++x) { + line[x] = padding_node; } + line += m_stride; + } } // >::initPadding -template +template void Grid::initInterior(const Node& interior_node) { - Node* line = m_pData; - for (int y = 0; y < m_height; ++y) { - for (int x = 0; x < m_width; ++x) { - line[x] = interior_node; - } - line += m_stride; + Node* line = m_data; + for (int y = 0; y < m_height; ++y) { + for (int x = 0; x < m_width; ++x) { + line[x] = interior_node; } + line += m_stride; + } } -template +template void Grid::swap(Grid& other) { - m_storage.swap(other.m_storage); - basicSwap(m_pData, other.m_pData); - basicSwap(m_width, other.m_width); - basicSwap(m_height, other.m_height); - basicSwap(m_stride, other.m_stride); - basicSwap(m_padding, other.m_padding); + m_storage.swap(other.m_storage); + basicSwap(m_data, other.m_data); + basicSwap(m_width, other.m_width); + basicSwap(m_height, other.m_height); + basicSwap(m_stride, other.m_stride); + basicSwap(m_padding, other.m_padding); } -template +template void swap(Grid& o1, Grid& o2) { - o1.swap(o2); + o1.swap(o2); } #endif // ifndef GRID_H_ diff --git a/foundation/GridLineTraverser.cpp b/foundation/GridLineTraverser.cpp index cca5205fa..1d7e9e68f 100644 --- a/foundation/GridLineTraverser.cpp +++ b/foundation/GridLineTraverser.cpp @@ -20,32 +20,32 @@ #include "LineIntersectionScalar.h" GridLineTraverser::GridLineTraverser(const QLineF& line) { - const QPoint p1(line.p1().toPoint()); - const QPoint p2(line.p2().toPoint()); - int h_spans, v_spans, num_spans; - double s1 = 0.0, s2 = 0.0; - if ((h_spans = std::abs(p1.x() - p2.x())) > (v_spans = std::abs(p1.y() - p2.y()))) { - // Major direction: horizontal. - num_spans = h_spans; - lineIntersectionScalar(line, QLineF(p1, QPoint(p1.x(), p1.y() + 1)), s1); - lineIntersectionScalar(line, QLineF(p2, QPoint(p2.x(), p2.y() + 1)), s2); - } else { - // Major direction: vertical. - num_spans = v_spans; - lineIntersectionScalar(line, QLineF(p1, QPoint(p1.x() + 1, p1.y())), s1); - lineIntersectionScalar(line, QLineF(p2, QPoint(p2.x() + 1, p2.y())), s2); - } - - m_dt = num_spans == 0 ? 0 : 1.0 / num_spans; - m_line.setP1(line.pointAt(s1)); - m_line.setP2(line.pointAt(s2)); - m_totalStops = num_spans + 1; - m_stopsDone = 0; + const QPoint p1(line.p1().toPoint()); + const QPoint p2(line.p2().toPoint()); + int h_spans, v_spans, num_spans; + double s1 = 0.0, s2 = 0.0; + if ((h_spans = std::abs(p1.x() - p2.x())) > (v_spans = std::abs(p1.y() - p2.y()))) { + // Major direction: horizontal. + num_spans = h_spans; + lineIntersectionScalar(line, QLineF(p1, QPoint(p1.x(), p1.y() + 1)), s1); + lineIntersectionScalar(line, QLineF(p2, QPoint(p2.x(), p2.y() + 1)), s2); + } else { + // Major direction: vertical. + num_spans = v_spans; + lineIntersectionScalar(line, QLineF(p1, QPoint(p1.x() + 1, p1.y())), s1); + lineIntersectionScalar(line, QLineF(p2, QPoint(p2.x() + 1, p2.y())), s2); + } + + m_dt = num_spans == 0 ? 0 : 1.0 / num_spans; + m_line.setP1(line.pointAt(s1)); + m_line.setP2(line.pointAt(s2)); + m_totalStops = num_spans + 1; + m_stopsDone = 0; } QPoint GridLineTraverser::next() { - const QPointF pt(m_line.pointAt(m_stopsDone * m_dt)); - ++m_stopsDone; + const QPointF pt(m_line.pointAt(m_stopsDone * m_dt)); + ++m_stopsDone; - return pt.toPoint(); + return pt.toPoint(); } diff --git a/foundation/GridLineTraverser.h b/foundation/GridLineTraverser.h index 68aff0791..15736a41f 100644 --- a/foundation/GridLineTraverser.h +++ b/foundation/GridLineTraverser.h @@ -27,21 +27,19 @@ * Think about drawing a line on an image. */ class GridLineTraverser { - // Member-wise copying is OK. -public: - explicit GridLineTraverser(const QLineF& line); + // Member-wise copying is OK. + public: + explicit GridLineTraverser(const QLineF& line); - bool hasNext() const { - return m_stopsDone < m_totalStops; - } + bool hasNext() const { return m_stopsDone < m_totalStops; } - QPoint next(); + QPoint next(); -private: - QLineF m_line; - double m_dt; - int m_totalStops; - int m_stopsDone; + private: + QLineF m_line; + double m_dt; + int m_totalStops; + int m_stopsDone; }; diff --git a/foundation/Hashes.h b/foundation/Hashes.h index 52e4dafab..2b1cf6e7c 100644 --- a/foundation/Hashes.h +++ b/foundation/Hashes.h @@ -5,20 +5,20 @@ #include namespace hashes { -template +template struct hash; -template<> +template <> struct hash { - std::size_t operator()(const QString& str) const noexcept { - const QChar* data = str.constData(); - std::size_t hash = 5381; - for (int i = 0; i < str.size(); ++i) { - hash = ((hash << 5) + hash) ^ ((data[i].row() << 8) | data[i].cell()); - } - - return hash; + std::size_t operator()(const QString& str) const noexcept { + const QChar* data = str.constData(); + std::size_t hash = 5381; + for (int i = 0; i < str.size(); ++i) { + hash = ((hash << 5) + hash) ^ ((data[i].row() << 8) | data[i].cell()); } + + return hash; + } }; } // namespace hashes diff --git a/foundation/MatMNT.h b/foundation/MatMNT.h index 9a10aa7d1..933c5100e 100644 --- a/foundation/MatMNT.h +++ b/foundation/MatMNT.h @@ -21,7 +21,7 @@ #include -template +template class MatMNT; typedef MatMNT<2, 2, float> Mat22f; @@ -36,87 +36,79 @@ typedef MatMNT<4, 4, double> Mat44d; * * \note The memory layout is always column-major, as that's what MatrixCalc uses. */ -template +template class MatMNT { -public: - typedef T type; - enum { ROWS = static_cast(M), COLS = static_cast(N) }; - - /** - * \brief Initializes matrix elements to T(). - */ - MatMNT(); - - /** - * \brief Construction from an array of elements of possibly different type. - * - * Conversion is done by static casts. Data elements must be in column-major order. - */ - template - explicit MatMNT(const OT* data); - - /** - * \brief Construction from a matrix of same dimensions but another type. - * - * Conversion is done by static casts. - */ - template - explicit MatMNT(const MatMNT& other); - - /** - * \brief Assignment from a matrix of same dimensions but another type. - * - * Conversion is done by static casts. - */ - template - MatMNT& operator=(const MatMNT& other); - - const T* data() const { - return m_data; - } - - T* data() { - return m_data; - } - - const T& operator()(int i, int j) const { - return m_data[i + j * M]; - } - - T& operator()(int i, int j) { - return m_data[i + j * M]; - } - -private: - T m_data[M * N]; + public: + typedef T type; + enum { ROWS = static_cast(M), COLS = static_cast(N) }; + + /** + * \brief Initializes matrix elements to T(). + */ + MatMNT(); + + /** + * \brief Construction from an array of elements of possibly different type. + * + * Conversion is done by static casts. Data elements must be in column-major order. + */ + template + explicit MatMNT(const OT* data); + + /** + * \brief Construction from a matrix of same dimensions but another type. + * + * Conversion is done by static casts. + */ + template + explicit MatMNT(const MatMNT& other); + + /** + * \brief Assignment from a matrix of same dimensions but another type. + * + * Conversion is done by static casts. + */ + template + MatMNT& operator=(const MatMNT& other); + + const T* data() const { return m_data; } + + T* data() { return m_data; } + + const T& operator()(int i, int j) const { return m_data[i + j * M]; } + + T& operator()(int i, int j) { return m_data[i + j * M]; } + + private: + T m_data[M * N]; }; -template +template MatMNT::MatMNT() { - const size_t len = ROWS * COLS; - for (size_t i = 0; i < len; ++i) { - m_data[i] = T(); - } + const size_t len = ROWS * COLS; + for (size_t i = 0; i < len; ++i) { + m_data[i] = T(); + } } -template -template +template +template MatMNT::MatMNT(const OT* data) { - const size_t len = ROWS * COLS; - for (size_t i = 0; i < len; ++i) { - m_data[i] = static_cast(data[i]); - } + const size_t len = ROWS * COLS; + for (size_t i = 0; i < len; ++i) { + m_data[i] = static_cast(data[i]); + } } -template -template +template +template MatMNT::MatMNT(const MatMNT& other) { - const OT* data = other.data(); - const size_t len = ROWS * COLS; - for (size_t i = 0; i < len; ++i) { - m_data[i] = static_cast(data[i]); - } + const OT* data = other.data(); + const size_t len = ROWS * COLS; + for (size_t i = 0; i < len; ++i) { + m_data[i] = static_cast(data[i]); + } } #endif // ifndef MAT_MNT_H_ diff --git a/foundation/MatT.h b/foundation/MatT.h index 653b1d920..b2b3b0550 100644 --- a/foundation/MatT.h +++ b/foundation/MatT.h @@ -20,249 +20,240 @@ #define MAT_T_H_ #include -#include #include +#include /** * \brief A matrix of elements of type T. * * \note The memory layout is always column-major, as that's what MatrixCalc uses. */ -template +template class MatT { -public: - typedef T type; - - /** - * \brief Constructs an empty 0x0 matrix. - */ - MatT(); - - /** - * \brief Constructs a (rows)x(cols) matrix, initializing all elements to T(). - */ - MatT(size_t rows, size_t cols); - - /** - * \brief Constructs a (rows)x(cols) matrix, initializing all elements to the provided value. - */ - MatT(size_t rows, size_t cols, T initial_value); - - /** - * \brief Construction from an array of elements of possibly different type. - * - * Conversion is done by static casts. Data elements must be in column-major order. - */ - template - explicit MatT(size_t rows, size_t cols, const OT* data); - - /** - * Ordinary copy-construction. - */ - MatT(const MatT& other); - - /** - * \brief Construction from a matrix of a different type. - * - * Conversion is done by static casts. - */ - template - explicit MatT(const MatT& other); - - /** - * \brief Ordinary assignment. - */ - MatT& operator=(const MatT& other); - - /** - * \brief Assignment from a matrix of a different type. - * - * Conversion is done by static casts. - */ - template - MatT& operator=(const MatT& other); - - MatT& operator+=(const MatT& rhs); - - MatT& operator-=(const MatT& rhs); - - MatT& operator*=(T scalar); - - size_t rows() const { - return m_rows; - } - - size_t cols() const { - return m_cols; - } - - const T* data() const { - return m_data.get(); - } - - T* data() { - return m_data.get(); - } - - const T& operator()(size_t row, size_t col) const { - assert(row < m_rows && col < m_cols); - - return m_data[row + col * m_rows]; - } - - T& operator()(size_t row, size_t col) { - assert(row < m_rows && col < m_cols); - - return m_data[row + col * m_rows]; - } - - void fill(const T& value); - - void swap(MatT& other); - -private: - size_t m_rows; - size_t m_cols; - boost::scoped_array m_data; + public: + typedef T type; + + /** + * \brief Constructs an empty 0x0 matrix. + */ + MatT(); + + /** + * \brief Constructs a (rows)x(cols) matrix, initializing all elements to T(). + */ + MatT(size_t rows, size_t cols); + + /** + * \brief Constructs a (rows)x(cols) matrix, initializing all elements to the provided value. + */ + MatT(size_t rows, size_t cols, T initial_value); + + /** + * \brief Construction from an array of elements of possibly different type. + * + * Conversion is done by static casts. Data elements must be in column-major order. + */ + template + explicit MatT(size_t rows, size_t cols, const OT* data); + + /** + * Ordinary copy-construction. + */ + MatT(const MatT& other); + + /** + * \brief Construction from a matrix of a different type. + * + * Conversion is done by static casts. + */ + template + explicit MatT(const MatT& other); + + /** + * \brief Ordinary assignment. + */ + MatT& operator=(const MatT& other); + + /** + * \brief Assignment from a matrix of a different type. + * + * Conversion is done by static casts. + */ + template + MatT& operator=(const MatT& other); + + MatT& operator+=(const MatT& rhs); + + MatT& operator-=(const MatT& rhs); + + MatT& operator*=(T scalar); + + size_t rows() const { return m_rows; } + + size_t cols() const { return m_cols; } + + const T* data() const { return m_data.get(); } + + T* data() { return m_data.get(); } + + const T& operator()(size_t row, size_t col) const { + assert(row < m_rows && col < m_cols); + + return m_data[row + col * m_rows]; + } + + T& operator()(size_t row, size_t col) { + assert(row < m_rows && col < m_cols); + + return m_data[row + col * m_rows]; + } + + void fill(const T& value); + + void swap(MatT& other); + + private: + size_t m_rows; + size_t m_cols; + boost::scoped_array m_data; }; -template -MatT::MatT() : m_rows(0), m_cols(0) { -} +template +MatT::MatT() : m_rows(0), m_cols(0) {} -template +template MatT::MatT(size_t rows, size_t cols) - : m_rows(rows), - m_cols(cols), - m_data(new T[rows * cols]()) { // The "()" will cause elements to be initialized to T(). + : m_rows(rows), + m_cols(cols), + m_data(new T[rows * cols]()) { // The "()" will cause elements to be initialized to T(). } -template +template MatT::MatT(size_t rows, size_t cols, T initial_value) : m_rows(rows), m_cols(cols), m_data(new T[rows * cols]) { - const size_t len = rows * cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] = initial_value; - } + const size_t len = rows * cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] = initial_value; + } } -template -template +template +template MatT::MatT(size_t rows, size_t cols, const OT* data) : m_rows(rows), m_cols(cols), m_data(new T[rows * cols]) { - const size_t len = rows * cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] = static_cast(data[i]); - } + const size_t len = rows * cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] = static_cast(data[i]); + } } -template +template MatT::MatT(const MatT& other) : m_rows(other.rows()), m_cols(other.cols()), m_data(new T[m_rows * m_cols]) { - const size_t len = m_rows * m_cols; - const T* other_data = other.data(); - for (size_t i = 0; i < len; ++i) { - m_data[i] = other_data[i]; - } + const size_t len = m_rows * m_cols; + const T* other_data = other.data(); + for (size_t i = 0; i < len; ++i) { + m_data[i] = other_data[i]; + } } -template -template +template +template MatT::MatT(const MatT& other) : m_rows(other.rows()), m_cols(other.cols()), m_data(new T[m_rows * m_cols]) { - const size_t len = m_rows * m_cols; - const T* other_data = other.data(); - for (size_t i = 0; i < len; ++i) { - m_data[i] = other_data[i]; - } + const size_t len = m_rows * m_cols; + const T* other_data = other.data(); + for (size_t i = 0; i < len; ++i) { + m_data[i] = other_data[i]; + } } -template +template MatT& MatT::operator=(const MatT& other) { - MatT(other).swap(*this); + MatT(other).swap(*this); - return *this; + return *this; } -template -template +template +template MatT& MatT::operator=(const MatT& other) { - MatT(other).swap(*this); + MatT(other).swap(*this); - return *this; + return *this; } -template +template MatT& MatT::operator+=(const MatT& rhs) { - assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); + assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); - const size_t len = m_rows * m_cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] += rhs.m_data[i]; - } + const size_t len = m_rows * m_cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] += rhs.m_data[i]; + } - return *this; + return *this; } -template +template MatT& MatT::operator-=(const MatT& rhs) { - assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); + assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); - const size_t len = m_rows * m_cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] -= rhs.m_data[i]; - } + const size_t len = m_rows * m_cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] -= rhs.m_data[i]; + } - return *this; + return *this; } -template +template MatT& MatT::operator*=(const T scalar) { - const size_t len = m_rows * m_cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] *= scalar; - } + const size_t len = m_rows * m_cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] *= scalar; + } - return *this; + return *this; } -template +template void MatT::fill(const T& value) { - const size_t len = m_rows * m_cols; - for (size_t i = 0; i < len; ++i) { - m_data[i] = value; - } + const size_t len = m_rows * m_cols; + for (size_t i = 0; i < len; ++i) { + m_data[i] = value; + } } -template +template void MatT::swap(MatT& other) { - size_t tmp = m_rows; - m_rows = other.m_rows; - other.m_rows = tmp; + size_t tmp = m_rows; + m_rows = other.m_rows; + other.m_rows = tmp; - tmp = m_cols; - m_cols = other.m_cols; - other.m_cols = tmp; + tmp = m_cols; + m_cols = other.m_cols; + other.m_cols = tmp; - m_data.swap(other.m_data); + m_data.swap(other.m_data); } -template +template void swap(const MatT& o1, const MatT& o2) { - o1.swap(o2); + o1.swap(o2); } -template +template MatT operator*(const MatT& mat, double scalar) { - MatT res(mat); - res *= scalar; + MatT res(mat); + res *= scalar; - return res; + return res; } -template +template MatT operator*(double scalar, const MatT& mat) { - MatT res(mat); - res *= scalar; + MatT res(mat); + res *= scalar; - return res; + return res; } #endif // ifndef MAT_T_H_ diff --git a/foundation/NonCopyable.h b/foundation/NonCopyable.h index 685c2a498..0555e2c85 100644 --- a/foundation/NonCopyable.h +++ b/foundation/NonCopyable.h @@ -23,17 +23,17 @@ #include #endif -#define DECLARE_NON_COPYABLE(Class) \ -public: \ - /** \brief Copying is forbidden. */ \ - Class(const Class&) = delete; \ - /** \brief Copying is forbidden. */ \ - Class& operator=(const Class&) = delete; \ - /** \brief Moving is forbidden. */ \ - Class(Class&&) noexcept = delete; \ - /** \brief Moving is forbidden. */ \ - Class& operator=(Class&&) noexcept = delete; \ - \ -private: +#define DECLARE_NON_COPYABLE(Class) \ + public: \ + /** \brief Copying is forbidden. */ \ + Class(const Class&) = delete; \ + /** \brief Copying is forbidden. */ \ + Class& operator=(const Class&) = delete; \ + /** \brief Moving is forbidden. */ \ + Class(Class&&) noexcept = delete; \ + /** \brief Moving is forbidden. */ \ + Class& operator=(Class&&) noexcept = delete; \ + \ + private: #endif diff --git a/foundation/NumericTraits.h b/foundation/NumericTraits.h index c4b499a83..f8082025b 100644 --- a/foundation/NumericTraits.h +++ b/foundation/NumericTraits.h @@ -22,7 +22,7 @@ #include namespace numeric_traits_impl { -template +template struct IntegerSpecific; } // namespace numeric_traits_impl @@ -30,39 +30,31 @@ struct IntegerSpecific; * This class exists mainly because std::numeric_values<>::min() has * inconsistent behaviour for integer vs floating point types. */ -template +template class NumericTraits { -public: - static T max() { - return std::numeric_limits::max(); - } + public: + static T max() { return std::numeric_limits::max(); } - /** - * This one behaves as you expect, not as std::numeric_limits::min(). - * That is, this one will actually give you a negative value both for - * integer and floating point types. - */ - static T min() { - return numeric_traits_impl::IntegerSpecific::is_integer>::min(); - } + /** + * This one behaves as you expect, not as std::numeric_limits::min(). + * That is, this one will actually give you a negative value both for + * integer and floating point types. + */ + static T min() { return numeric_traits_impl::IntegerSpecific::is_integer>::min(); } -private: + private: }; namespace numeric_traits_impl { -template +template struct IntegerSpecific { - static T min() { - return std::numeric_limits::min(); - } + static T min() { return std::numeric_limits::min(); } }; -template +template struct IntegerSpecific { - static T min() { - return -std::numeric_limits::max(); - } + static T min() { return -std::numeric_limits::max(); } }; } // namespace numeric_traits_impl #endif // ifndef NUMERIC_TRAITS_H_ diff --git a/foundation/PerformanceTimer.cpp b/foundation/PerformanceTimer.cpp index 231f1a411..589e95648 100644 --- a/foundation/PerformanceTimer.cpp +++ b/foundation/PerformanceTimer.cpp @@ -20,13 +20,13 @@ #include void PerformanceTimer::print(const char* prefix) { - const clock_t now = clock(); - const double sec = double(now - m_start) / CLOCKS_PER_SEC; - if (sec > 10.0) { - qDebug() << prefix << (long) sec << " sec"; - } else if (sec > 0.01) { - qDebug() << prefix << (long) (sec * 1000) << " msec"; - } else { - qDebug() << prefix << (long) (sec * 1000000) << " usec"; - } + const clock_t now = clock(); + const double sec = double(now - m_start) / CLOCKS_PER_SEC; + if (sec > 10.0) { + qDebug() << prefix << (long) sec << " sec"; + } else if (sec > 0.01) { + qDebug() << prefix << (long) (sec * 1000) << " msec"; + } else { + qDebug() << prefix << (long) (sec * 1000000) << " usec"; + } } diff --git a/foundation/PerformanceTimer.h b/foundation/PerformanceTimer.h index 3d30a3f87..201e8de95 100644 --- a/foundation/PerformanceTimer.h +++ b/foundation/PerformanceTimer.h @@ -22,14 +22,13 @@ #include class PerformanceTimer { -public: - PerformanceTimer() : m_start(clock()) { - } + public: + PerformanceTimer() : m_start(clock()) {} - void print(const char* prefix = ""); + void print(const char* prefix = ""); -private: - const clock_t m_start; + private: + const clock_t m_start; }; diff --git a/foundation/PriorityQueue.h b/foundation/PriorityQueue.h index 99e6beefe..48618f73f 100644 --- a/foundation/PriorityQueue.h +++ b/foundation/PriorityQueue.h @@ -19,10 +19,10 @@ #ifndef PRIORITY_QUEUE_H_ #define PRIORITY_QUEUE_H_ -#include #include -#include #include +#include +#include /** * \brief A priority queue implemented as a binary heap. @@ -38,225 +38,202 @@ * \endcode * function in the same namespace as T. */ -template +template class PriorityQueue { - // Member-wise copying is OK. -public: - PriorityQueue() { - } + // Member-wise copying is OK. + public: + PriorityQueue() {} - void reserve(size_t capacity) { - m_index.reserve(capacity); - } + void reserve(size_t capacity) { m_index.reserve(capacity); } - bool empty() const { - return m_index.empty(); - } + bool empty() const { return m_index.empty(); } - size_t size() const { - return m_index.size(); - } + size_t size() const { return m_index.size(); } - /** - * \brief Provides access to the head of priority queue. - * - * Modification of an object is allowed, provided your modifications don't - * affect the logical order of objects, or you will be calling reposition(), - * pop() or erase() on the modified object before any other operation that - * involves comparing objects. - */ - T& front() { - return m_index.front(); - } + /** + * \brief Provides access to the head of priority queue. + * + * Modification of an object is allowed, provided your modifications don't + * affect the logical order of objects, or you will be calling reposition(), + * pop() or erase() on the modified object before any other operation that + * involves comparing objects. + */ + T& front() { return m_index.front(); } - const T& front() const { - return m_index.front(); - } + const T& front() const { return m_index.front(); } - void push(const T& obj); + void push(const T& obj); - /** - * Like push(), but implemented through swapping \p obj with a default - * constructed instance of T. This will make sence if copying a default - * constructed instance of T is much cheaper than copying \p obj. - */ - void pushDestructive(T& obj); + /** + * Like push(), but implemented through swapping \p obj with a default + * constructed instance of T. This will make sence if copying a default + * constructed instance of T is much cheaper than copying \p obj. + */ + void pushDestructive(T& obj); - void pop(); + void pop(); - /** - * Retrieve-and-pop, implemented through swapping \p obj with the instance - * at the front of the queue. There are no special requirements to - * the state of the object being passed to this function. - */ - void retrieveFront(T& obj); + /** + * Retrieve-and-pop, implemented through swapping \p obj with the instance + * at the front of the queue. There are no special requirements to + * the state of the object being passed to this function. + */ + void retrieveFront(T& obj); - void swapWith(PriorityQueue& other) { - m_index.swap(other.m_index); - } + void swapWith(PriorityQueue& other) { m_index.swap(other.m_index); } -protected: - void erase(size_t idx); + protected: + void erase(size_t idx); - void reposition(size_t idx); + void reposition(size_t idx); -private: - static size_t parent(size_t idx) { - return (idx - 1) / 2; - } + private: + static size_t parent(size_t idx) { return (idx - 1) / 2; } - static size_t left(size_t idx) { - return idx * 2 + 1; - } + static size_t left(size_t idx) { return idx * 2 + 1; } - static size_t right(size_t idx) { - return idx * 2 + 2; - } + static size_t right(size_t idx) { return idx * 2 + 2; } - SubClass* subClass() { - return static_cast(this); - } + SubClass* subClass() { return static_cast(this); } - const SubClass* subClass() const { - return static_cast(this); - } + const SubClass* subClass() const { return static_cast(this); } - size_t bubbleUp(size_t idx); + size_t bubbleUp(size_t idx); - size_t bubbleDown(size_t idx); + size_t bubbleDown(size_t idx); - std::vector m_index; + std::vector m_index; }; -template +template inline void swap(PriorityQueue& o1, PriorityQueue& o2) { - o1.swap(o2); + o1.swap(o2); } -template +template void PriorityQueue::push(const T& obj) { - const size_t idx = m_index.size(); - m_index.push_back(obj); - subClass()->setIndex(m_index.back(), idx); - bubbleUp(idx); + const size_t idx = m_index.size(); + m_index.push_back(obj); + subClass()->setIndex(m_index.back(), idx); + bubbleUp(idx); } -template +template void PriorityQueue::pushDestructive(T& obj) { - using namespace std; + using namespace std; - const size_t idx = m_index.size(); - m_index.push_back(T()); - swap(m_index.back(), obj); - subClass()->setIndex(m_index.back(), idx); - bubbleUp(idx); + const size_t idx = m_index.size(); + m_index.push_back(T()); + swap(m_index.back(), obj); + subClass()->setIndex(m_index.back(), idx); + bubbleUp(idx); } -template +template void PriorityQueue::pop() { - using namespace std; + using namespace std; - assert(!empty()); + assert(!empty()); - swap(m_index.front(), m_index.back()); - subClass()->setIndex(m_index.front(), 0); + swap(m_index.front(), m_index.back()); + subClass()->setIndex(m_index.front(), 0); - m_index.pop_back(); - if (!empty()) { - bubbleDown(0); - } + m_index.pop_back(); + if (!empty()) { + bubbleDown(0); + } } -template +template void PriorityQueue::retrieveFront(T& obj) { - using namespace std; + using namespace std; - assert(!empty()); + assert(!empty()); - swap(m_index.front(), obj); - swap(m_index.front(), m_index.back()); - subClass()->setIndex(m_index.front(), 0); + swap(m_index.front(), obj); + swap(m_index.front(), m_index.back()); + subClass()->setIndex(m_index.front(), 0); - m_index.pop_back(); - if (!empty()) { - bubbleDown(0); - } + m_index.pop_back(); + if (!empty()) { + bubbleDown(0); + } } -template +template void PriorityQueue::erase(const size_t idx) { - using namespace std; + using namespace std; - swap(m_index[idx], m_index.back()); - subClass()->setIndex(m_index[idx], idx); + swap(m_index[idx], m_index.back()); + subClass()->setIndex(m_index[idx], idx); - m_index.pop_back(); - reposition(m_index[idx]); + m_index.pop_back(); + reposition(m_index[idx]); } -template +template void PriorityQueue::reposition(const size_t idx) { - bubbleUp(bubbleDown(idx)); + bubbleUp(bubbleDown(idx)); } -template +template size_t PriorityQueue::bubbleUp(size_t idx) { - using namespace std; - - // Iteratively swap the element with its parent, - // if it's greater than the parent. - - assert(idx < m_index.size()); - - while (idx > 0) { - const size_t parent_idx = parent(idx); - if (!subClass()->higherThan(m_index[idx], m_index[parent_idx])) { - break; - } - swap(m_index[idx], m_index[parent_idx]); - subClass()->setIndex(m_index[idx], idx); - subClass()->setIndex(m_index[parent_idx], parent_idx); - idx = parent_idx; + using namespace std; + + // Iteratively swap the element with its parent, + // if it's greater than the parent. + + assert(idx < m_index.size()); + + while (idx > 0) { + const size_t parent_idx = parent(idx); + if (!subClass()->higherThan(m_index[idx], m_index[parent_idx])) { + break; } + swap(m_index[idx], m_index[parent_idx]); + subClass()->setIndex(m_index[idx], idx); + subClass()->setIndex(m_index[parent_idx], parent_idx); + idx = parent_idx; + } - return idx; + return idx; } -template +template size_t PriorityQueue::bubbleDown(size_t idx) { - using namespace std; - - const size_t len = m_index.size(); - assert(idx < len); - - // While any child is greater than the element itself, - // swap it with the greatest child. - - for (;;) { - const size_t lft = left(idx); - const size_t rgt = right(idx); - size_t best_child; - - if (rgt < len) { - best_child = subClass()->higherThan(m_index[lft], m_index[rgt]) ? lft : rgt; - } else if (lft < len) { - best_child = lft; - } else { - break; - } - - if (subClass()->higherThan(m_index[best_child], m_index[idx])) { - swap(m_index[idx], m_index[best_child]); - subClass()->setIndex(m_index[idx], idx); - subClass()->setIndex(m_index[best_child], best_child); - idx = best_child; - } else { - break; - } + using namespace std; + + const size_t len = m_index.size(); + assert(idx < len); + + // While any child is greater than the element itself, + // swap it with the greatest child. + + while (true) { + const size_t lft = left(idx); + const size_t rgt = right(idx); + size_t best_child; + + if (rgt < len) { + best_child = subClass()->higherThan(m_index[lft], m_index[rgt]) ? lft : rgt; + } else if (lft < len) { + best_child = lft; + } else { + break; + } + + if (subClass()->higherThan(m_index[best_child], m_index[idx])) { + swap(m_index[idx], m_index[best_child]); + subClass()->setIndex(m_index[idx], idx); + subClass()->setIndex(m_index[best_child], best_child); + idx = best_child; + } else { + break; } + } - return idx; + return idx; } // >::bubbleDown #endif // ifndef PRIORITY_QUEUE_H_ diff --git a/foundation/Property.h b/foundation/Property.h index 1af3acef9..8e610c0db 100644 --- a/foundation/Property.h +++ b/foundation/Property.h @@ -19,17 +19,17 @@ #ifndef PROPERTY_H_ #define PROPERTY_H_ -#include "ref_countable.h" #include "intrusive_ptr.h" +#include "ref_countable.h" class QDomDocument; class QDomElement; class Property : public ref_countable { -public: - virtual intrusive_ptr clone() const = 0; + public: + virtual intrusive_ptr clone() const = 0; - virtual QDomElement toXml(QDomDocument& doc, const QString& name) const = 0; + virtual QDomElement toXml(QDomDocument& doc, const QString& name) const = 0; }; diff --git a/foundation/PropertyFactory.cpp b/foundation/PropertyFactory.cpp index 82516ef4d..cc7d4ac7b 100644 --- a/foundation/PropertyFactory.cpp +++ b/foundation/PropertyFactory.cpp @@ -20,14 +20,14 @@ #include void PropertyFactory::registerProperty(const QString& property, PropertyConstructor constructor) { - m_registry[property] = constructor; + m_registry[property] = constructor; } intrusive_ptr PropertyFactory::construct(const QDomElement& el) const { - auto it(m_registry.find(el.attribute("type"))); - if (it != m_registry.end()) { - return (*it->second)(el); - } else { - return nullptr; - } + auto it(m_registry.find(el.attribute("type"))); + if (it != m_registry.end()) { + return (*it->second)(el); + } else { + return nullptr; + } } diff --git a/foundation/PropertyFactory.h b/foundation/PropertyFactory.h index 296c6e22a..32b6ae9e1 100644 --- a/foundation/PropertyFactory.h +++ b/foundation/PropertyFactory.h @@ -19,28 +19,28 @@ #ifndef PROPERTY_FACTORY_H_ #define PROPERTY_FACTORY_H_ -#include "Property.h" -#include "intrusive_ptr.h" -#include "Hashes.h" #include #include +#include "Hashes.h" +#include "Property.h" +#include "intrusive_ptr.h" class QDomElement; class PropertyFactory { - // Member-wise copying is OK. -public: - virtual ~PropertyFactory() = default; + // Member-wise copying is OK. + public: + virtual ~PropertyFactory() = default; - typedef intrusive_ptr (*PropertyConstructor)(const QDomElement& el); + typedef intrusive_ptr (*PropertyConstructor)(const QDomElement& el); - void registerProperty(const QString& property, PropertyConstructor constructor); + void registerProperty(const QString& property, PropertyConstructor constructor); - intrusive_ptr construct(const QDomElement& el) const; + intrusive_ptr construct(const QDomElement& el) const; -private: - typedef std::unordered_map> Registry; - Registry m_registry; + private: + typedef std::unordered_map> Registry; + Registry m_registry; }; diff --git a/foundation/PropertySet.cpp b/foundation/PropertySet.cpp index 4ace19ed3..d4c20a8aa 100644 --- a/foundation/PropertySet.cpp +++ b/foundation/PropertySet.cpp @@ -17,54 +17,54 @@ */ #include "PropertySet.h" -#include "PropertyFactory.h" #include +#include "PropertyFactory.h" PropertySet::PropertySet(const QDomElement& el, const PropertyFactory& factory) { - const QString property_str("property"); - QDomNode node(el.firstChild()); + const QString property_str("property"); + QDomNode node(el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != property_str) { - continue; - } + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != property_str) { + continue; + } - QDomElement prop_el(node.toElement()); - intrusive_ptr prop(factory.construct(prop_el)); - if (prop) { - m_props.push_back(prop); - } + QDomElement prop_el(node.toElement()); + intrusive_ptr prop(factory.construct(prop_el)); + if (prop) { + m_props.push_back(prop); } + } } PropertySet::PropertySet(const PropertySet& other) { - m_props.reserve(other.m_props.size()); + m_props.reserve(other.m_props.size()); - for (const intrusive_ptr& prop : other.m_props) { - m_props.push_back(prop->clone()); - } + for (const intrusive_ptr& prop : other.m_props) { + m_props.push_back(prop->clone()); + } } PropertySet& PropertySet::operator=(const PropertySet& other) { - PropertySet(other).swap(*this); + PropertySet(other).swap(*this); - return *this; + return *this; } void PropertySet::swap(PropertySet& other) { - m_props.swap(other.m_props); + m_props.swap(other.m_props); } QDomElement PropertySet::toXml(QDomDocument& doc, const QString& name) const { - const QString property_str("property"); - QDomElement props_el(doc.createElement(name)); + const QString property_str("property"); + QDomElement props_el(doc.createElement(name)); - for (const intrusive_ptr& prop : m_props) { - props_el.appendChild(prop->toXml(doc, property_str)); - } + for (const intrusive_ptr& prop : m_props) { + props_el.appendChild(prop->toXml(doc, property_str)); + } - return props_el; + return props_el; } diff --git a/foundation/PropertySet.h b/foundation/PropertySet.h index 5c7499e8f..214c1e79a 100644 --- a/foundation/PropertySet.h +++ b/foundation/PropertySet.h @@ -19,10 +19,10 @@ #ifndef PROPERTY_SET_H_ #define PROPERTY_SET_H_ -#include "ref_countable.h" -#include "intrusive_ptr.h" -#include "Property.h" #include +#include "Property.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class PropertyFactory; class QDomDocument; @@ -30,118 +30,118 @@ class QDomElement; class QString; class PropertySet : public ref_countable { -public: - PropertySet() = default; - - /** - * \brief Makes a deep copy of another property set. - */ - PropertySet(const PropertySet& other); - - PropertySet(const QDomElement& el, const PropertyFactory& factory); - - /** - * \brief Makes a deep copy of another property set. - */ - PropertySet& operator=(const PropertySet& other); - - void swap(PropertySet& other); - - QDomElement toXml(QDomDocument& doc, const QString& name) const; - - /** - * Returns a property stored in this set, if one having a suitable - * type is found, or returns a null smart pointer otherwise. - */ - template - intrusive_ptr locate(); - - template - intrusive_ptr locate() const; - - /** - * Returns a property stored in this set, if one having a suitable - * type is found, or returns a default constructed object otherwise. - */ - template - intrusive_ptr locateOrDefault(); - - template - intrusive_ptr locateOrDefault() const; - - /** - * Returns a property stored in this set, if one having a suitable - * type is found. Otherwise, a default constructed object is put - * to the set and then returned. - */ - template - intrusive_ptr locateOrCreate(); - -private: - typedef std::vector> PropList; - PropList m_props; + public: + PropertySet() = default; + + /** + * \brief Makes a deep copy of another property set. + */ + PropertySet(const PropertySet& other); + + PropertySet(const QDomElement& el, const PropertyFactory& factory); + + /** + * \brief Makes a deep copy of another property set. + */ + PropertySet& operator=(const PropertySet& other); + + void swap(PropertySet& other); + + QDomElement toXml(QDomDocument& doc, const QString& name) const; + + /** + * Returns a property stored in this set, if one having a suitable + * type is found, or returns a null smart pointer otherwise. + */ + template + intrusive_ptr locate(); + + template + intrusive_ptr locate() const; + + /** + * Returns a property stored in this set, if one having a suitable + * type is found, or returns a default constructed object otherwise. + */ + template + intrusive_ptr locateOrDefault(); + + template + intrusive_ptr locateOrDefault() const; + + /** + * Returns a property stored in this set, if one having a suitable + * type is found. Otherwise, a default constructed object is put + * to the set and then returned. + */ + template + intrusive_ptr locateOrCreate(); + + private: + typedef std::vector> PropList; + PropList m_props; }; -template +template intrusive_ptr PropertySet::locate() { - PropList::iterator it(m_props.begin()); - const PropList::iterator end(m_props.end()); - for (; it != end; ++it) { - if (T* obj = dynamic_cast(it->get())) { - return intrusive_ptr(obj); - } + PropList::iterator it(m_props.begin()); + const PropList::iterator end(m_props.end()); + for (; it != end; ++it) { + if (T* obj = dynamic_cast(it->get())) { + return intrusive_ptr(obj); } + } - return nullptr; + return nullptr; } -template +template intrusive_ptr PropertySet::locate() const { - PropList::const_iterator it(m_props.begin()); - const PropList::const_iterator end(m_props.end()); - for (; it != end; ++it) { - if (const T* obj = dynamic_cast(it->get())) { - return intrusive_ptr(obj); - } + PropList::const_iterator it(m_props.begin()); + const PropList::const_iterator end(m_props.end()); + for (; it != end; ++it) { + if (const T* obj = dynamic_cast(it->get())) { + return intrusive_ptr(obj); } + } - return nullptr; + return nullptr; } -template +template intrusive_ptr PropertySet::locateOrDefault() { - intrusive_ptr obj(locate()); - if (!obj) { - obj.reset(new T); - } + intrusive_ptr obj(locate()); + if (!obj) { + obj.reset(new T); + } - return obj; + return obj; } -template +template intrusive_ptr PropertySet::locateOrDefault() const { - intrusive_ptr obj(locate()); - if (!obj) { - obj.reset(new T); - } + intrusive_ptr obj(locate()); + if (!obj) { + obj.reset(new T); + } - return obj; + return obj; } -template +template intrusive_ptr PropertySet::locateOrCreate() { - intrusive_ptr obj(locate()); - if (!obj) { - obj.reset(new T); - m_props.push_back(obj); - } + intrusive_ptr obj(locate()); + if (!obj) { + obj.reset(new T); + m_props.push_back(obj); + } - return obj; + return obj; } inline void swap(PropertySet& o1, PropertySet& o2) { - o1.swap(o2); + o1.swap(o2); } #endif // ifndef PROPERTY_SET_H_ diff --git a/foundation/Proximity.cpp b/foundation/Proximity.cpp index 796329f8d..75c9f0dec 100644 --- a/foundation/Proximity.cpp +++ b/foundation/Proximity.cpp @@ -17,61 +17,61 @@ */ #include "Proximity.h" -#include #include +#include Proximity::Proximity(const QPointF& p1, const QPointF& p2) { - const double dx = p1.x() - p2.x(); - const double dy = p1.y() - p2.y(); - m_sqDist = dx * dx + dy * dy; // dx * dy; + const double dx = p1.x() - p2.x(); + const double dy = p1.y() - p2.y(); + m_sqDist = dx * dx + dy * dy; // dx * dy; } Proximity Proximity::pointAndLineSegment(const QPointF& pt, const QLineF& segment, QPointF* point_on_segment) { - if (segment.p1() == segment.p2()) { - // Line segment is zero length. - if (point_on_segment) { - *point_on_segment = segment.p1(); - } - - return Proximity(pt, segment.p1()); + if (segment.p1() == segment.p2()) { + // Line segment is zero length. + if (point_on_segment) { + *point_on_segment = segment.p1(); } - QLineF perpendicular(segment.normalVector()); + return Proximity(pt, segment.p1()); + } - // Make the perpendicular pass through pt. - perpendicular.translate(-perpendicular.p1()); - perpendicular.translate(pt); - // Calculate intersection. - QPointF intersection; - segment.intersect(perpendicular, &intersection); + QLineF perpendicular(segment.normalVector()); - const double dx1 = segment.p1().x() - intersection.x(); - const double dy1 = segment.p1().y() - intersection.y(); - const double dx2 = segment.p2().x() - intersection.x(); - const double dy2 = segment.p2().y() - intersection.y(); - const double dx12 = dx1 * dx2; - const double dy12 = dy1 * dy2; - if ((dx12 < 0.0) || (dy12 < 0.0) || ((dx12 == 0.0) && (dy12 == 0.0))) { - // Intersection is on the segment. - if (point_on_segment) { - *point_on_segment = intersection; - } + // Make the perpendicular pass through pt. + perpendicular.translate(-perpendicular.p1()); + perpendicular.translate(pt); + // Calculate intersection. + QPointF intersection; + segment.intersect(perpendicular, &intersection); - return Proximity(intersection, pt); + const double dx1 = segment.p1().x() - intersection.x(); + const double dy1 = segment.p1().y() - intersection.y(); + const double dx2 = segment.p2().x() - intersection.x(); + const double dy2 = segment.p2().y() - intersection.y(); + const double dx12 = dx1 * dx2; + const double dy12 = dy1 * dy2; + if ((dx12 < 0.0) || (dy12 < 0.0) || ((dx12 == 0.0) && (dy12 == 0.0))) { + // Intersection is on the segment. + if (point_on_segment) { + *point_on_segment = intersection; } - Proximity prx[2]; - QPointF pts[2]; + return Proximity(intersection, pt); + } - prx[0] = Proximity(segment.p1(), pt); - prx[1] = Proximity(segment.p2(), pt); - pts[0] = segment.p1(); - pts[1] = segment.p2(); + Proximity prx[2]; + QPointF pts[2]; - const Proximity* p_min_prx = std::min_element(prx, prx + 2); - if (point_on_segment) { - *point_on_segment = pts[p_min_prx - prx]; - } + prx[0] = Proximity(segment.p1(), pt); + prx[1] = Proximity(segment.p2(), pt); + pts[0] = segment.p1(); + pts[1] = segment.p2(); + + const Proximity* min_prx = std::min_element(prx, prx + 2); + if (point_on_segment) { + *point_on_segment = pts[min_prx - prx]; + } - return *p_min_prx; + return *min_prx; } // Proximity::pointAndLineSegment diff --git a/foundation/Proximity.h b/foundation/Proximity.h index 300a8a175..f384306de 100644 --- a/foundation/Proximity.h +++ b/foundation/Proximity.h @@ -19,66 +19,44 @@ #ifndef PROXIMITY_H_ #define PROXIMITY_H_ -#include #include +#include class QPointF; class QLineF; class Proximity { -public: - Proximity() : m_sqDist(std::numeric_limits::max()) { - } + public: + Proximity() : m_sqDist(std::numeric_limits::max()) {} - Proximity(const QPointF& p1, const QPointF& p2); + Proximity(const QPointF& p1, const QPointF& p2); - static Proximity fromDist(double dist) { - return Proximity(dist * dist); - } + static Proximity fromDist(double dist) { return Proximity(dist * dist); } - static Proximity fromSqDist(double sqDist) { - return Proximity(sqDist); - } + static Proximity fromSqDist(double sqDist) { return Proximity(sqDist); } - static Proximity pointAndLineSegment(const QPointF& pt, const QLineF& segment, QPointF* point_on_segment = nullptr); + static Proximity pointAndLineSegment(const QPointF& pt, const QLineF& segment, QPointF* point_on_segment = nullptr); - double dist() const { - return std::sqrt(m_sqDist); - } + double dist() const { return std::sqrt(m_sqDist); } - double sqDist() const { - return m_sqDist; - } + double sqDist() const { return m_sqDist; } - bool operator==(const Proximity& rhs) const { - return m_sqDist == rhs.m_sqDist; - } + bool operator==(const Proximity& rhs) const { return m_sqDist == rhs.m_sqDist; } - bool operator!=(const Proximity& rhs) const { - return m_sqDist != rhs.m_sqDist; - } + bool operator!=(const Proximity& rhs) const { return m_sqDist != rhs.m_sqDist; } - bool operator<(const Proximity& rhs) const { - return m_sqDist < rhs.m_sqDist; - } + bool operator<(const Proximity& rhs) const { return m_sqDist < rhs.m_sqDist; } - bool operator>(const Proximity& rhs) const { - return m_sqDist > rhs.m_sqDist; - } + bool operator>(const Proximity& rhs) const { return m_sqDist > rhs.m_sqDist; } - bool operator<=(const Proximity& rhs) const { - return m_sqDist <= rhs.m_sqDist; - } + bool operator<=(const Proximity& rhs) const { return m_sqDist <= rhs.m_sqDist; } - bool operator>=(const Proximity& rhs) const { - return m_sqDist >= rhs.m_sqDist; - } + bool operator>=(const Proximity& rhs) const { return m_sqDist >= rhs.m_sqDist; } -private: - explicit Proximity(double sqDist) : m_sqDist(sqDist) { - } + private: + explicit Proximity(double sqDist) : m_sqDist(sqDist) {} - double m_sqDist; + double m_sqDist; }; diff --git a/foundation/QtSignalForwarder.cpp b/foundation/QtSignalForwarder.cpp index 6303d0f10..8993e2eaf 100644 --- a/foundation/QtSignalForwarder.cpp +++ b/foundation/QtSignalForwarder.cpp @@ -19,10 +19,10 @@ #include "QtSignalForwarder.h" QtSignalForwarder::QtSignalForwarder(QObject* emitter, const char* signal, const boost::function& slot) - : QObject(emitter), m_slot(slot) { - connect(emitter, signal, SLOT(handleSignal())); + : QObject(emitter), m_slot(slot) { + connect(emitter, signal, SLOT(handleSignal())); } void QtSignalForwarder::handleSignal() { - m_slot(); + m_slot(); } diff --git a/foundation/QtSignalForwarder.h b/foundation/QtSignalForwarder.h index 1dca80938..a85c039aa 100644 --- a/foundation/QtSignalForwarder.h +++ b/foundation/QtSignalForwarder.h @@ -19,9 +19,9 @@ #ifndef QT_SIGNAL_FORWARDER_H_ #define QT_SIGNAL_FORWARDER_H_ -#include "NonCopyable.h" #include #include +#include "NonCopyable.h" /** * \brief Connects to a Qt signal and forwards it to a boost::function. @@ -30,28 +30,28 @@ * at connection time. */ class QtSignalForwarder : public QObject { - Q_OBJECT - DECLARE_NON_COPYABLE(QtSignalForwarder) - -public: - /** - * \brief Constructor. - * - * \param emitter The object that will emit a signal. The forwarder - * will become its child. - * \param signal The signal specification in the form of SIGNAL(name()). - * Signals with arguments may be specified, but the arguments - * won't be forwarded. - * \param slot A boost::function to forward the signal to. - */ - QtSignalForwarder(QObject* emitter, const char* signal, const boost::function& slot); - -private slots: - - void handleSignal(); - -private: - boost::function m_slot; + Q_OBJECT + DECLARE_NON_COPYABLE(QtSignalForwarder) + + public: + /** + * \brief Constructor. + * + * \param emitter The object that will emit a signal. The forwarder + * will become its child. + * \param signal The signal specification in the form of SIGNAL(name()). + * Signals with arguments may be specified, but the arguments + * won't be forwarded. + * \param slot A boost::function to forward the signal to. + */ + QtSignalForwarder(QObject* emitter, const char* signal, const boost::function& slot); + + private slots: + + void handleSignal(); + + private: + boost::function m_slot; }; diff --git a/foundation/SafeDeletingQObjectPtr.h b/foundation/SafeDeletingQObjectPtr.h index 0a71ed371..637ab4d88 100644 --- a/foundation/SafeDeletingQObjectPtr.h +++ b/foundation/SafeDeletingQObjectPtr.h @@ -21,51 +21,42 @@ #include "NonCopyable.h" -template +template class SafeDeletingQObjectPtr { - DECLARE_NON_COPYABLE(SafeDeletingQObjectPtr) + DECLARE_NON_COPYABLE(SafeDeletingQObjectPtr) -public: - explicit SafeDeletingQObjectPtr(T* obj = 0) : m_pObj(obj) { - } + public: + explicit SafeDeletingQObjectPtr(T* obj = 0) : m_obj(obj) {} - ~SafeDeletingQObjectPtr() { - if (m_pObj) { - m_pObj->disconnect(); - m_pObj->deleteLater(); - } + ~SafeDeletingQObjectPtr() { + if (m_obj) { + m_obj->disconnect(); + m_obj->deleteLater(); } + } - void reset(T* other) { - SafeDeletingQObjectPtr(other).swap(*this); - } + void reset(T* other) { SafeDeletingQObjectPtr(other).swap(*this); } - T& operator*() const { - return *m_pObj; - } + T& operator*() const { return *m_obj; } - T* operator->() const { - return m_pObj; - } + T* operator->() const { return m_obj; } - T* get() const { - return m_pObj; - } + T* get() const { return m_obj; } - void swap(SafeDeletingQObjectPtr& other) { - T* tmp = m_pObj; - m_pObj = other.m_pObj; - other.m_pObj = tmp; - } + void swap(SafeDeletingQObjectPtr& other) { + T* tmp = m_obj; + m_obj = other.m_obj; + other.m_obj = tmp; + } -private: - T* m_pObj; + private: + T* m_obj; }; -template +template void swap(SafeDeletingQObjectPtr& o1, SafeDeletingQObjectPtr& o2) { - o1.swap(o2); + o1.swap(o2); } #endif // ifndef SAFE_DELETING_QOBJECT_PTR_H_ diff --git a/foundation/ScopedDecInc.h b/foundation/ScopedDecInc.h index 317cccc3c..9f56d8217 100644 --- a/foundation/ScopedDecInc.h +++ b/foundation/ScopedDecInc.h @@ -23,19 +23,15 @@ #include #endif -template +template class ScopedDecInc { -public: - explicit ScopedDecInc(T& counter) : m_counter(counter) { - --counter; - } + public: + explicit ScopedDecInc(T& counter) : m_counter(counter) { --counter; } - ~ScopedDecInc() { - ++m_counter; - } + ~ScopedDecInc() { ++m_counter; } -private: - T& m_counter; + private: + T& m_counter; }; diff --git a/foundation/ScopedIncDec.h b/foundation/ScopedIncDec.h index dd32a95cd..32653d65b 100644 --- a/foundation/ScopedIncDec.h +++ b/foundation/ScopedIncDec.h @@ -23,19 +23,15 @@ #include #endif -template +template class ScopedIncDec { -public: - explicit ScopedIncDec(T& counter) : m_counter(counter) { - ++counter; - } + public: + explicit ScopedIncDec(T& counter) : m_counter(counter) { ++counter; } - ~ScopedIncDec() { - --m_counter; - } + ~ScopedIncDec() { --m_counter; } -private: - T& m_counter; + private: + T& m_counter; }; diff --git a/foundation/Span.h b/foundation/Span.h index 3b5a36c4d..958fe8904 100644 --- a/foundation/Span.h +++ b/foundation/Span.h @@ -23,92 +23,75 @@ * \brief Represents a [from, to) range in one-dimensional space. */ class Span { -public: - /** - * \brief Constructs an empty span. - */ - Span() : m_begin(0), m_end(0) { - } - - /** - * \brief Constructs a [begin, end) span. - */ - Span(int begin, int end) : m_begin(begin), m_end(end) { - } - - /** - * \brief Constructs a span between a point and another span. - */ - Span(int begin, const Span& end) : m_begin(begin), m_end(end.begin()) { - } - - /** - * \brief Constructs a span between another span and a point. - */ - Span(const Span& begin, int end) : m_begin(begin.end()), m_end(end) { - } - - /** - * \brief Constructs a span between two other spans. - */ - Span(const Span& begin, const Span& end) : m_begin(begin.end()), m_end(end.begin()) { - } - - int begin() const { - return m_begin; - } - - int end() const { - return m_end; - } - - int width() const { - return m_end - m_begin; - } - - double center() const { - return 0.5 * (m_begin + m_end); - } - - bool operator==(const Span& other) const { - return m_begin == other.m_begin && m_end == other.m_end; - } - - bool operator!=(const Span& other) const { - return m_begin != other.m_begin || m_end != other.m_end; - } - - Span& operator+=(int offset) { - m_begin += offset; - m_end += offset; - - return *this; - } - - Span& operator-=(int offset) { - m_begin -= offset; - m_end -= offset; - - return *this; - } - - Span operator+(int offset) const { - Span span(*this); - span += offset; - - return span; - } - - Span operator-(int offset) const { - Span span(*this); - span -= offset; - - return span; - } - -private: - int m_begin; - int m_end; + public: + /** + * \brief Constructs an empty span. + */ + Span() : m_begin(0), m_end(0) {} + + /** + * \brief Constructs a [begin, end) span. + */ + Span(int begin, int end) : m_begin(begin), m_end(end) {} + + /** + * \brief Constructs a span between a point and another span. + */ + Span(int begin, const Span& end) : m_begin(begin), m_end(end.begin()) {} + + /** + * \brief Constructs a span between another span and a point. + */ + Span(const Span& begin, int end) : m_begin(begin.end()), m_end(end) {} + + /** + * \brief Constructs a span between two other spans. + */ + Span(const Span& begin, const Span& end) : m_begin(begin.end()), m_end(end.begin()) {} + + int begin() const { return m_begin; } + + int end() const { return m_end; } + + int width() const { return m_end - m_begin; } + + double center() const { return 0.5 * (m_begin + m_end); } + + bool operator==(const Span& other) const { return m_begin == other.m_begin && m_end == other.m_end; } + + bool operator!=(const Span& other) const { return m_begin != other.m_begin || m_end != other.m_end; } + + Span& operator+=(int offset) { + m_begin += offset; + m_end += offset; + + return *this; + } + + Span& operator-=(int offset) { + m_begin -= offset; + m_end -= offset; + + return *this; + } + + Span operator+(int offset) const { + Span span(*this); + span += offset; + + return span; + } + + Span operator-(int offset) const { + Span span(*this); + span -= offset; + + return span; + } + + private: + int m_begin; + int m_end; }; diff --git a/foundation/StaticPool.h b/foundation/StaticPool.h index 11bf7d8e8..9f9f3e529 100644 --- a/foundation/StaticPool.h +++ b/foundation/StaticPool.h @@ -19,34 +19,33 @@ #ifndef STATIC_POOL_H_ #define STATIC_POOL_H_ -#include "NonCopyable.h" -#include #include +#include +#include "NonCopyable.h" -template +template class StaticPoolBase { - DECLARE_NON_COPYABLE(StaticPoolBase) - -public: - StaticPoolBase(T* buf, size_t size) : m_pNext(buf), m_sizeRemaining(size) { - } - - /** - * \brief Allocates a sequence of objects. - * - * If the pool has enough free space, returns a sequence of requested - * number of elements, otherwise throws an std::runtime_error. - * If T is a POD type, the returned objects are uninitialized, - * otherwise they are default-constructed. - * - * This function was moved to the base class in order to have - * just one instantiation of it for different sized pool of the same type. - */ - T* alloc(size_t num_elements); - -private: - T* m_pNext; - size_t m_sizeRemaining; + DECLARE_NON_COPYABLE(StaticPoolBase) + + public: + StaticPoolBase(T* buf, size_t size) : m_next(buf), m_sizeRemaining(size) {} + + /** + * \brief Allocates a sequence of objects. + * + * If the pool has enough free space, returns a sequence of requested + * number of elements, otherwise throws an std::runtime_error. + * If T is a POD type, the returned objects are uninitialized, + * otherwise they are default-constructed. + * + * This function was moved to the base class in order to have + * just one instantiation of it for different sized pool of the same type. + */ + T* alloc(size_t num_elements); + + private: + T* m_next; + size_t m_sizeRemaining; }; @@ -56,30 +55,29 @@ class StaticPoolBase { * There is no way of releasing the allocated objects * besides destroying the whole pool. */ -template +template class StaticPool : public StaticPoolBase { - DECLARE_NON_COPYABLE(StaticPool) + DECLARE_NON_COPYABLE(StaticPool) -public: - StaticPool() : StaticPoolBase(m_buf, S) { - } + public: + StaticPool() : StaticPoolBase(m_buf, S) {} -private: - T m_buf[S]; + private: + T m_buf[S]; }; -template +template T* StaticPoolBase::alloc(size_t num_elements) { - if (num_elements > m_sizeRemaining) { - throw std::runtime_error("StaticPool overflow"); - } + if (num_elements > m_sizeRemaining) { + throw std::runtime_error("StaticPool overflow"); + } - T* sequence = m_pNext; - m_pNext += num_elements; - m_sizeRemaining -= num_elements; + T* sequence = m_next; + m_next += num_elements; + m_sizeRemaining -= num_elements; - return sequence; + return sequence; } #endif // ifndef STATIC_POOL_H_ diff --git a/foundation/ValueConv.h b/foundation/ValueConv.h index 0aac063d3..34ca635dc 100644 --- a/foundation/ValueConv.h +++ b/foundation/ValueConv.h @@ -19,44 +19,43 @@ #ifndef VALUE_CONV_H_ #define VALUE_CONV_H_ -#include "NumericTraits.h" #include +#include "NumericTraits.h" -template +template class StaticCastValueConv { -public: - template - ToType operator()(FromType val) const { - return static_cast(val); - } + public: + template + ToType operator()(FromType val) const { + return static_cast(val); + } }; -template +template class RoundAndClipValueConv { -public: - explicit RoundAndClipValueConv(ToType min = NumericTraits::min(), ToType max = NumericTraits::max()) - : m_min(min), m_max(max) { - } - - template - ToType operator()(FromType val) const { - // To avoid possible "comparing signed to unsigned" warnings, - // we do the comparison with FromType. It should be fine, as - // "Round" in the name of the class assumes it's a floating point type, - // and therefore should be "wider" than ToType. - if (val < FromType(m_min)) { - return m_min; - } else if (val > FromType(m_max)) { - return m_max; - } else { - return static_cast(std::floor(val + 0.5)); - } + public: + explicit RoundAndClipValueConv(ToType min = NumericTraits::min(), ToType max = NumericTraits::max()) + : m_min(min), m_max(max) {} + + template + ToType operator()(FromType val) const { + // To avoid possible "comparing signed to unsigned" warnings, + // we do the comparison with FromType. It should be fine, as + // "Round" in the name of the class assumes it's a floating point type, + // and therefore should be "wider" than ToType. + if (val < FromType(m_min)) { + return m_min; + } else if (val > FromType(m_max)) { + return m_max; + } else { + return static_cast(std::floor(val + 0.5)); } + } -private: - ToType m_min; - ToType m_max; + private: + ToType m_min; + ToType m_max; }; diff --git a/foundation/VecNT.h b/foundation/VecNT.h index 27fe27158..54cdb368c 100644 --- a/foundation/VecNT.h +++ b/foundation/VecNT.h @@ -22,7 +22,7 @@ #include #include -template +template class VecNT; typedef VecNT<1, float> Vec1f; @@ -34,366 +34,352 @@ typedef VecNT<3, double> Vec3d; typedef VecNT<4, float> Vec4f; typedef VecNT<4, double> Vec4d; -template +template class VecNT { -public: - typedef T type; - enum { SIZE = static_cast(N) }; - - /** - * \brief Initializes vector elements to T(). - */ - VecNT(); - - /** - * \brief Construction from an array of elements of possibly different type. - * - * Conversion is done by static casts. - */ - template - VecNT(const OT* data); - - /** - * \brief Construction from a vector of same dimension but another type. - * - * Conversion is done by static casts. - */ - template - VecNT(const VecNT& other); - - /** - * \brief Construction from a one-less dimensional - * vector and the last element value. - */ - template - VecNT(const VecNT& lesser, T last); - - /** - * \brief 1D vector constructor. - * - * Will not compile for different dimensions. - */ - explicit VecNT(T x); - - /** - * \brief 2D vector constructor. - * - * Will not compile for different dimensions. - */ - VecNT(T x, T y); - - /** - * \brief 3D vector constructor. - * - * Will not compile for different dimensions. - */ - VecNT(T x, T y, T z); - - /** - * \brief 4D vector constructor. - * - * Will not compile for different dimensions. - */ - VecNT(T x, T y, T z, T w); - - /** - * \brief Construction from a QPointF. - * - * Will not compile for N != 2. Will compile for any T's that - * are convertable from qreal by a static cast. - */ - VecNT(const QPointF& pt); - - /** - * \brief Implicit conversion to QPointF. - * - * Will not compile for N != 2. Will compile for any T's that - * are convertable to qreal by a static cast. - */ - operator QPointF() const; - - /** - * \brief Assignment from a vector of same dimension but another type. - * - * Conversion is done by static casts. - */ - template - VecNT& operator=(const VecNT& other); - - T& operator[](size_t idx) { - return m_data[idx]; - } - - const T& operator[](size_t idx) const { - return m_data[idx]; - } - - VecNT& operator+=(T scalar); - - VecNT& operator+=(const VecNT& other); - - VecNT& operator-=(T scalar); - - VecNT& operator-=(const VecNT& other); - - VecNT& operator*=(T scalar); - - VecNT& operator/=(T scalar); - - const T* data() const { - return m_data; - } - - T* data() { - return m_data; - } - - /** - * \brief Sums elements in the vector. - */ - T sum() const; - - T dot(const VecNT& other) const; - - T squaredNorm() const { - return dot(*this); - } - -private: - T m_data[N]; + public: + typedef T type; + enum { SIZE = static_cast(N) }; + + /** + * \brief Initializes vector elements to T(). + */ + VecNT(); + + /** + * \brief Construction from an array of elements of possibly different type. + * + * Conversion is done by static casts. + */ + template + VecNT(const OT* data); + + /** + * \brief Construction from a vector of same dimension but another type. + * + * Conversion is done by static casts. + */ + template + VecNT(const VecNT& other); + + /** + * \brief Construction from a one-less dimensional + * vector and the last element value. + */ + template + VecNT(const VecNT& lesser, T last); + + /** + * \brief 1D vector constructor. + * + * Will not compile for different dimensions. + */ + explicit VecNT(T x); + + /** + * \brief 2D vector constructor. + * + * Will not compile for different dimensions. + */ + VecNT(T x, T y); + + /** + * \brief 3D vector constructor. + * + * Will not compile for different dimensions. + */ + VecNT(T x, T y, T z); + + /** + * \brief 4D vector constructor. + * + * Will not compile for different dimensions. + */ + VecNT(T x, T y, T z, T w); + + /** + * \brief Construction from a QPointF. + * + * Will not compile for N != 2. Will compile for any T's that + * are convertable from qreal by a static cast. + */ + VecNT(const QPointF& pt); + + /** + * \brief Implicit conversion to QPointF. + * + * Will not compile for N != 2. Will compile for any T's that + * are convertable to qreal by a static cast. + */ + operator QPointF() const; + + /** + * \brief Assignment from a vector of same dimension but another type. + * + * Conversion is done by static casts. + */ + template + VecNT& operator=(const VecNT& other); + + T& operator[](size_t idx) { return m_data[idx]; } + + const T& operator[](size_t idx) const { return m_data[idx]; } + + VecNT& operator+=(T scalar); + + VecNT& operator+=(const VecNT& other); + + VecNT& operator-=(T scalar); + + VecNT& operator-=(const VecNT& other); + + VecNT& operator*=(T scalar); + + VecNT& operator/=(T scalar); + + const T* data() const { return m_data; } + + T* data() { return m_data; } + + /** + * \brief Sums elements in the vector. + */ + T sum() const; + + T dot(const VecNT& other) const; + + T squaredNorm() const { return dot(*this); } + + private: + T m_data[N]; }; namespace vecnt { -template +template struct SizeSpecific; -template +template struct SizeSpecific<1, T> { - static void assign(T* data, T x) { - data[0] = x; - } + static void assign(T* data, T x) { data[0] = x; } }; -template +template struct SizeSpecific<2, T> { - static void assign(T* data, T x, T y) { - data[0] = x; - data[1] = y; - } - - static void assign(T* data, const QPointF& pt) { - data[0] = static_cast(pt.x()); - data[1] = static_cast(pt.y()); - } - - static QPointF toQPointF(const T* data) { - return QPointF(static_cast(data[0]), static_cast(data[1])); - } + static void assign(T* data, T x, T y) { + data[0] = x; + data[1] = y; + } + + static void assign(T* data, const QPointF& pt) { + data[0] = static_cast(pt.x()); + data[1] = static_cast(pt.y()); + } + + static QPointF toQPointF(const T* data) { return QPointF(static_cast(data[0]), static_cast(data[1])); } }; -template +template struct SizeSpecific<3, T> { - static void assign(T* data, T x, T y, T z) { - data[0] = x; - data[1] = y; - data[2] = z; - } + static void assign(T* data, T x, T y, T z) { + data[0] = x; + data[1] = y; + data[2] = z; + } }; -template +template struct SizeSpecific<4, T> { - static void assign(T* data, T x, T y, T z, T w) { - data[0] = x; - data[1] = y; - data[2] = z; - data[3] = w; - } + static void assign(T* data, T x, T y, T z, T w) { + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + } }; } // namespace vecnt -template +template VecNT::VecNT() { - for (size_t i = 0; i < N; ++i) { - m_data[i] = T(); - } + for (size_t i = 0; i < N; ++i) { + m_data[i] = T(); + } } -template -template +template +template VecNT::VecNT(const OT* data) { - for (size_t i = 0; i < N; ++i) { - m_data[i] = static_cast(data[i]); - } + for (size_t i = 0; i < N; ++i) { + m_data[i] = static_cast(data[i]); + } } -template -template +template +template VecNT::VecNT(const VecNT& other) { - for (size_t i = 0; i < N; ++i) { - m_data[i] = static_cast(other[i]); - } + for (size_t i = 0; i < N; ++i) { + m_data[i] = static_cast(other[i]); + } } -template -template +template +template VecNT::VecNT(const VecNT& lesser, T last) { - for (size_t i = 0; i < N - 1; ++i) { - m_data[i] = static_cast(lesser[i]); - } - m_data[N - 1] = last; + for (size_t i = 0; i < N - 1; ++i) { + m_data[i] = static_cast(lesser[i]); + } + m_data[N - 1] = last; } -template +template VecNT::VecNT(T x) { - vecnt::SizeSpecific::assign(m_data, x); + vecnt::SizeSpecific::assign(m_data, x); } -template +template VecNT::VecNT(T x, T y) { - vecnt::SizeSpecific::assign(m_data, x, y); + vecnt::SizeSpecific::assign(m_data, x, y); } -template +template VecNT::VecNT(T x, T y, T z) { - vecnt::SizeSpecific::assign(m_data, x, y, z); + vecnt::SizeSpecific::assign(m_data, x, y, z); } -template +template VecNT::VecNT(T x, T y, T z, T w) { - vecnt::SizeSpecific::assign(m_data, x, y, z, w); + vecnt::SizeSpecific::assign(m_data, x, y, z, w); } -template +template VecNT::VecNT(const QPointF& pt) { - vecnt::SizeSpecific::assign(m_data, pt); + vecnt::SizeSpecific::assign(m_data, pt); } -template +template VecNT::operator QPointF() const { - return vecnt::SizeSpecific::toQPointF(m_data); + return vecnt::SizeSpecific::toQPointF(m_data); } -template -template +template +template VecNT& VecNT::operator=(const VecNT& other) { - for (size_t i = 0; i < N; ++i) { - m_data[i] = static_cast(other[i]); - } + for (size_t i = 0; i < N; ++i) { + m_data[i] = static_cast(other[i]); + } - return *this; + return *this; } -template +template VecNT& VecNT::operator+=(T scalar) { - for (size_t i = 0; i < N; ++i) { - m_data[i] += scalar; - } + for (size_t i = 0; i < N; ++i) { + m_data[i] += scalar; + } - return *this; + return *this; } -template +template VecNT& VecNT::operator+=(const VecNT& other) { - for (size_t i = 0; i < N; ++i) { - m_data[i] += other[i]; - } + for (size_t i = 0; i < N; ++i) { + m_data[i] += other[i]; + } - return *this; + return *this; } -template +template VecNT& VecNT::operator-=(T scalar) { - for (size_t i = 0; i < N; ++i) { - m_data[i] -= scalar; - } + for (size_t i = 0; i < N; ++i) { + m_data[i] -= scalar; + } - return *this; + return *this; } -template +template VecNT& VecNT::operator-=(const VecNT& other) { - for (size_t i = 0; i < N; ++i) { - m_data[i] -= other[i]; - } + for (size_t i = 0; i < N; ++i) { + m_data[i] -= other[i]; + } - return *this; + return *this; } -template +template VecNT& VecNT::operator*=(T scalar) { - for (size_t i = 0; i < N; ++i) { - m_data[i] *= scalar; - } + for (size_t i = 0; i < N; ++i) { + m_data[i] *= scalar; + } - return *this; + return *this; } -template +template VecNT& VecNT::operator/=(T scalar) { - return *this *= (T(1) / scalar); + return *this *= (T(1) / scalar); } -template +template T VecNT::sum() const { - T sum = T(); - for (size_t i = 0; i < N; ++i) { - sum += m_data[i]; - } + T sum = T(); + for (size_t i = 0; i < N; ++i) { + sum += m_data[i]; + } - return sum; + return sum; } -template +template T VecNT::dot(const VecNT& other) const { - T sum = T(); - for (size_t i = 0; i < N; ++i) { - sum += m_data[i] * other[i]; - } + T sum = T(); + for (size_t i = 0; i < N; ++i) { + sum += m_data[i] * other[i]; + } - return sum; + return sum; } -template +template VecNT operator+(const VecNT& lhs, const VecNT& rhs) { - VecNT res(lhs); - res += rhs; + VecNT res(lhs); + res += rhs; - return res; + return res; } -template +template VecNT operator-(const VecNT& lhs, const VecNT& rhs) { - VecNT res(lhs); - res -= rhs; + VecNT res(lhs); + res -= rhs; - return res; + return res; } -template +template VecNT operator-(const VecNT& vec) { - VecNT res(vec); - for (size_t i = 0; i < N; ++i) { - res[i] = -res[i]; - } + VecNT res(vec); + for (size_t i = 0; i < N; ++i) { + res[i] = -res[i]; + } - return res; + return res; } -template +template VecNT operator*(const VecNT& vec, T scalar) { - VecNT res(vec); - res *= scalar; + VecNT res(vec); + res *= scalar; - return res; + return res; } -template +template VecNT operator*(T scalar, const VecNT& vec) { - VecNT res(vec); - res *= scalar; + VecNT res(vec); + res *= scalar; - return res; + return res; } #endif // ifndef VEC_NT_H_ diff --git a/foundation/VecT.h b/foundation/VecT.h index be6de920c..55ea8b76e 100644 --- a/foundation/VecT.h +++ b/foundation/VecT.h @@ -20,227 +20,219 @@ #define VEC_T_H_ #include -#include #include +#include /** * \brief A (column) vector of elements of type T. */ -template +template class VecT { -public: - typedef T type; - - /** - * \brief Constructs an empty vector. - */ - VecT(); - - /** - * \brief Constructs a vector of specified size initialized with T(). - */ - explicit VecT(size_t size); - - /** - * \brief Constructs a vector of specified size initializing to the provided value. - */ - VecT(size_t size, T initial_value); - - /** - * \brief Construction from an array of elements of possibly different type. - * - * Conversion is done by static casts. - */ - template - explicit VecT(size_t size, const OT* data); - - /** - * Ordinary copy-construction. - */ - VecT(const VecT& other); - - /** - * \brief Construction from a vector of a different type. - * - * Conversion is done by static casts. - */ - template - explicit VecT(const VecT& other); - - /** - * \brief Ordinary assignment. - */ - VecT& operator=(const VecT& other); - - /** - * \brief Assignment from a vector of a different type. - * - * Conversion is done by static casts. - */ - template - VecT& operator=(const VecT& other); - - VecT& operator+=(const VecT& rhs); - - VecT& operator-=(const VecT& rhs); - - VecT& operator*=(T scalar); - - size_t size() const { - return m_size; - } - - const T* data() const { - return m_data.get(); - } - - T* data() { - return m_data.get(); - } - - const T& operator[](size_t idx) const { - assert(idx < m_size); - - return m_data[idx]; - } - - T& operator[](size_t idx) { - assert(idx < m_size); - - return m_data[idx]; - } - - void fill(const T& value); - - void swap(VecT& other); - -private: - boost::scoped_array m_data; - size_t m_size; + public: + typedef T type; + + /** + * \brief Constructs an empty vector. + */ + VecT(); + + /** + * \brief Constructs a vector of specified size initialized with T(). + */ + explicit VecT(size_t size); + + /** + * \brief Constructs a vector of specified size initializing to the provided value. + */ + VecT(size_t size, T initial_value); + + /** + * \brief Construction from an array of elements of possibly different type. + * + * Conversion is done by static casts. + */ + template + explicit VecT(size_t size, const OT* data); + + /** + * Ordinary copy-construction. + */ + VecT(const VecT& other); + + /** + * \brief Construction from a vector of a different type. + * + * Conversion is done by static casts. + */ + template + explicit VecT(const VecT& other); + + /** + * \brief Ordinary assignment. + */ + VecT& operator=(const VecT& other); + + /** + * \brief Assignment from a vector of a different type. + * + * Conversion is done by static casts. + */ + template + VecT& operator=(const VecT& other); + + VecT& operator+=(const VecT& rhs); + + VecT& operator-=(const VecT& rhs); + + VecT& operator*=(T scalar); + + size_t size() const { return m_size; } + + const T* data() const { return m_data.get(); } + + T* data() { return m_data.get(); } + + const T& operator[](size_t idx) const { + assert(idx < m_size); + + return m_data[idx]; + } + + T& operator[](size_t idx) { + assert(idx < m_size); + + return m_data[idx]; + } + + void fill(const T& value); + + void swap(VecT& other); + + private: + boost::scoped_array m_data; + size_t m_size; }; -template -VecT::VecT() : m_size(0) { -} +template +VecT::VecT() : m_size(0) {} -template +template VecT::VecT(size_t size) - : m_data(new T[size]()), - // The "()" will cause elements to be initialized to T(). - m_size(size) { -} + : m_data(new T[size]()), + // The "()" will cause elements to be initialized to T(). + m_size(size) {} -template +template VecT::VecT(size_t size, T initial_value) : m_data(new T[size]), m_size(size) { - for (size_t i = 0; i < size; ++i) { - m_data[i] = initial_value; - } + for (size_t i = 0; i < size; ++i) { + m_data[i] = initial_value; + } } -template -template +template +template VecT::VecT(size_t size, const OT* data) : m_data(new T[size]), m_size(size) { - for (size_t i = 0; i < size; ++i) { - m_data[i] = static_cast(data[i]); - } + for (size_t i = 0; i < size; ++i) { + m_data[i] = static_cast(data[i]); + } } -template +template VecT::VecT(const VecT& other) : m_data(new T[other.m_size]), m_size(other.m_size) { - const T* other_data = other.data(); - for (size_t i = 0; i < m_size; ++i) { - m_data[i] = other_data[i]; - } + const T* other_data = other.data(); + for (size_t i = 0; i < m_size; ++i) { + m_data[i] = other_data[i]; + } } -template -template +template +template VecT::VecT(const VecT& other) : m_data(new T[other.m_size]), m_size(other.m_size) { - const T* other_data = other.data(); - for (size_t i = 0; i < m_size; ++i) { - m_data[i] = other_data[i]; - } + const T* other_data = other.data(); + for (size_t i = 0; i < m_size; ++i) { + m_data[i] = other_data[i]; + } } -template +template VecT& VecT::operator=(const VecT& other) { - VecT(other).swap(*this); + VecT(other).swap(*this); - return *this; + return *this; } -template -template +template +template VecT& VecT::operator=(const VecT& other) { - VecT(other).swap(*this); + VecT(other).swap(*this); - return *this; + return *this; } -template +template VecT& VecT::operator+=(const VecT& rhs) { - assert(m_size == rhs.m_size); - for (size_t i = 0; i < m_size; ++i) { - m_data[i] += rhs.m_data[i]; - } + assert(m_size == rhs.m_size); + for (size_t i = 0; i < m_size; ++i) { + m_data[i] += rhs.m_data[i]; + } - return *this; + return *this; } -template +template VecT& VecT::operator-=(const VecT& rhs) { - assert(m_size == rhs.m_size); - for (size_t i = 0; i < m_size; ++i) { - m_data[i] -= rhs.m_data[i]; - } + assert(m_size == rhs.m_size); + for (size_t i = 0; i < m_size; ++i) { + m_data[i] -= rhs.m_data[i]; + } - return *this; + return *this; } -template +template VecT& VecT::operator*=(const T scalar) { - for (size_t i = 0; i < m_size; ++i) { - m_data[i] *= scalar; - } + for (size_t i = 0; i < m_size; ++i) { + m_data[i] *= scalar; + } - return *this; + return *this; } -template +template void VecT::fill(const T& value) { - for (size_t i = 0; i < m_size; ++i) { - m_data[i] = value; - } + for (size_t i = 0; i < m_size; ++i) { + m_data[i] = value; + } } -template +template void VecT::swap(VecT& other) { - size_t tmp = m_size; - m_size = other.m_size; - other.m_size = tmp; - m_data.swap(other.m_data); + size_t tmp = m_size; + m_size = other.m_size; + other.m_size = tmp; + m_data.swap(other.m_data); } -template +template void swap(const VecT& o1, const VecT& o2) { - o1.swap(o2); + o1.swap(o2); } -template +template VecT operator*(const VecT& vec, double scalar) { - VecT res(vec); - res *= scalar; + VecT res(vec); + res *= scalar; - return res; + return res; } -template +template VecT operator*(double scalar, const VecT& vec) { - VecT res(vec); - res *= scalar; + VecT res(vec); + res *= scalar; - return res; + return res; } #endif // ifndef VEC_T_H_ diff --git a/foundation/VirtualFunction.h b/foundation/VirtualFunction.h index adc29a8c0..bb117be61 100644 --- a/foundation/VirtualFunction.h +++ b/foundation/VirtualFunction.h @@ -21,28 +21,24 @@ #include -template +template class VirtualFunction { -public: - virtual ~VirtualFunction() { - } + public: + virtual ~VirtualFunction() {} - virtual Res operator()(ArgTypes... args) const = 0; + virtual Res operator()(ArgTypes... args) const = 0; }; -template +template class ProxyFunction : public VirtualFunction { -public: - explicit ProxyFunction(Delegate delegate) : m_delegate(delegate) { - } + public: + explicit ProxyFunction(Delegate delegate) : m_delegate(delegate) {} - Res operator()(ArgTypes... args) const override { - return m_delegate(args...); - } + Res operator()(ArgTypes... args) const override { return m_delegate(args...); } -private: - Delegate m_delegate; + private: + Delegate m_delegate; }; diff --git a/foundation/intrusive_ptr.h b/foundation/intrusive_ptr.h index 529df2fd1..690b97030 100644 --- a/foundation/intrusive_ptr.h +++ b/foundation/intrusive_ptr.h @@ -26,74 +26,74 @@ #include #endif -template +template class intrusive_ptr { - template - friend class intrusive_ptr; + template + friend class intrusive_ptr; -public: - struct hash; + public: + struct hash; - using pointer = T*; + using pointer = T*; - template - using __enable_if_convertible = - typename std::enable_if::pointer, pointer>::value>::type; + template + using enable_if_convertible = + typename std::enable_if::pointer, pointer>::value>::type; -public: - constexpr intrusive_ptr(std::nullptr_t = nullptr) noexcept; + public: + constexpr intrusive_ptr(std::nullptr_t = nullptr) noexcept; - explicit intrusive_ptr(T* obj) noexcept; + explicit intrusive_ptr(T* obj) noexcept; - intrusive_ptr(const intrusive_ptr& other) noexcept; + intrusive_ptr(const intrusive_ptr& other) noexcept; - intrusive_ptr(intrusive_ptr&& other) noexcept; + intrusive_ptr(intrusive_ptr&& other) noexcept; - template> - intrusive_ptr(const intrusive_ptr& other) noexcept; + template > + intrusive_ptr(const intrusive_ptr& other) noexcept; - template> - intrusive_ptr(intrusive_ptr&& other) noexcept; + template > + intrusive_ptr(intrusive_ptr&& other) noexcept; - ~intrusive_ptr() noexcept; + ~intrusive_ptr() noexcept; - intrusive_ptr& operator=(std::nullptr_t) noexcept; + intrusive_ptr& operator=(std::nullptr_t) noexcept; - intrusive_ptr& operator=(const intrusive_ptr& rhs) noexcept; + intrusive_ptr& operator=(const intrusive_ptr& rhs) noexcept; - intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept; + intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept; - template> - intrusive_ptr& operator=(const intrusive_ptr& rhs) noexcept; + template > + intrusive_ptr& operator=(const intrusive_ptr& rhs) noexcept; - template> - intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept; + template > + intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept; - T& operator*() const; + T& operator*() const; - T* operator->() const noexcept; + T* operator->() const noexcept; - T* get() const noexcept; + T* get() const noexcept; - T* release() noexcept; + T* release() noexcept; - void reset(std::nullptr_t = nullptr) noexcept; + void reset(std::nullptr_t = nullptr) noexcept; - void reset(T* obj) noexcept; + void reset(T* obj) noexcept; - void swap(intrusive_ptr& other) noexcept; + void swap(intrusive_ptr& other) noexcept; - explicit operator bool() const noexcept; + explicit operator bool() const noexcept; -private: - T* fork() const noexcept; + private: + T* fork() const noexcept; - void intrusive_ref(const T& obj) const noexcept; + void intrusive_ref(const T& obj) const noexcept; - void intrusive_unref(const T& obj) const noexcept; + void intrusive_unref(const T& obj) const noexcept; - T* m_obj; + T* m_obj; }; /** @@ -101,9 +101,9 @@ class intrusive_ptr { * * May be specialized or overloaded. */ -template +template inline void intrusive_ptr::intrusive_ref(const T& obj) const noexcept { - obj.ref(); + obj.ref(); } /** @@ -111,172 +111,167 @@ inline void intrusive_ptr::intrusive_ref(const T& obj) const noexcept { * * May be specialized or overloaded. */ -template +template inline void intrusive_ptr::intrusive_unref(const T& obj) const noexcept { - obj.unref(); + obj.unref(); } -template -constexpr inline intrusive_ptr::intrusive_ptr(std::nullptr_t) noexcept : m_obj(nullptr) { -} +template +constexpr inline intrusive_ptr::intrusive_ptr(std::nullptr_t) noexcept : m_obj(nullptr) {} -template +template inline intrusive_ptr::intrusive_ptr(T* obj) noexcept : m_obj(obj) { - if (obj) { - intrusive_ref(*obj); - } + if (obj) { + intrusive_ref(*obj); + } } -template -inline intrusive_ptr::intrusive_ptr(const intrusive_ptr& other) noexcept : m_obj(other.fork()) { -} +template +inline intrusive_ptr::intrusive_ptr(const intrusive_ptr& other) noexcept : m_obj(other.fork()) {} -template -inline intrusive_ptr::intrusive_ptr(intrusive_ptr&& other) noexcept : m_obj(other.release()) { -} +template +inline intrusive_ptr::intrusive_ptr(intrusive_ptr&& other) noexcept : m_obj(other.release()) {} -template -template -inline intrusive_ptr::intrusive_ptr(const intrusive_ptr& other) noexcept : m_obj(other.fork()) { -} +template +template +inline intrusive_ptr::intrusive_ptr(const intrusive_ptr& other) noexcept : m_obj(other.fork()) {} -template -template -inline intrusive_ptr::intrusive_ptr(intrusive_ptr&& other) noexcept : m_obj(other.release()) { -} +template +template +inline intrusive_ptr::intrusive_ptr(intrusive_ptr&& other) noexcept : m_obj(other.release()) {} -template +template inline intrusive_ptr::~intrusive_ptr() noexcept { - if (m_obj) { - intrusive_unref(*m_obj); - } + if (m_obj) { + intrusive_unref(*m_obj); + } } -template +template inline intrusive_ptr& intrusive_ptr::operator=(std::nullptr_t) noexcept { - reset(); + reset(); - return *this; + return *this; } -template +template inline intrusive_ptr& intrusive_ptr::operator=(const intrusive_ptr& rhs) noexcept { - intrusive_ptr(rhs).swap(*this); + intrusive_ptr(rhs).swap(*this); - return *this; + return *this; } -template +template inline intrusive_ptr& intrusive_ptr::operator=(intrusive_ptr&& rhs) noexcept { - intrusive_ptr(std::move(rhs)).swap(*this); + intrusive_ptr(std::move(rhs)).swap(*this); - return *this; + return *this; } -template -template +template +template inline intrusive_ptr& intrusive_ptr::operator=(const intrusive_ptr& rhs) noexcept { - intrusive_ptr(rhs).swap(*this); + intrusive_ptr(rhs).swap(*this); - return *this; + return *this; } -template -template +template +template inline intrusive_ptr& intrusive_ptr::operator=(intrusive_ptr&& rhs) noexcept { - intrusive_ptr(std::move(rhs)).swap(*this); + intrusive_ptr(std::move(rhs)).swap(*this); - return *this; + return *this; } -template +template inline T& intrusive_ptr::operator*() const { - return *get(); + return *get(); } -template +template inline T* intrusive_ptr::operator->() const noexcept { - return get(); + return get(); } -template +template inline T* intrusive_ptr::get() const noexcept { - return m_obj; + return m_obj; } -template +template inline T* intrusive_ptr::release() noexcept { - T* obj = m_obj; - m_obj = nullptr; + T* obj = m_obj; + m_obj = nullptr; - return obj; + return obj; } -template +template inline void intrusive_ptr::reset(std::nullptr_t) noexcept { - intrusive_ptr().swap(*this); + intrusive_ptr().swap(*this); } -template +template inline void intrusive_ptr::reset(T* obj) noexcept { - intrusive_ptr(obj).swap(*this); + intrusive_ptr(obj).swap(*this); } -template +template inline void intrusive_ptr::swap(intrusive_ptr& other) noexcept { - T* obj = other.m_obj; - other.m_obj = m_obj; - m_obj = obj; + T* obj = other.m_obj; + other.m_obj = m_obj; + m_obj = obj; } -template +template inline intrusive_ptr::operator bool() const noexcept { - return (get() != nullptr); + return (get() != nullptr); } -template +template inline T* intrusive_ptr::fork() const noexcept { - T* obj = m_obj; - if (obj) { - intrusive_ref(*obj); - } + T* obj = m_obj; + if (obj) { + intrusive_ref(*obj); + } - return obj; + return obj; } -template +template inline intrusive_ptr make_intrusive(Args&&... args) { - return intrusive_ptr(new T(std::forward(args)...)); + return intrusive_ptr(new T(std::forward(args)...)); }; -template +template struct intrusive_ptr::hash { - std::size_t operator()(const intrusive_ptr& __p) const noexcept { - return reinterpret_cast(__p.get()); - } + std::size_t operator()(const intrusive_ptr& __p) const noexcept { + return reinterpret_cast(__p.get()); + } }; -#define INTRUSIVE_PTR_OP(op) \ - template \ - inline bool operator op(const intrusive_ptr& lhs, const intrusive_ptr& rhs) { \ - return (lhs.get() op rhs.get()); \ - } \ - \ - template \ - inline bool operator op(const intrusive_ptr& lhs, const intrusive_ptr& rhs) { \ - return (lhs.get() op rhs.get()); \ - } \ - \ - template \ - inline bool operator op(std::nullptr_t, const intrusive_ptr& rhs) { \ - return (nullptr op rhs.get()); \ - } \ - \ - template \ - inline bool operator op(const intrusive_ptr& lhs, std::nullptr_t) { \ - return (lhs.get() op nullptr); \ - } +#define INTRUSIVE_PTR_OP(op) \ + template \ + inline bool operator op(const intrusive_ptr& lhs, const intrusive_ptr& rhs) { \ + return (lhs.get() op rhs.get()); \ + } \ + \ + template \ + inline bool operator op(const intrusive_ptr& lhs, const intrusive_ptr& rhs) { \ + return (lhs.get() op rhs.get()); \ + } \ + \ + template \ + inline bool operator op(std::nullptr_t, const intrusive_ptr& rhs) { \ + return (nullptr op rhs.get()); \ + } \ + \ + template \ + inline bool operator op(const intrusive_ptr& lhs, std::nullptr_t) { \ + return (lhs.get() op nullptr); \ + } INTRUSIVE_PTR_OP(==) @@ -290,9 +285,9 @@ INTRUSIVE_PTR_OP(<=) INTRUSIVE_PTR_OP(>=) -template +template inline void swap(intrusive_ptr& lhs, intrusive_ptr& rhs) { - lhs.swap(rhs); + lhs.swap(rhs); } #endif // ifndef intrusive_ptr_H_ diff --git a/foundation/ref_countable.h b/foundation/ref_countable.h index 73a8e91d5..bf0a941bb 100644 --- a/foundation/ref_countable.h +++ b/foundation/ref_countable.h @@ -26,34 +26,31 @@ #include class ref_countable { -public: - ref_countable() : m_refCounter(0) { - } + public: + ref_countable() : m_counter(0) {} - ref_countable(const ref_countable& other) { - // don't copy the reference counter! - } + ref_countable(const ref_countable& other) { + // don't copy the reference counter! + } - ref_countable& operator=(const ref_countable& other) { - // don't copy the reference counter! + ref_countable& operator=(const ref_countable& other) { + // don't copy the reference counter! - return *this; - } + return *this; + } - virtual ~ref_countable() = default; + virtual ~ref_countable() = default; - void ref() const { - m_refCounter.fetchAndAddRelaxed(1); - } + void ref() const { m_counter.fetchAndAddRelaxed(1); } - void unref() const { - if (m_refCounter.fetchAndAddRelease(-1) == 1) { - delete this; - } + void unref() const { + if (m_counter.fetchAndAddRelease(-1) == 1) { + delete this; } + } -private: - mutable QAtomicInt m_refCounter; + private: + mutable QAtomicInt m_counter; }; diff --git a/imageproc/AdjustBrightness.cpp b/imageproc/AdjustBrightness.cpp index ba47bf62c..b027932c4 100644 --- a/imageproc/AdjustBrightness.cpp +++ b/imageproc/AdjustBrightness.cpp @@ -21,70 +21,70 @@ namespace imageproc { void adjustBrightness(QImage& rgb_image, const QImage& brightness, const double wr, const double wb) { - switch (rgb_image.format()) { - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - break; - default: - throw std::invalid_argument("adjustBrightness: not (A)RGB32"); - } - - if ((brightness.format() != QImage::Format_Indexed8) || !brightness.allGray()) { - throw std::invalid_argument("adjustBrightness: brightness not grayscale"); - } - - if (rgb_image.size() != brightness.size()) { - throw std::invalid_argument("adjustBrightness: image and brightness have different sizes"); - } - - auto* rgb_line = reinterpret_cast(rgb_image.bits()); - const int rgb_wpl = rgb_image.bytesPerLine() / 4; - - const uint8_t* br_line = brightness.bits(); - const int br_bpl = brightness.bytesPerLine(); - - const int width = rgb_image.width(); - const int height = rgb_image.height(); - - const double wg = 1.0 - wr - wb; - const double wu = (1.0 - wb); - const double wv = (1.0 - wr); - const double r_wg = 1.0 / wg; - const double r_wu = 1.0 / wu; - const double r_wv = 1.0 / wv; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t RGB = rgb_line[x]; - const double R = (RGB >> 16) & 0xFF; - const double G = (RGB >> 8) & 0xFF; - const double B = RGB & 0xFF; - - const double Y = wr * R + wg * G + wb * B; - const double U = (B - Y) * r_wu; - const double V = (R - Y) * r_wv; - - double new_Y = br_line[x]; - double new_R = new_Y + V * wv; - double new_B = new_Y + U * wu; - double new_G = (new_Y - new_R * wr - new_B * wb) * r_wg; - - RGB &= 0xFF000000; // preserve alpha - RGB |= uint32_t(qBound(0, int(new_R + 0.5), 255)) << 16; - RGB |= uint32_t(qBound(0, int(new_G + 0.5), 255)) << 8; - RGB |= uint32_t(qBound(0, int(new_B + 0.5), 255)); - rgb_line[x] = RGB; - } - rgb_line += rgb_wpl; - br_line += br_bpl; + switch (rgb_image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + break; + default: + throw std::invalid_argument("adjustBrightness: not (A)RGB32"); + } + + if ((brightness.format() != QImage::Format_Indexed8) || !brightness.allGray()) { + throw std::invalid_argument("adjustBrightness: brightness not grayscale"); + } + + if (rgb_image.size() != brightness.size()) { + throw std::invalid_argument("adjustBrightness: image and brightness have different sizes"); + } + + auto* rgb_line = reinterpret_cast(rgb_image.bits()); + const int rgb_wpl = rgb_image.bytesPerLine() / 4; + + const uint8_t* br_line = brightness.bits(); + const int br_bpl = brightness.bytesPerLine(); + + const int width = rgb_image.width(); + const int height = rgb_image.height(); + + const double wg = 1.0 - wr - wb; + const double wu = (1.0 - wb); + const double wv = (1.0 - wr); + const double r_wg = 1.0 / wg; + const double r_wu = 1.0 / wu; + const double r_wv = 1.0 / wv; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t RGB = rgb_line[x]; + const double R = (RGB >> 16) & 0xFF; + const double G = (RGB >> 8) & 0xFF; + const double B = RGB & 0xFF; + + const double Y = wr * R + wg * G + wb * B; + const double U = (B - Y) * r_wu; + const double V = (R - Y) * r_wv; + + double new_Y = br_line[x]; + double new_R = new_Y + V * wv; + double new_B = new_Y + U * wu; + double new_G = (new_Y - new_R * wr - new_B * wb) * r_wg; + + RGB &= 0xFF000000; // preserve alpha + RGB |= uint32_t(qBound(0, int(new_R + 0.5), 255)) << 16; + RGB |= uint32_t(qBound(0, int(new_G + 0.5), 255)) << 8; + RGB |= uint32_t(qBound(0, int(new_B + 0.5), 255)); + rgb_line[x] = RGB; } + rgb_line += rgb_wpl; + br_line += br_bpl; + } } // adjustBrightness void adjustBrightnessYUV(QImage& rgb_image, const QImage& brightness) { - adjustBrightness(rgb_image, brightness, 0.299, 0.114); + adjustBrightness(rgb_image, brightness, 0.299, 0.114); } void adjustBrightnessGrayscale(QImage& rgb_image, const QImage& brightness) { - adjustBrightness(rgb_image, brightness, 11.0 / 32.0, 5.0 / 32.0); + adjustBrightness(rgb_image, brightness, 11.0 / 32.0, 5.0 / 32.0); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/BWColor.h b/imageproc/BWColor.h index 54bf366d5..6590ff496 100644 --- a/imageproc/BWColor.h +++ b/imageproc/BWColor.h @@ -23,7 +23,7 @@ namespace imageproc { enum BWColor { WHITE = 0, BLACK = 1 }; inline BWColor operator!(BWColor c) { - return static_cast(~c & 1); + return static_cast(~c & 1); } } // namespace imageproc #endif diff --git a/imageproc/BackgroundColorCalculator.cpp b/imageproc/BackgroundColorCalculator.cpp index e74d41114..a11765dbe 100644 --- a/imageproc/BackgroundColorCalculator.cpp +++ b/imageproc/BackgroundColorCalculator.cpp @@ -1,233 +1,244 @@ -#include #include "BackgroundColorCalculator.h" -#include "Grayscale.h" +#include #include "Binarize.h" -#include "BinaryImage.h" -#include "RasterOp.h" -#include "PolygonRasterizer.h" -#include "Morphology.h" #include "DebugImages.h" +#include "Grayscale.h" +#include "Morphology.h" +#include "PolygonRasterizer.h" +#include "RasterOp.h" namespace imageproc { namespace { class RgbHistogram { -public: - explicit RgbHistogram(const QImage& img); + public: + explicit RgbHistogram(const QImage& img); - RgbHistogram(const QImage& img, const BinaryImage& mask); + RgbHistogram(const QImage& img, const BinaryImage& mask); - const int* redChannel() const { - return m_red; - } + const int* redChannel() const { return m_red; } - const int* greenChannel() const { - return m_green; - } + const int* greenChannel() const { return m_green; } - const int* blueChannel() const { - return m_blue; - } + const int* blueChannel() const { return m_blue; } -private: - void fromRgbImage(const QImage& img); + private: + void fromRgbImage(const QImage& img); - void fromRgbImage(const QImage& img, const BinaryImage& mask); + void fromRgbImage(const QImage& img, const BinaryImage& mask); - int m_red[256]; - int m_green[256]; - int m_blue[256]; + int m_red[256]; + int m_green[256]; + int m_blue[256]; }; RgbHistogram::RgbHistogram(const QImage& img) { - memset(m_red, 0, sizeof(m_red)); - memset(m_green, 0, sizeof(m_green)); - memset(m_blue, 0, sizeof(m_blue)); + memset(m_red, 0, sizeof(m_red)); + memset(m_green, 0, sizeof(m_green)); + memset(m_blue, 0, sizeof(m_blue)); - if (img.isNull()) { - return; - } + if (img.isNull()) { + return; + } - if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32))) { - throw std::invalid_argument("RgbHistogram: wrong image format"); - } + if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32))) { + throw std::invalid_argument("RgbHistogram: wrong image format"); + } - fromRgbImage(img); + fromRgbImage(img); } RgbHistogram::RgbHistogram(const QImage& img, const BinaryImage& mask) { - memset(m_red, 0, sizeof(m_red)); - memset(m_green, 0, sizeof(m_green)); - memset(m_blue, 0, sizeof(m_blue)); + memset(m_red, 0, sizeof(m_red)); + memset(m_green, 0, sizeof(m_green)); + memset(m_blue, 0, sizeof(m_blue)); - if (img.isNull()) { - return; - } + if (img.isNull()) { + return; + } - if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32))) { - throw std::invalid_argument("RgbHistogram: wrong image format"); - } + if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32))) { + throw std::invalid_argument("RgbHistogram: wrong image format"); + } - if (img.size() != mask.size()) { - throw std::invalid_argument("RgbHistogram: img and mask have different sizes"); - } + if (img.size() != mask.size()) { + throw std::invalid_argument("RgbHistogram: img and mask have different sizes"); + } - fromRgbImage(img, mask); + fromRgbImage(img, mask); } void RgbHistogram::fromRgbImage(const QImage& img) { - const auto* img_line = reinterpret_cast(img.bits()); - const int img_stride = img.bytesPerLine() / sizeof(uint32_t); - - const int width = img.width(); - const int height = img.height(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - ++m_red[(img_line[x] >> 16) & 0xff]; - ++m_green[(img_line[x] >> 8) & 0xff]; - ++m_blue[img_line[x] & 0xff]; - } - img_line += img_stride; + const auto* img_line = reinterpret_cast(img.bits()); + const int img_stride = img.bytesPerLine() / sizeof(uint32_t); + + const int width = img.width(); + const int height = img.height(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + ++m_red[(img_line[x] >> 16) & 0xff]; + ++m_green[(img_line[x] >> 8) & 0xff]; + ++m_blue[img_line[x] & 0xff]; } + img_line += img_stride; + } } void RgbHistogram::fromRgbImage(const QImage& img, const BinaryImage& mask) { - const auto* img_line = reinterpret_cast(img.bits()); - const int img_stride = img.bytesPerLine() / sizeof(uint32_t); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - - const int width = img.width(); - const int height = img.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - ++m_red[(img_line[x] >> 16) & 0xff]; - ++m_green[(img_line[x] >> 8) & 0xff]; - ++m_blue[img_line[x] & 0xff]; - } - } - img_line += img_stride; - mask_line += mask_stride; - } + const auto* img_line = reinterpret_cast(img.bits()); + const int img_stride = img.bytesPerLine() / sizeof(uint32_t); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + + const int width = img.width(); + const int height = img.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + ++m_red[(img_line[x] >> 16) & 0xff]; + ++m_green[(img_line[x] >> 8) & 0xff]; + ++m_blue[img_line[x] & 0xff]; + } + } + img_line += img_stride; + mask_line += mask_stride; + } } void grayHistToArray(int* raw_hist, GrayscaleHistogram hist) { - for (int i = 0; i < 256; ++i) { - raw_hist[i] = hist[i]; - } + for (int i = 0; i < 256; ++i) { + raw_hist[i] = hist[i]; + } } void checkImageIsValid(const QImage& img) { - if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32) - || ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()))) { - throw std::invalid_argument("BackgroundColorCalculator: wrong image format"); - } - if (img.isNull()) { - throw std::invalid_argument("BackgroundColorCalculator: image is null."); - } + if (!((img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_ARGB32) + || ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()))) { + throw std::invalid_argument("BackgroundColorCalculator: wrong image format"); + } + if (img.isNull()) { + throw std::invalid_argument("BackgroundColorCalculator: image is null."); + } } } // namespace uint8_t BackgroundColorCalculator::calcDominantLevel(const int* hist) { - int integral_hist[256]; - integral_hist[0] = hist[0]; - for (int i = 1; i < 256; ++i) { - integral_hist[i] = hist[i] + integral_hist[i - 1]; - } + int integral_hist[256]; + integral_hist[0] = hist[0]; + for (int i = 1; i < 256; ++i) { + integral_hist[i] = hist[i] + integral_hist[i - 1]; + } - const int num_colors = 256; - const int window_size = 10; - - int best_pos = 0; - int best_sum = integral_hist[window_size - 1]; - for (int i = 1; i <= num_colors - window_size; ++i) { - const int sum = integral_hist[i + window_size - 1] - integral_hist[i - 1]; - if (sum > best_sum) { - best_sum = sum; - best_pos = i; - } + const int num_colors = 256; + const int window_size = 10; + + int best_pos = 0; + int best_sum = integral_hist[window_size - 1]; + for (int i = 1; i <= num_colors - window_size; ++i) { + const int sum = integral_hist[i + window_size - 1] - integral_hist[i - 1]; + if (sum > best_sum) { + best_sum = sum; + best_pos = i; } + } - int half_sum = 0; - for (int i = best_pos; i < best_pos + window_size; ++i) { - half_sum += hist[i]; - if (half_sum >= best_sum / 2) { - return static_cast(i); - } + int half_sum = 0; + for (int i = best_pos; i < best_pos + window_size; ++i) { + half_sum += hist[i]; + if (half_sum >= best_sum / 2) { + return static_cast(i); } + } - assert(!"Unreachable"); + assert(!"Unreachable"); - return 0; + return 0; } // BackgroundColorCalculator::calcDominantLevel -QColor BackgroundColorCalculator::calcDominantBackgroundColor(const QImage& img) { - checkImageIsValid(img); +QColor BackgroundColorCalculator::calcDominantBackgroundColor(const QImage& img) const { + checkImageIsValid(img); - BinaryImage background_mask(img, BinaryThreshold::otsuThreshold(img)); - if (2 * background_mask.countBlackPixels() <= (background_mask.width() * background_mask.height())) { - background_mask.invert(); - } + BinaryImage background_mask(img, BinaryThreshold::otsuThreshold(img)); + if (isBlackOnWhite(background_mask)) { + background_mask.invert(); + } - return calcDominantColor(img, background_mask); + return calcDominantColor(img, background_mask); } QColor BackgroundColorCalculator::calcDominantBackgroundColor(const QImage& img, const BinaryImage& mask, - DebugImages* dbg) { - checkImageIsValid(img); - - if (img.size() != mask.size()) { - throw std::invalid_argument("BackgroundColorCalculator: img and mask have different sizes"); - } - - BinaryImage background_mask(img, BinaryThreshold::otsuThreshold(GrayscaleHistogram(img, mask))); - if (2 * background_mask.countBlackPixels() <= mask.countBlackPixels()) { - background_mask.invert(); - } - rasterOp>(background_mask, mask); - if (dbg) { - dbg->add(background_mask, "background_mask"); - } - - return calcDominantColor(img, background_mask); + DebugImages* dbg) const { + checkImageIsValid(img); + + if (img.size() != mask.size()) { + throw std::invalid_argument("BackgroundColorCalculator: img and mask have different sizes"); + } + + BinaryImage background_mask(img, BinaryThreshold::otsuThreshold(GrayscaleHistogram(img, mask))); + if (isBlackOnWhite(background_mask, mask)) { + background_mask.invert(); + } + rasterOp>(background_mask, mask); + if (dbg) { + dbg->add(background_mask, "background_mask"); + } + + return calcDominantColor(img, background_mask); } QColor BackgroundColorCalculator::calcDominantBackgroundColor(const QImage& img, const QPolygonF& crop_area, - DebugImages* dbg) { - checkImageIsValid(img); + DebugImages* dbg) const { + checkImageIsValid(img); - if (crop_area.intersected(QRectF(img.rect())).isEmpty()) { - throw std::invalid_argument("BackgroundColorCalculator: the cropping area is wrong."); - } + if (crop_area.intersected(QRectF(img.rect())).isEmpty()) { + throw std::invalid_argument("BackgroundColorCalculator: the cropping area is wrong."); + } - BinaryImage mask(img.size(), BLACK); - PolygonRasterizer::fillExcept(mask, WHITE, crop_area, Qt::WindingFill); + BinaryImage mask(img.size(), BLACK); + PolygonRasterizer::fillExcept(mask, WHITE, crop_area, Qt::WindingFill); - return calcDominantBackgroundColor(img, mask, dbg); + return calcDominantBackgroundColor(img, mask, dbg); } QColor BackgroundColorCalculator::calcDominantColor(const QImage& img, const BinaryImage& background_mask) { - if ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()) { - const GrayscaleHistogram hist(img, background_mask); - int raw_hist[256]; - grayHistToArray(raw_hist, hist); - uint8_t dominant_gray = calcDominantLevel(raw_hist); - - return QColor(dominant_gray, dominant_gray, dominant_gray); - } else { - const RgbHistogram hist(img, background_mask); - uint8_t dominant_red = calcDominantLevel(hist.redChannel()); - uint8_t dominant_green = calcDominantLevel(hist.greenChannel()); - uint8_t dominant_blue = calcDominantLevel(hist.blueChannel()); - - return QColor(dominant_red, dominant_green, dominant_blue); - } + if ((img.format() == QImage::Format_Indexed8) && img.isGrayscale()) { + const GrayscaleHistogram hist(img, background_mask); + int raw_hist[256]; + grayHistToArray(raw_hist, hist); + uint8_t dominant_gray = calcDominantLevel(raw_hist); + + return QColor(dominant_gray, dominant_gray, dominant_gray); + } else { + const RgbHistogram hist(img, background_mask); + uint8_t dominant_red = calcDominantLevel(hist.redChannel()); + uint8_t dominant_green = calcDominantLevel(hist.greenChannel()); + uint8_t dominant_blue = calcDominantLevel(hist.blueChannel()); + + return QColor(dominant_red, dominant_green, dominant_blue); + } +} + +BackgroundColorCalculator::BackgroundColorCalculator(bool internalBlackOnWhiteDetection) + : m_internalBlackOnWhiteDetection(internalBlackOnWhiteDetection) {} + +bool BackgroundColorCalculator::isBlackOnWhite(const BinaryImage& img) const { + if (!m_internalBlackOnWhiteDetection) { + return true; + } + return (2 * img.countBlackPixels()) <= (img.width() * img.height()); +} + +bool BackgroundColorCalculator::isBlackOnWhite(BinaryImage img, const BinaryImage& mask) const { + if (!m_internalBlackOnWhiteDetection) { + return true; + } + rasterOp>(img, mask); + return (2 * img.countBlackPixels() <= mask.countBlackPixels()); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/BackgroundColorCalculator.h b/imageproc/BackgroundColorCalculator.h index 94fc4e59c..abc60f10b 100644 --- a/imageproc/BackgroundColorCalculator.h +++ b/imageproc/BackgroundColorCalculator.h @@ -4,6 +4,7 @@ #include +#include "BinaryImage.h" class QImage; class QColor; @@ -12,22 +13,27 @@ class DebugImages; namespace imageproc { class GrayscaleHistogram; -class BinaryImage; class BackgroundColorCalculator { -public: - static QColor calcDominantBackgroundColor(const QImage& img); + public: + explicit BackgroundColorCalculator(bool internalBlackOnWhiteDetection = true); - static QColor calcDominantBackgroundColor(const QImage& img, const BinaryImage& mask, DebugImages* dbg = nullptr); + QColor calcDominantBackgroundColor(const QImage& img) const; - static QColor calcDominantBackgroundColor(const QImage& img, - const QPolygonF& crop_area, - DebugImages* dbg = nullptr); + QColor calcDominantBackgroundColor(const QImage& img, const BinaryImage& mask, DebugImages* dbg = nullptr) const; -private: - static uint8_t calcDominantLevel(const int* hist); + QColor calcDominantBackgroundColor(const QImage& img, const QPolygonF& crop_area, DebugImages* dbg = nullptr) const; - static QColor calcDominantColor(const QImage& img, const BinaryImage& background_mask); + private: + static uint8_t calcDominantLevel(const int* hist); + + static QColor calcDominantColor(const QImage& img, const BinaryImage& background_mask); + + bool isBlackOnWhite(const BinaryImage& img) const; + + bool isBlackOnWhite(BinaryImage img, const BinaryImage& mask) const; + + bool m_internalBlackOnWhiteDetection; }; } // namespace imageproc diff --git a/imageproc/BadAllocIfNull.cpp b/imageproc/BadAllocIfNull.cpp index da481cde2..9c71b8e8f 100644 --- a/imageproc/BadAllocIfNull.cpp +++ b/imageproc/BadAllocIfNull.cpp @@ -22,9 +22,9 @@ namespace imageproc { const QImage& badAllocIfNull(const QImage& image) { - if (image.isNull()) { - throw std::bad_alloc(); - } - return image; + if (image.isNull()) { + throw std::bad_alloc(); + } + return image; } } // namespace imageproc diff --git a/imageproc/Binarize.cpp b/imageproc/Binarize.cpp index 9ef983f2b..843088ba3 100644 --- a/imageproc/Binarize.cpp +++ b/imageproc/Binarize.cpp @@ -17,100 +17,100 @@ */ #include "Binarize.h" -#include "BinaryImage.h" -#include "Grayscale.h" -#include "IntegralImage.h" #include #include #include +#include "BinaryImage.h" +#include "Grayscale.h" +#include "IntegralImage.h" namespace imageproc { BinaryImage binarizeOtsu(const QImage& src) { - return BinaryImage(src, BinaryThreshold::otsuThreshold(src)); + return BinaryImage(src, BinaryThreshold::otsuThreshold(src)); } BinaryImage binarizeMokji(const QImage& src, const unsigned max_edge_width, const unsigned min_edge_magnitude) { - const BinaryThreshold threshold(BinaryThreshold::mokjiThreshold(src, max_edge_width, min_edge_magnitude)); + const BinaryThreshold threshold(BinaryThreshold::mokjiThreshold(src, max_edge_width, min_edge_magnitude)); - return BinaryImage(src, threshold); + return BinaryImage(src, threshold); } BinaryImage binarizeSauvola(const QImage& src, const QSize window_size, const double k) { - if (window_size.isEmpty()) { - throw std::invalid_argument("binarizeSauvola: invalid window_size"); + if (window_size.isEmpty()) { + throw std::invalid_argument("binarizeSauvola: invalid window_size"); + } + + if (src.isNull()) { + return BinaryImage(); + } + + const QImage gray(toGrayscale(src)); + const int w = gray.width(); + const int h = gray.height(); + + IntegralImage integral_image(w, h); + IntegralImage integral_sqimage(w, h); + + const uint8_t* gray_line = gray.bits(); + const int gray_bpl = gray.bytesPerLine(); + + for (int y = 0; y < h; ++y, gray_line += gray_bpl) { + integral_image.beginRow(); + integral_sqimage.beginRow(); + for (int x = 0; x < w; ++x) { + const uint32_t pixel = gray_line[x]; + integral_image.push(pixel); + integral_sqimage.push(pixel * pixel); } - - if (src.isNull()) { - return BinaryImage(); + } + + const int window_lower_half = window_size.height() >> 1; + const int window_upper_half = window_size.height() - window_lower_half; + const int window_left_half = window_size.width() >> 1; + const int window_right_half = window_size.width() - window_left_half; + + BinaryImage bw_img(w, h); + uint32_t* bw_line = bw_img.data(); + const int bw_wpl = bw_img.wordsPerLine(); + + gray_line = gray.bits(); + for (int y = 0; y < h; ++y) { + const int top = std::max(0, y - window_lower_half); + const int bottom = std::min(h, y + window_upper_half); // exclusive + for (int x = 0; x < w; ++x) { + const int left = std::max(0, x - window_left_half); + const int right = std::min(w, x + window_right_half); // exclusive + const int area = (bottom - top) * (right - left); + assert(area > 0); // because window_size > 0 and w > 0 and h > 0 + const QRect rect(left, top, right - left, bottom - top); + const double window_sum = integral_image.sum(rect); + const double window_sqsum = integral_sqimage.sum(rect); + + const double r_area = 1.0 / area; + const double mean = window_sum * r_area; + const double sqmean = window_sqsum * r_area; + + const double variance = sqmean - mean * mean; + const double deviation = std::sqrt(std::fabs(variance)); + + const double threshold = mean * (1.0 + k * (deviation / 128.0 - 1.0)); + + const uint32_t msb = uint32_t(1) << 31; + const uint32_t mask = msb >> (x & 31); + if (int(gray_line[x]) < threshold) { + // black + bw_line[x >> 5] |= mask; + } else { + // white + bw_line[x >> 5] &= ~mask; + } } - const QImage gray(toGrayscale(src)); - const int w = gray.width(); - const int h = gray.height(); - - IntegralImage integral_image(w, h); - IntegralImage integral_sqimage(w, h); - - const uint8_t* gray_line = gray.bits(); - const int gray_bpl = gray.bytesPerLine(); + gray_line += gray_bpl; + bw_line += bw_wpl; + } - for (int y = 0; y < h; ++y, gray_line += gray_bpl) { - integral_image.beginRow(); - integral_sqimage.beginRow(); - for (int x = 0; x < w; ++x) { - const uint32_t pixel = gray_line[x]; - integral_image.push(pixel); - integral_sqimage.push(pixel * pixel); - } - } - - const int window_lower_half = window_size.height() >> 1; - const int window_upper_half = window_size.height() - window_lower_half; - const int window_left_half = window_size.width() >> 1; - const int window_right_half = window_size.width() - window_left_half; - - BinaryImage bw_img(w, h); - uint32_t* bw_line = bw_img.data(); - const int bw_wpl = bw_img.wordsPerLine(); - - gray_line = gray.bits(); - for (int y = 0; y < h; ++y) { - const int top = std::max(0, y - window_lower_half); - const int bottom = std::min(h, y + window_upper_half); // exclusive - for (int x = 0; x < w; ++x) { - const int left = std::max(0, x - window_left_half); - const int right = std::min(w, x + window_right_half); // exclusive - const int area = (bottom - top) * (right - left); - assert(area > 0); // because window_size > 0 and w > 0 and h > 0 - const QRect rect(left, top, right - left, bottom - top); - const double window_sum = integral_image.sum(rect); - const double window_sqsum = integral_sqimage.sum(rect); - - const double r_area = 1.0 / area; - const double mean = window_sum * r_area; - const double sqmean = window_sqsum * r_area; - - const double variance = sqmean - mean * mean; - const double deviation = std::sqrt(std::fabs(variance)); - - const double threshold = mean * (1.0 + k * (deviation / 128.0 - 1.0)); - - const uint32_t msb = uint32_t(1) << 31; - const uint32_t mask = msb >> (x & 31); - if (int(gray_line[x]) < threshold) { - // black - bw_line[x >> 5] |= mask; - } else { - // white - bw_line[x >> 5] &= ~mask; - } - } - - gray_line += gray_bpl; - bw_line += bw_wpl; - } - - return bw_img; + return bw_img; } // binarizeSauvola BinaryImage binarizeWolf(const QImage& src, @@ -118,101 +118,101 @@ BinaryImage binarizeWolf(const QImage& src, const unsigned char lower_bound, const unsigned char upper_bound, const double k) { - if (window_size.isEmpty()) { - throw std::invalid_argument("binarizeWolf: invalid window_size"); + if (window_size.isEmpty()) { + throw std::invalid_argument("binarizeWolf: invalid window_size"); + } + + if (src.isNull()) { + return BinaryImage(); + } + + const QImage gray(toGrayscale(src)); + const int w = gray.width(); + const int h = gray.height(); + + IntegralImage integral_image(w, h); + IntegralImage integral_sqimage(w, h); + + const uint8_t* gray_line = gray.bits(); + const int gray_bpl = gray.bytesPerLine(); + + uint32_t min_gray_level = 255; + + for (int y = 0; y < h; ++y, gray_line += gray_bpl) { + integral_image.beginRow(); + integral_sqimage.beginRow(); + for (int x = 0; x < w; ++x) { + const uint32_t pixel = gray_line[x]; + integral_image.push(pixel); + integral_sqimage.push(pixel * pixel); + min_gray_level = std::min(min_gray_level, pixel); } - - if (src.isNull()) { - return BinaryImage(); + } + + const int window_lower_half = window_size.height() >> 1; + const int window_upper_half = window_size.height() - window_lower_half; + const int window_left_half = window_size.width() >> 1; + const int window_right_half = window_size.width() - window_left_half; + + std::vector means(w * h, 0); + std::vector deviations(w * h, 0); + + double max_deviation = 0; + + for (int y = 0; y < h; ++y) { + const int top = std::max(0, y - window_lower_half); + const int bottom = std::min(h, y + window_upper_half); // exclusive + for (int x = 0; x < w; ++x) { + const int left = std::max(0, x - window_left_half); + const int right = std::min(w, x + window_right_half); // exclusive + const int area = (bottom - top) * (right - left); + assert(area > 0); // because window_size > 0 and w > 0 and h > 0 + const QRect rect(left, top, right - left, bottom - top); + const double window_sum = integral_image.sum(rect); + const double window_sqsum = integral_sqimage.sum(rect); + + const double r_area = 1.0 / area; + const double mean = window_sum * r_area; + const double sqmean = window_sqsum * r_area; + + const double variance = sqmean - mean * mean; + const double deviation = std::sqrt(std::fabs(variance)); + max_deviation = std::max(max_deviation, deviation); + means[w * y + x] = (float) mean; + deviations[w * y + x] = (float) deviation; } - - const QImage gray(toGrayscale(src)); - const int w = gray.width(); - const int h = gray.height(); - - IntegralImage integral_image(w, h); - IntegralImage integral_sqimage(w, h); - - const uint8_t* gray_line = gray.bits(); - const int gray_bpl = gray.bytesPerLine(); - - uint32_t min_gray_level = 255; - - for (int y = 0; y < h; ++y, gray_line += gray_bpl) { - integral_image.beginRow(); - integral_sqimage.beginRow(); - for (int x = 0; x < w; ++x) { - const uint32_t pixel = gray_line[x]; - integral_image.push(pixel); - integral_sqimage.push(pixel * pixel); - min_gray_level = std::min(min_gray_level, pixel); - } - } - - const int window_lower_half = window_size.height() >> 1; - const int window_upper_half = window_size.height() - window_lower_half; - const int window_left_half = window_size.width() >> 1; - const int window_right_half = window_size.width() - window_left_half; - - std::vector means(w * h, 0); - std::vector deviations(w * h, 0); - - double max_deviation = 0; - - for (int y = 0; y < h; ++y) { - const int top = std::max(0, y - window_lower_half); - const int bottom = std::min(h, y + window_upper_half); // exclusive - for (int x = 0; x < w; ++x) { - const int left = std::max(0, x - window_left_half); - const int right = std::min(w, x + window_right_half); // exclusive - const int area = (bottom - top) * (right - left); - assert(area > 0); // because window_size > 0 and w > 0 and h > 0 - const QRect rect(left, top, right - left, bottom - top); - const double window_sum = integral_image.sum(rect); - const double window_sqsum = integral_sqimage.sum(rect); - - const double r_area = 1.0 / area; - const double mean = window_sum * r_area; - const double sqmean = window_sqsum * r_area; - - const double variance = sqmean - mean * mean; - const double deviation = std::sqrt(std::fabs(variance)); - max_deviation = std::max(max_deviation, deviation); - means[w * y + x] = (float) mean; - deviations[w * y + x] = (float) deviation; - } - } - - // TODO: integral images can be disposed at this point. - - BinaryImage bw_img(w, h); - uint32_t* bw_line = bw_img.data(); - const int bw_wpl = bw_img.wordsPerLine(); - - gray_line = gray.bits(); - for (int y = 0; y < h; ++y, gray_line += gray_bpl, bw_line += bw_wpl) { - for (int x = 0; x < w; ++x) { - const float mean = means[y * w + x]; - const float deviation = deviations[y * w + x]; - const double a = 1.0 - deviation / max_deviation; - const double threshold = mean - k * a * (mean - min_gray_level); - - const uint32_t msb = uint32_t(1) << 31; - const uint32_t mask = msb >> (x & 31); - if ((gray_line[x] < lower_bound) || ((gray_line[x] <= upper_bound) && (int(gray_line[x]) < threshold))) { - // black - bw_line[x >> 5] |= mask; - } else { - // white - bw_line[x >> 5] &= ~mask; - } - } + } + + // TODO: integral images can be disposed at this point. + + BinaryImage bw_img(w, h); + uint32_t* bw_line = bw_img.data(); + const int bw_wpl = bw_img.wordsPerLine(); + + gray_line = gray.bits(); + for (int y = 0; y < h; ++y, gray_line += gray_bpl, bw_line += bw_wpl) { + for (int x = 0; x < w; ++x) { + const float mean = means[y * w + x]; + const float deviation = deviations[y * w + x]; + const double a = 1.0 - deviation / max_deviation; + const double threshold = mean - k * a * (mean - min_gray_level); + + const uint32_t msb = uint32_t(1) << 31; + const uint32_t mask = msb >> (x & 31); + if ((gray_line[x] < lower_bound) || ((gray_line[x] <= upper_bound) && (int(gray_line[x]) < threshold))) { + // black + bw_line[x >> 5] |= mask; + } else { + // white + bw_line[x >> 5] &= ~mask; + } } + } - return bw_img; + return bw_img; } // binarizeWolf BinaryImage peakThreshold(const QImage& image) { - return BinaryImage(image, BinaryThreshold::peakThreshold(image)); + return BinaryImage(image, BinaryThreshold::peakThreshold(image)); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/BinaryImage.cpp b/imageproc/BinaryImage.cpp index de402989e..505029e99 100644 --- a/imageproc/BinaryImage.cpp +++ b/imageproc/BinaryImage.cpp @@ -16,484 +16,470 @@ along with this program. If not, see . */ -#include #include "BinaryImage.h" -#include "ByteOrder.h" -#include "BitOps.h" #include #include #include -#include -#include -#include #include +#include +#include #include #include #include -#include +#include +#include +#include +#include "BitOps.h" +#include "ByteOrder.h" namespace imageproc { class BinaryImage::SharedData { -private: - /** - * Resolves the ambiguity of: - * \code - * void SharedData::operator delete(void*, size_t); - * \endcode - * Which may be interpreted as both a placement and non-placement delete. - */ - struct NumWords { - size_t numWords; - - explicit NumWords(size_t num_words) : numWords(num_words) { - } - }; + private: + /** + * Resolves the ambiguity of: + * \code + * void SharedData::operator delete(void*, size_t); + * \endcode + * Which may be interpreted as both a placement and non-placement delete. + */ + struct NumWords { + size_t numWords; -public: - static SharedData* create(size_t num_words) { - return new (NumWords(num_words)) SharedData(); - } + explicit NumWords(size_t num_words) : numWords(num_words) {} + }; - uint32_t* data() { - return m_data; - } + public: + static SharedData* create(size_t num_words) { return new (NumWords(num_words)) SharedData(); } - const uint32_t* data() const { - return m_data; - } + uint32_t* data() { return m_data; } - bool isShared() const { - return m_refCounter.fetchAndAddRelaxed(0) > 1; - } + const uint32_t* data() const { return m_data; } - void ref() const { - m_refCounter.ref(); - } + bool isShared() const { return m_counter.fetchAndAddRelaxed(0) > 1; } - void unref() const; + void ref() const { m_counter.ref(); } - static void* operator new(size_t size, NumWords num_words); + void unref() const; - static void operator delete(void* addr, NumWords num_words); + static void* operator new(size_t size, NumWords num_words); -private: - SharedData() : m_refCounter(1) { - } + static void operator delete(void* addr, NumWords num_words); - SharedData& operator=(const SharedData&) = delete; // forbidden + private: + SharedData() : m_counter(1) {} - mutable QAtomicInt m_refCounter; - uint32_t m_data[1]{}; // more data follows + SharedData& operator=(const SharedData&) = delete; // forbidden + + mutable QAtomicInt m_counter; + uint32_t m_data[1]{}; // more data follows }; -BinaryImage::BinaryImage() : m_pData(nullptr), m_width(0), m_height(0), m_wpl(0) { -} +BinaryImage::BinaryImage() : m_data(nullptr), m_width(0), m_height(0), m_wpl(0) {} BinaryImage::BinaryImage(const int width, const int height) - : m_width(width), m_height(height), m_wpl((width + 31) / 32) { - if ((m_width > 0) && (m_height > 0)) { - m_pData = SharedData::create(m_height * m_wpl); - } else { - throw std::invalid_argument("BinaryImage dimensions are wrong"); - } + : m_width(width), m_height(height), m_wpl((width + 31) / 32) { + if ((m_width > 0) && (m_height > 0)) { + m_data = SharedData::create(m_height * m_wpl); + } else { + throw std::invalid_argument("BinaryImage dimensions are wrong"); + } } BinaryImage::BinaryImage(const QSize size) - : m_width(size.width()), m_height(size.height()), m_wpl((size.width() + 31) / 32) { - if ((m_width > 0) && (m_height > 0)) { - m_pData = SharedData::create(m_height * m_wpl); - } else { - throw std::invalid_argument("BinaryImage dimensions are wrong"); - } + : m_width(size.width()), m_height(size.height()), m_wpl((size.width() + 31) / 32) { + if ((m_width > 0) && (m_height > 0)) { + m_data = SharedData::create(m_height * m_wpl); + } else { + throw std::invalid_argument("BinaryImage dimensions are wrong"); + } } BinaryImage::BinaryImage(const int width, const int height, const BWColor color) - : m_width(width), m_height(height), m_wpl((width + 31) / 32) { - if ((m_width > 0) && (m_height > 0)) { - m_pData = SharedData::create(m_height * m_wpl); - } else { - throw std::invalid_argument("BinaryImage dimensions are wrong"); - } - fill(color); + : m_width(width), m_height(height), m_wpl((width + 31) / 32) { + if ((m_width > 0) && (m_height > 0)) { + m_data = SharedData::create(m_height * m_wpl); + } else { + throw std::invalid_argument("BinaryImage dimensions are wrong"); + } + fill(color); } BinaryImage::BinaryImage(const QSize size, const BWColor color) - : m_width(size.width()), m_height(size.height()), m_wpl((size.width() + 31) / 32) { - if ((m_width > 0) && (m_height > 0)) { - m_pData = SharedData::create(m_height * m_wpl); - } else { - throw std::invalid_argument("BinaryImage dimensions are wrong"); - } - fill(color); + : m_width(size.width()), m_height(size.height()), m_wpl((size.width() + 31) / 32) { + if ((m_width > 0) && (m_height > 0)) { + m_data = SharedData::create(m_height * m_wpl); + } else { + throw std::invalid_argument("BinaryImage dimensions are wrong"); + } + fill(color); } BinaryImage::BinaryImage(const int width, const int height, SharedData* const data) - : m_pData(data), m_width(width), m_height(height), m_wpl((width + 31) / 32) { -} + : m_data(data), m_width(width), m_height(height), m_wpl((width + 31) / 32) {} BinaryImage::BinaryImage(const BinaryImage& other) - : m_pData(other.m_pData), m_width(other.m_width), m_height(other.m_height), m_wpl(other.m_wpl) { - if (m_pData) { - m_pData->ref(); - } + : m_data(other.m_data), m_width(other.m_width), m_height(other.m_height), m_wpl(other.m_wpl) { + if (m_data) { + m_data->ref(); + } } BinaryImage::BinaryImage(const QImage& image, const BinaryThreshold threshold) - : m_pData(nullptr), m_width(0), m_height(0), m_wpl(0) { - const QRect image_rect(image.rect()); - - switch (image.format()) { - case QImage::Format_Invalid: - break; - case QImage::Format_Mono: - *this = fromMono(image); - break; - case QImage::Format_MonoLSB: - *this = fromMonoLSB(image); - break; - case QImage::Format_Indexed8: - *this = fromIndexed8(image, image_rect, threshold); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - *this = fromRgb32(image, image_rect, threshold); - break; - case QImage::Format_ARGB32_Premultiplied: - *this = fromArgb32Premultiplied(image, image_rect, threshold); - break; - case QImage::Format_RGB16: - *this = fromRgb16(image, image_rect, threshold); - break; - default: - throw std::runtime_error("Unsupported QImage format"); - } + : m_data(nullptr), m_width(0), m_height(0), m_wpl(0) { + const QRect image_rect(image.rect()); + + switch (image.format()) { + case QImage::Format_Invalid: + break; + case QImage::Format_Mono: + *this = fromMono(image); + break; + case QImage::Format_MonoLSB: + *this = fromMonoLSB(image); + break; + case QImage::Format_Indexed8: + *this = fromIndexed8(image, image_rect, threshold); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + *this = fromRgb32(image, image_rect, threshold); + break; + case QImage::Format_ARGB32_Premultiplied: + *this = fromArgb32Premultiplied(image, image_rect, threshold); + break; + case QImage::Format_RGB16: + *this = fromRgb16(image, image_rect, threshold); + break; + default: + throw std::runtime_error("Unsupported QImage format"); + } } BinaryImage::BinaryImage(const QImage& image, const QRect& rect, const BinaryThreshold threshold) - : m_pData(nullptr), m_width(0), m_height(0), m_wpl(0) { - if (rect.isEmpty()) { - return; - } else if (rect.intersected(image.rect()) != rect) { - throw std::invalid_argument("BinaryImage: rect exceedes the QImage"); - } - - switch (image.format()) { - case QImage::Format_Invalid: - break; - case QImage::Format_Mono: - *this = fromMono(image, rect); - break; - case QImage::Format_MonoLSB: - *this = fromMonoLSB(image, rect); - break; - case QImage::Format_Indexed8: - *this = fromIndexed8(image, rect, threshold); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - *this = fromRgb32(image, rect, threshold); - break; - case QImage::Format_ARGB32_Premultiplied: - *this = fromArgb32Premultiplied(image, rect, threshold); - break; - case QImage::Format_RGB16: - *this = fromRgb16(image, rect, threshold); - break; - default: - throw std::runtime_error("BinaryImage: Unsupported QImage format"); - } + : m_data(nullptr), m_width(0), m_height(0), m_wpl(0) { + if (rect.isEmpty()) { + return; + } else if (rect.intersected(image.rect()) != rect) { + throw std::invalid_argument("BinaryImage: rect exceedes the QImage"); + } + + switch (image.format()) { + case QImage::Format_Invalid: + break; + case QImage::Format_Mono: + *this = fromMono(image, rect); + break; + case QImage::Format_MonoLSB: + *this = fromMonoLSB(image, rect); + break; + case QImage::Format_Indexed8: + *this = fromIndexed8(image, rect, threshold); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + *this = fromRgb32(image, rect, threshold); + break; + case QImage::Format_ARGB32_Premultiplied: + *this = fromArgb32Premultiplied(image, rect, threshold); + break; + case QImage::Format_RGB16: + *this = fromRgb16(image, rect, threshold); + break; + default: + throw std::runtime_error("BinaryImage: Unsupported QImage format"); + } } BinaryImage::~BinaryImage() { - if (m_pData) { - m_pData->unref(); - } + if (m_data) { + m_data->unref(); + } } BinaryImage& BinaryImage::operator=(const BinaryImage& other) { - BinaryImage(other).swap(*this); + BinaryImage(other).swap(*this); - return *this; + return *this; } void BinaryImage::swap(BinaryImage& other) { - std::swap(m_pData, other.m_pData); - std::swap(m_width, other.m_width); - std::swap(m_height, other.m_height); - std::swap(m_wpl, other.m_wpl); + std::swap(m_data, other.m_data); + std::swap(m_width, other.m_width); + std::swap(m_height, other.m_height); + std::swap(m_wpl, other.m_wpl); } void BinaryImage::invert() { - if (isNull()) { - return; - } - - const size_t num_words = m_height * m_wpl; + if (isNull()) { + return; + } - assert(m_pData); - if (!m_pData->isShared()) { - // In-place operation - uint32_t* data = this->data(); - for (size_t i = 0; i < num_words; ++i, ++data) { - *data = ~*data; - } - } else { - SharedData* new_data = SharedData::create(num_words); + const size_t num_words = m_height * m_wpl; - const uint32_t* src_data = m_pData->data(); - uint32_t* dst_data = new_data->data(); - for (size_t i = 0; i < num_words; ++i, ++src_data, ++dst_data) { - *dst_data = ~*src_data; - } + assert(m_data); + if (!m_data->isShared()) { + // In-place operation + uint32_t* data = this->data(); + for (size_t i = 0; i < num_words; ++i, ++data) { + *data = ~*data; + } + } else { + SharedData* new_data = SharedData::create(num_words); - m_pData->unref(); - m_pData = new_data; + const uint32_t* src_data = m_data->data(); + uint32_t* dst_data = new_data->data(); + for (size_t i = 0; i < num_words; ++i, ++src_data, ++dst_data) { + *dst_data = ~*src_data; } + + m_data->unref(); + m_data = new_data; + } } BinaryImage BinaryImage::inverted() const { - if (isNull()) { - return BinaryImage(); - } + if (isNull()) { + return BinaryImage(); + } - const size_t num_words = m_height * m_wpl; - SharedData* new_data = SharedData::create(num_words); + const size_t num_words = m_height * m_wpl; + SharedData* new_data = SharedData::create(num_words); - const uint32_t* src_data = m_pData->data(); - uint32_t* dst_data = new_data->data(); - for (size_t i = 0; i < num_words; ++i, ++src_data, ++dst_data) { - *dst_data = ~*src_data; - } + const uint32_t* src_data = m_data->data(); + uint32_t* dst_data = new_data->data(); + for (size_t i = 0; i < num_words; ++i, ++src_data, ++dst_data) { + *dst_data = ~*src_data; + } - return BinaryImage(m_width, m_height, new_data); + return BinaryImage(m_width, m_height, new_data); } void BinaryImage::fill(const BWColor color) { - if (isNull()) { - throw std::logic_error("Attempt to fill a null BinaryImage!"); - } + if (isNull()) { + throw std::logic_error("Attempt to fill a null BinaryImage!"); + } - const int pattern = (color == BLACK) ? ~0 : 0; - memset(data(), pattern, m_height * m_wpl * 4); + const int pattern = (color == BLACK) ? ~0 : 0; + memset(data(), pattern, m_height * m_wpl * 4); } void BinaryImage::fill(const QRect& rect, const BWColor color) { - if (rect.isEmpty()) { - return; - } + if (rect.isEmpty()) { + return; + } - fillRectImpl(data(), rect.intersected(this->rect()), color); + fillRectImpl(data(), rect.intersected(this->rect()), color); } void BinaryImage::fillExcept(const QRect& rect, const BWColor color) { - if (isNull()) { - throw std::logic_error("Attempt to fill a null BinaryImage!"); - } - - if (rect.contains(this->rect())) { - return; - } - - const QRect bounded_rect(rect.intersected(this->rect())); - if (bounded_rect.isEmpty()) { - fill(color); - - return; - } + if (isNull()) { + throw std::logic_error("Attempt to fill a null BinaryImage!"); + } - const int pattern = (color == BLACK) ? ~0 : 0; - uint32_t* const data = this->data(); // this will call copyIfShared() - if (bounded_rect.top() > 0) { - memset(data, pattern, bounded_rect.top() * m_wpl * 4); - } - - int y_top = bounded_rect.top(); - if (bounded_rect.left() > 0) { - const QRect left_rect(0, y_top, bounded_rect.left(), bounded_rect.height()); - fillRectImpl(data, left_rect, color); - } + if (rect.contains(this->rect())) { + return; + } - const int x_left = bounded_rect.left() + bounded_rect.width(); - if (x_left < m_width) { - const QRect right_rect(x_left, y_top, m_width - x_left, bounded_rect.height()); - fillRectImpl(data, right_rect, color); - } + const QRect bounded_rect(rect.intersected(this->rect())); + if (bounded_rect.isEmpty()) { + fill(color); - y_top = bounded_rect.top() + bounded_rect.height(); - if (y_top < m_height) { - memset(data + y_top * m_wpl, pattern, (m_height - y_top) * m_wpl * 4); - } + return; + } + + const int pattern = (color == BLACK) ? ~0 : 0; + uint32_t* const data = this->data(); // this will call copyIfShared() + if (bounded_rect.top() > 0) { + memset(data, pattern, bounded_rect.top() * m_wpl * 4); + } + + int y_top = bounded_rect.top(); + if (bounded_rect.left() > 0) { + const QRect left_rect(0, y_top, bounded_rect.left(), bounded_rect.height()); + fillRectImpl(data, left_rect, color); + } + + const int x_left = bounded_rect.left() + bounded_rect.width(); + if (x_left < m_width) { + const QRect right_rect(x_left, y_top, m_width - x_left, bounded_rect.height()); + fillRectImpl(data, right_rect, color); + } + + y_top = bounded_rect.top() + bounded_rect.height(); + if (y_top < m_height) { + memset(data + y_top * m_wpl, pattern, (m_height - y_top) * m_wpl * 4); + } } // BinaryImage::fillExcept void BinaryImage::fillFrame(const QRect& outer_rect, const QRect& inner_rect, const BWColor color) { - if (isNull()) { - throw std::logic_error("Attempt to fill a null BinaryImage!"); - } - - const QRect bounded_outer_rect(outer_rect.intersected(this->rect())); - const QRect bounded_inner_rect(inner_rect.intersected(bounded_outer_rect)); - if (bounded_inner_rect == bounded_outer_rect) { - return; - } else if (bounded_inner_rect.isEmpty()) { - fill(bounded_outer_rect, color); - - return; - } - - uint32_t* const data = this->data(); - - QRect top_rect(bounded_outer_rect); - top_rect.setBottom(bounded_inner_rect.top() - 1); - if (top_rect.height() != 0) { - fillRectImpl(data, top_rect, color); - } - - QRect left_rect(bounded_inner_rect); - left_rect.setLeft(bounded_outer_rect.left()); - left_rect.setRight(bounded_inner_rect.left() - 1); - if (left_rect.width() != 0) { - fillRectImpl(data, left_rect, color); - } - - QRect right_rect(bounded_inner_rect); - right_rect.setRight(bounded_outer_rect.right()); - right_rect.setLeft(bounded_inner_rect.right() + 1); - if (right_rect.width() != 0) { - fillRectImpl(data, right_rect, color); - } - - QRect bottom_rect(bounded_outer_rect); - bottom_rect.setTop(bounded_inner_rect.bottom() + 1); - if (bottom_rect.height() != 0) { - fillRectImpl(data, bottom_rect, color); - } + if (isNull()) { + throw std::logic_error("Attempt to fill a null BinaryImage!"); + } + + const QRect bounded_outer_rect(outer_rect.intersected(this->rect())); + const QRect bounded_inner_rect(inner_rect.intersected(bounded_outer_rect)); + if (bounded_inner_rect == bounded_outer_rect) { + return; + } else if (bounded_inner_rect.isEmpty()) { + fill(bounded_outer_rect, color); + + return; + } + + uint32_t* const data = this->data(); + + QRect top_rect(bounded_outer_rect); + top_rect.setBottom(bounded_inner_rect.top() - 1); + if (top_rect.height() != 0) { + fillRectImpl(data, top_rect, color); + } + + QRect left_rect(bounded_inner_rect); + left_rect.setLeft(bounded_outer_rect.left()); + left_rect.setRight(bounded_inner_rect.left() - 1); + if (left_rect.width() != 0) { + fillRectImpl(data, left_rect, color); + } + + QRect right_rect(bounded_inner_rect); + right_rect.setRight(bounded_outer_rect.right()); + right_rect.setLeft(bounded_inner_rect.right() + 1); + if (right_rect.width() != 0) { + fillRectImpl(data, right_rect, color); + } + + QRect bottom_rect(bounded_outer_rect); + bottom_rect.setTop(bounded_inner_rect.bottom() + 1); + if (bottom_rect.height() != 0) { + fillRectImpl(data, bottom_rect, color); + } } // BinaryImage::fillFrame int BinaryImage::countBlackPixels() const { - return countBlackPixels(rect()); + return countBlackPixels(rect()); } int BinaryImage::countWhitePixels() const { - return countWhitePixels(rect()); + return countWhitePixels(rect()); } int BinaryImage::countBlackPixels(const QRect& rect) const { - const QRect r(rect.intersected(this->rect())); - if (r.isEmpty()) { - return 0; - } - - const int top = r.top(); - const int bottom = r.bottom(); - const int first_word_idx = r.left() >> 5; - const int last_word_idx = r.right() >> 5; // r.right() is within rect - const uint32_t first_word_mask = ~uint32_t(0) >> (r.left() & 31); - const int last_word_unused_bits = (last_word_idx << 5) + 31 - r.right(); - const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; - const uint32_t* line = data() + top * m_wpl; - - int count = 0; - - if (first_word_idx == last_word_idx) { - if (r.width() == 1) { - for (int y = top; y <= bottom; ++y, line += m_wpl) { - count += (line[first_word_idx] >> last_word_unused_bits) & 1; - } - } else { - const uint32_t mask = first_word_mask & last_word_mask; - for (int y = top; y <= bottom; ++y, line += m_wpl) { - count += countNonZeroBits(line[first_word_idx] & mask); - } - } + const QRect r(rect.intersected(this->rect())); + if (r.isEmpty()) { + return 0; + } + + const int top = r.top(); + const int bottom = r.bottom(); + const int first_word_idx = r.left() >> 5; + const int last_word_idx = r.right() >> 5; // r.right() is within rect + const uint32_t first_word_mask = ~uint32_t(0) >> (r.left() & 31); + const int last_word_unused_bits = (last_word_idx << 5) + 31 - r.right(); + const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; + const uint32_t* line = data() + top * m_wpl; + + int count = 0; + + if (first_word_idx == last_word_idx) { + if (r.width() == 1) { + for (int y = top; y <= bottom; ++y, line += m_wpl) { + count += (line[first_word_idx] >> last_word_unused_bits) & 1; + } } else { - for (int y = top; y <= bottom; ++y, line += m_wpl) { - int idx = first_word_idx; - count += countNonZeroBits(line[idx] & first_word_mask); - for (++idx; idx != last_word_idx; ++idx) { - count += countNonZeroBits(line[idx]); - } - count += countNonZeroBits(line[idx] & last_word_mask); - } - } - - return count; + const uint32_t mask = first_word_mask & last_word_mask; + for (int y = top; y <= bottom; ++y, line += m_wpl) { + count += countNonZeroBits(line[first_word_idx] & mask); + } + } + } else { + for (int y = top; y <= bottom; ++y, line += m_wpl) { + int idx = first_word_idx; + count += countNonZeroBits(line[idx] & first_word_mask); + for (++idx; idx != last_word_idx; ++idx) { + count += countNonZeroBits(line[idx]); + } + count += countNonZeroBits(line[idx] & last_word_mask); + } + } + + return count; } // BinaryImage::countBlackPixels int BinaryImage::countWhitePixels(const QRect& rect) const { - const QRect r(rect.intersected(this->rect())); - if (r.isEmpty()) { - return 0; - } + const QRect r(rect.intersected(this->rect())); + if (r.isEmpty()) { + return 0; + } - return r.width() * r.height() - countBlackPixels(r); + return r.width() * r.height() - countBlackPixels(r); } QRect BinaryImage::contentBoundingBox(const BWColor content_color) const { - if (isNull()) { - return QRect(); - } - - const int w = m_width; - const int h = m_height; - const int wpl = m_wpl; - const int last_word_idx = (w - 1) >> 5; - const int last_word_bits = w - (last_word_idx << 5); - const int last_word_unused_bits = 32 - last_word_bits; - const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; - const uint32_t modifier = (content_color == WHITE) ? ~uint32_t(0) : 0; - const uint32_t* const data = this->data(); - - int bottom = -1; // inclusive - const uint32_t* line = data + h * wpl; - for (int y = h - 1; y >= 0; --y) { - line -= wpl; - if (!isLineMonotone(line, last_word_idx, last_word_mask, modifier)) { - bottom = y; - break; + if (isNull()) { + return QRect(); + } + + const int w = m_width; + const int h = m_height; + const int wpl = m_wpl; + const int last_word_idx = (w - 1) >> 5; + const int last_word_bits = w - (last_word_idx << 5); + const int last_word_unused_bits = 32 - last_word_bits; + const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; + const uint32_t modifier = (content_color == WHITE) ? ~uint32_t(0) : 0; + const uint32_t* const data = this->data(); + + int bottom = -1; // inclusive + const uint32_t* line = data + h * wpl; + for (int y = h - 1; y >= 0; --y) { + line -= wpl; + if (!isLineMonotone(line, last_word_idx, last_word_mask, modifier)) { + bottom = y; + break; + } + } + + if (bottom == -1) { + return QRect(); + } + + int top = bottom; + line = data; + for (int y = 0; y < bottom; ++y, line += wpl) { + if (!isLineMonotone(line, last_word_idx, last_word_mask, modifier)) { + top = y; + break; + } + } + + // These are offsets from the corresponding side. + int left = w; + int right = w; + + assert(line == data + top * wpl); + // All lines above top and below bottom are empty. + for (int y = top; y <= bottom; ++y, line += wpl) { + if (left != 0) { + left = leftmostBitOffset(line, left, modifier); + } + if (right != 0) { + const uint32_t word = (line[last_word_idx] ^ modifier) >> last_word_unused_bits; + if (word) { + const int offset = countLeastSignificantZeroes(word); + if (offset < right) { + right = offset; } + } else if (right > last_word_bits) { + right -= last_word_bits; + right = rightmostBitOffset(line + last_word_idx, right, modifier); + right += last_word_bits; + } } + } - if (bottom == -1) { - return QRect(); - } - - int top = bottom; - line = data; - for (int y = 0; y < bottom; ++y, line += wpl) { - if (!isLineMonotone(line, last_word_idx, last_word_mask, modifier)) { - top = y; - break; - } - } - - // These are offsets from the corresponding side. - int left = w; - int right = w; - - assert(line == data + top * wpl); - // All lines above top and below bottom are empty. - for (int y = top; y <= bottom; ++y, line += wpl) { - if (left != 0) { - left = leftmostBitOffset(line, left, modifier); - } - if (right != 0) { - const uint32_t word = (line[last_word_idx] ^ modifier) >> last_word_unused_bits; - if (word) { - const int offset = countLeastSignificantZeroes(word); - if (offset < right) { - right = offset; - } - } else if (right > last_word_bits) { - right -= last_word_bits; - right = rightmostBitOffset(line + last_word_idx, right, modifier); - right += last_word_bits; - } - } - } - - // bottom is inclusive, right is a positive offset from width. - return QRect(left, top, w - right - left, bottom - top + 1); + // bottom is inclusive, right is a positive offset from width. + return QRect(left, top, w - right - left, bottom - top + 1); } // BinaryImage::contentBoundingBox namespace { @@ -510,7 +496,7 @@ const int MultiplyDeBruijnBitPosition2[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, * https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup */ const inline int countConsecutiveZeroBitsTrailing(uint32_t v) { - return MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; + return MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; } /** @@ -520,673 +506,673 @@ const inline int countConsecutiveZeroBitsTrailing(uint32_t v) { * https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn */ const inline int findPositionOfTheHighestBitSet(uint32_t v) { - v |= v >> 1; // first round down to one less than a power of 2 - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; - return MultiplyDeBruijnBitPosition2[(uint32_t)(v * 0x07C4ACDDU) >> 27]; + return MultiplyDeBruijnBitPosition2[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } } // namespace void BinaryImage::rectangularizeAreas(std::vector& areas, const BWColor content_color, const int sensitivity) { - if (isNull()) { - return; - } - - const int w = m_width; - const int h = m_height; - const int wpl = m_wpl; - const int last_word_idx = (w - 1) >> 5; - const int last_word_bits = w - (last_word_idx << 5); - const int last_word_unused_bits = 32 - last_word_bits; - const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; - const uint32_t modifier = (content_color == WHITE) ? ~uint32_t(0) : 0; - const uint32_t* const data = this->data(); - - const uint32_t* line = data; - // create list of filled continuous blocks on each line - for (int y = 0; y < h; ++y, line += wpl) { - QRect area; - area.setTop(y); - area.setBottom(y); - bool area_found = false; - for (int i = 0; i <= last_word_idx; ++i) { - uint32_t word = line[i] ^ modifier; - if (i == last_word_idx) { - // The last (possibly incomplete) word. - word &= last_word_mask; - } - if (word) { - if (!area_found) { - area.setLeft((i << 5) + 31 - findPositionOfTheHighestBitSet(~line[i])); - area_found = true; - } - area.setRight(((i + 1) << 5) - 1); - } else { - if (area_found) { - uint32_t v = line[i - 1]; - if (v) { - area.setRight(area.right() - countConsecutiveZeroBitsTrailing(~v)); - } - areas.emplace_back(area); - area_found = false; - } - } + if (isNull()) { + return; + } + + const int w = m_width; + const int h = m_height; + const int wpl = m_wpl; + const int last_word_idx = (w - 1) >> 5; + const int last_word_bits = w - (last_word_idx << 5); + const int last_word_unused_bits = 32 - last_word_bits; + const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; + const uint32_t modifier = (content_color == WHITE) ? ~uint32_t(0) : 0; + const uint32_t* const data = this->data(); + + const uint32_t* line = data; + // create list of filled continuous blocks on each line + for (int y = 0; y < h; ++y, line += wpl) { + QRect area; + area.setTop(y); + area.setBottom(y); + bool area_found = false; + for (int i = 0; i <= last_word_idx; ++i) { + uint32_t word = line[i] ^ modifier; + if (i == last_word_idx) { + // The last (possibly incomplete) word. + word &= last_word_mask; + } + if (word) { + if (!area_found) { + area.setLeft((i << 5) + 31 - findPositionOfTheHighestBitSet(~line[i])); + area_found = true; } + area.setRight(((i + 1) << 5) - 1); + } else { if (area_found) { - uint32_t v = line[last_word_idx]; - if (v) { - area.setRight(area.right() - countConsecutiveZeroBitsTrailing(~v)); - } - areas.emplace_back(area); + uint32_t v = line[i - 1]; + if (v) { + area.setRight(area.right() - countConsecutiveZeroBitsTrailing(~v)); + } + areas.emplace_back(area); + area_found = false; + } + } + } + if (area_found) { + uint32_t v = line[last_word_idx]; + if (v) { + area.setRight(area.right() - countConsecutiveZeroBitsTrailing(~v)); + } + areas.emplace_back(area); + } + } + + // join adjacent blocks of areas + bool join = true; + int overlap = 16; + while (join) { + join = false; + std::vector tmp; + for (QRect area : areas) { + // take an area and try to join with something in tmp + QRect enlArea(area.adjusted(-overlap, -overlap, overlap, overlap)); + bool intersected = false; + std::vector tmp2; + for (QRect ta : tmp) { + QRect enlTA(ta.adjusted(-overlap, -overlap, overlap, overlap)); + if (enlArea.intersects(enlTA)) { + intersected = true; + join = true; + tmp2.push_back(area.united(ta)); + } else { + tmp2.push_back(ta); + } + } + if (!intersected) { + tmp2.push_back(area); + } + tmp = tmp2; + } + areas = tmp; + } + + const auto percent = (float) (sensitivity / 100.); + if (percent < 1.) { + for (QRect& area : areas) { + int word_width = area.width() >> 5; + + int left = area.left(); + int left_word = left >> 5; + int right = area.x() + area.width(); + int right_word = right >> 5; + int top = area.top(); + int bottom = area.bottom(); + + uint32_t* pdata = this->data(); + + const auto criterium = (int) (area.width() * percent); + const auto criterium_word = (int) (word_width * percent); + + // cut the dirty upper lines + for (int y = top; y < bottom; y++) { + line = pdata + m_wpl * y; + + int mword = 0; + + for (int k = left_word; k < right_word; k++) { + if (!line[k]) { + mword++; // count the totally white words + } } - } - // join adjacent blocks of areas - bool join = true; - int overlap = 16; - while (join) { - join = false; - std::vector tmp; - for (QRect area : areas) { - // take an area and try to join with something in tmp - QRect enlArea(area.adjusted(-overlap, -overlap, overlap, overlap)); - bool intersected = false; - std::vector tmp2; - for (QRect ta : tmp) { - QRect enlTA(ta.adjusted(-overlap, -overlap, overlap, overlap)); - if (enlArea.intersects(enlTA)) { - intersected = true; - join = true; - tmp2.push_back(area.united(ta)); - } else { - tmp2.push_back(ta); - } - } - if (!intersected) { - tmp2.push_back(area); - } - tmp = tmp2; + if (mword > criterium_word) { + area.setTop(y); + break; } - areas = tmp; - } + } + + // cut the dirty bottom lines + for (int y = bottom; y > top; y--) { + line = pdata + m_wpl * y; + + int mword = 0; - const auto percent = (float) (sensitivity / 100.); - if (percent < 1.) { - for (QRect& area : areas) { - int word_width = area.width() >> 5; - - int left = area.left(); - int left_word = left >> 5; - int right = area.x() + area.width(); - int right_word = right >> 5; - int top = area.top(); - int bottom = area.bottom(); - - uint32_t* pdata = this->data(); - - const auto criterium = (int) (area.width() * percent); - const auto criterium_word = (int) (word_width * percent); - - // cut the dirty upper lines - for (int y = top; y < bottom; y++) { - line = pdata + m_wpl * y; - - int mword = 0; - - for (int k = left_word; k < right_word; k++) { - if (!line[k]) { - mword++; // count the totally white words - } - } - - if (mword > criterium_word) { - area.setTop(y); - break; - } - } - - // cut the dirty bottom lines - for (int y = bottom; y > top; y--) { - line = pdata + m_wpl * y; - - int mword = 0; - - for (int k = left_word; k < right_word; k++) { - if (!line[k]) { - mword++; - } - } - - if (mword > criterium_word) { - area.setBottom(y); - break; - } - } - - for (int x = left; x < right; x++) { - int mword = 0; - - for (int y = top; y < bottom; y++) { - if (WHITE == getPixel(x, y)) { - mword++; - } - } - - if (mword > criterium) { - area.setLeft(x); - break; - } - } - - for (int x = right; x > left; x--) { - int mword = 0; - - for (int y = top; y < bottom; y++) { - if (WHITE == getPixel(x, y)) { - mword++; - } - } - - if (mword > criterium) { - area.setRight(x); - break; - } - } - - area = area.intersected(this->rect()); + for (int k = left_word; k < right_word; k++) { + if (!line[k]) { + mword++; + } } + + if (mword > criterium_word) { + area.setBottom(y); + break; + } + } + + for (int x = left; x < right; x++) { + int mword = 0; + + for (int y = top; y < bottom; y++) { + if (WHITE == getPixel(x, y)) { + mword++; + } + } + + if (mword > criterium) { + area.setLeft(x); + break; + } + } + + for (int x = right; x > left; x--) { + int mword = 0; + + for (int y = top; y < bottom; y++) { + if (WHITE == getPixel(x, y)) { + mword++; + } + } + + if (mword > criterium) { + area.setRight(x); + break; + } + } + + area = area.intersected(this->rect()); } + } } void BinaryImage::setPixel(int x, int y, BWColor color) { - uint32_t* line = this->data() + m_wpl * y; + uint32_t* line = this->data() + m_wpl * y; - (color == WHITE) ? line[x >> 5] &= ~(0x80000000 >> (x & 31)) : line[x >> 5] |= (0x80000000 >> (x & 31)); + (color == WHITE) ? line[x >> 5] &= ~(0x80000000 >> (x & 31)) : line[x >> 5] |= (0x80000000 >> (x & 31)); } BWColor BinaryImage::getPixel(int x, int y) { - uint32_t* line = this->data() + m_wpl * y; + uint32_t* line = this->data() + m_wpl * y; - return (BWColor)((line[x >> 5] >> (31 - (x & 31))) & 1); + return (BWColor)((line[x >> 5] >> (31 - (x & 31))) & 1); } uint32_t* BinaryImage::data() { - if (isNull()) { - return nullptr; - } + if (isNull()) { + return nullptr; + } - copyIfShared(); + copyIfShared(); - return m_pData->data(); + return m_data->data(); } const uint32_t* BinaryImage::data() const { - if (isNull()) { - return nullptr; - } + if (isNull()) { + return nullptr; + } - return m_pData->data(); + return m_data->data(); } QImage BinaryImage::toQImage() const { - if (isNull()) { - return QImage(); - } - - QImage dst(m_width, m_height, QImage::Format_Mono); - assert(dst.bytesPerLine() % 4 == 0); - dst.setColorCount(2); - dst.setColor(0, 0xffffffff); - dst.setColor(1, 0xff000000); - const int dst_wpl = dst.bytesPerLine() / 4; - auto* dst_line = (uint32_t*) dst.bits(); - const uint32_t* src_line = data(); - const int src_wpl = m_wpl; - - for (int i = m_height; i > 0; --i) { - for (int j = 0; j < src_wpl; ++j) { - dst_line[j] = htonl(src_line[j]); - } - src_line += src_wpl; - dst_line += dst_wpl; - } - - return dst; + if (isNull()) { + return QImage(); + } + + QImage dst(m_width, m_height, QImage::Format_Mono); + assert(dst.bytesPerLine() % 4 == 0); + dst.setColorCount(2); + dst.setColor(0, 0xffffffff); + dst.setColor(1, 0xff000000); + const int dst_wpl = dst.bytesPerLine() / 4; + auto* dst_line = (uint32_t*) dst.bits(); + const uint32_t* src_line = data(); + const int src_wpl = m_wpl; + + for (int i = m_height; i > 0; --i) { + for (int j = 0; j < src_wpl; ++j) { + dst_line[j] = htonl(src_line[j]); + } + src_line += src_wpl; + dst_line += dst_wpl; + } + + return dst; } QImage BinaryImage::toAlphaMask(const QColor& color) const { - if (isNull()) { - return QImage(); - } + if (isNull()) { + return QImage(); + } - const int alpha = color.alpha(); - const int red = (color.red() * alpha + 128) / 255; - const int green = (color.green() * alpha + 128) / 255; - const int blue = (color.blue() * alpha + 128) / 255; - const uint32_t colors[] = { - 0, // replaces white - qRgba(red, green, blue, alpha) // replaces black - }; + const int alpha = color.alpha(); + const int red = (color.red() * alpha + 128) / 255; + const int green = (color.green() * alpha + 128) / 255; + const int blue = (color.blue() * alpha + 128) / 255; + const uint32_t colors[] = { + 0, // replaces white + qRgba(red, green, blue, alpha) // replaces black + }; - const int width = m_width; - const int height = m_height; + const int width = m_width; + const int height = m_height; - QImage dst(width, height, QImage::Format_ARGB32_Premultiplied); - assert(dst.bytesPerLine() % 4 == 0); + QImage dst(width, height, QImage::Format_ARGB32_Premultiplied); + assert(dst.bytesPerLine() % 4 == 0); - const int dst_stride = dst.bytesPerLine() / 4; - auto* dst_line = (uint32_t*) dst.bits(); + const int dst_stride = dst.bytesPerLine() / 4; + auto* dst_line = (uint32_t*) dst.bits(); - const uint32_t* src_line = data(); - const int src_stride = m_wpl; + const uint32_t* src_line = data(); + const int src_stride = m_wpl; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; ++x) { - dst_line[x] = colors[(src_line[x >> 5] >> (31 - (x & 31))) & 1]; - } - src_line += src_stride; - dst_line += dst_stride; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; ++x) { + dst_line[x] = colors[(src_line[x >> 5] >> (31 - (x & 31))) & 1]; } + src_line += src_stride; + dst_line += dst_stride; + } - return dst; + return dst; } // BinaryImage::toAlphaMask void BinaryImage::copyIfShared() { - assert(m_pData); - if (!m_pData->isShared()) { - return; - } - - const size_t num_words = m_height * m_wpl; - SharedData* new_data = SharedData::create(num_words); - memcpy(new_data->data(), m_pData->data(), num_words * 4); - m_pData->unref(); - m_pData = new_data; + assert(m_data); + if (!m_data->isShared()) { + return; + } + + const size_t num_words = m_height * m_wpl; + SharedData* new_data = SharedData::create(num_words); + memcpy(new_data->data(), m_data->data(), num_words * 4); + m_data->unref(); + m_data = new_data; } void BinaryImage::fillRectImpl(uint32_t* const data, const QRect& rect, const BWColor color) { - const uint32_t pattern = (color == BLACK) ? ~uint32_t(0) : 0; + const uint32_t pattern = (color == BLACK) ? ~uint32_t(0) : 0; - if ((rect.x() == 0) && (rect.width() == m_width)) { - memset(data + rect.y() * m_wpl, pattern, rect.height() * m_wpl * 4); + if ((rect.x() == 0) && (rect.width() == m_width)) { + memset(data + rect.y() * m_wpl, pattern, rect.height() * m_wpl * 4); - return; - } + return; + } - const uint32_t first_word_idx = rect.left() >> 5; - // Note: rect.right() == rect.left() + rect.width() - 1 - const uint32_t last_word_idx = rect.right() >> 5; - const uint32_t first_word_mask = ~uint32_t(0) >> (rect.left() & 31); - const uint32_t last_word_mask = ~uint32_t(0) << (31 - (rect.right() & 31)); - uint32_t* line = data + rect.top() * m_wpl; - - if (first_word_idx == last_word_idx) { - line += first_word_idx; - const uint32_t mask = first_word_mask & last_word_mask; - for (int i = rect.height(); i > 0; --i, line += m_wpl) { - *line = (*line & ~mask) | (pattern & mask); - } + const uint32_t first_word_idx = rect.left() >> 5; + // Note: rect.right() == rect.left() + rect.width() - 1 + const uint32_t last_word_idx = rect.right() >> 5; + const uint32_t first_word_mask = ~uint32_t(0) >> (rect.left() & 31); + const uint32_t last_word_mask = ~uint32_t(0) << (31 - (rect.right() & 31)); + uint32_t* line = data + rect.top() * m_wpl; - return; + if (first_word_idx == last_word_idx) { + line += first_word_idx; + const uint32_t mask = first_word_mask & last_word_mask; + for (int i = rect.height(); i > 0; --i, line += m_wpl) { + *line = (*line & ~mask) | (pattern & mask); } - for (int i = rect.height(); i > 0; --i, line += m_wpl) { - // First word in a line. - uint32_t* pword = &line[first_word_idx]; - *pword = (*pword & ~first_word_mask) | (pattern & first_word_mask); + return; + } - uint32_t* last_pword = &line[last_word_idx]; - for (++pword; pword != last_pword; ++pword) { - *pword = pattern; - } - // Last word in a line. - *pword = (*pword & ~last_word_mask) | (pattern & last_word_mask); + for (int i = rect.height(); i > 0; --i, line += m_wpl) { + // First word in a line. + uint32_t* pword = &line[first_word_idx]; + *pword = (*pword & ~first_word_mask) | (pattern & first_word_mask); + + uint32_t* last_pword = &line[last_word_idx]; + for (++pword; pword != last_pword; ++pword) { + *pword = pattern; } + // Last word in a line. + *pword = (*pword & ~last_word_mask) | (pattern & last_word_mask); + } } // BinaryImage::fillRectImpl BinaryImage BinaryImage::fromMono(const QImage& image) { - const int width = image.width(); - const int height = image.height(); - - assert(image.bytesPerLine() % 4 == 0); - const int src_wpl = image.bytesPerLine() / 4; - const auto* src_line = (const uint32_t*) image.bits(); - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - - uint32_t modifier = ~uint32_t(0); - if (image.colorCount() >= 2) { - if (qGray(image.color(0)) > qGray(image.color(1))) { - // if color 0 is lighter than color 1 - modifier = ~modifier; - } + const int width = image.width(); + const int height = image.height(); + + assert(image.bytesPerLine() % 4 == 0); + const int src_wpl = image.bytesPerLine() / 4; + const auto* src_line = (const uint32_t*) image.bits(); + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + + uint32_t modifier = ~uint32_t(0); + if (image.colorCount() >= 2) { + if (qGray(image.color(0)) > qGray(image.color(1))) { + // if color 0 is lighter than color 1 + modifier = ~modifier; } + } - for (int i = height; i > 0; --i) { - for (int j = 0; j < dst_wpl; ++j) { - dst_line[j] = ntohl(src_line[j]) ^ modifier; - } - src_line += src_wpl; - dst_line += dst_wpl; + for (int i = height; i > 0; --i) { + for (int j = 0; j < dst_wpl; ++j) { + dst_line[j] = ntohl(src_line[j]) ^ modifier; } + src_line += src_wpl; + dst_line += dst_wpl; + } - return dst; + return dst; } BinaryImage BinaryImage::fromMono(const QImage& image, const QRect& rect) { - const int width = rect.width(); - const int height = rect.height(); - - assert(image.bytesPerLine() % 4 == 0); - const int src_wpl = image.bytesPerLine() / 4; - const auto* src_line = (const uint32_t*) image.bits(); - src_line += rect.top() * src_wpl; - src_line += rect.left() >> 5; - const int word1_unused_bits = rect.left() & 31; - const int word2_unused_bits = 32 - word1_unused_bits; - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - const int dst_last_word_unused_bits = (dst_wpl << 5) - width; - - uint32_t modifier = ~uint32_t(0); - if (image.colorCount() >= 2) { - if (qGray(image.color(0)) > qGray(image.color(1))) { - // if color 0 is lighter than color 1 - modifier = ~modifier; - } - } - - if (word1_unused_bits == 0) { - // It's not just an optimization. The code in the other branch - // is not going to work for this case because uint32_t << 32 - // does not actually clear the word. - for (int i = height; i > 0; --i) { - for (int j = 0; j < dst_wpl; ++j) { - dst_line[j] = ntohl(src_line[j]) ^ modifier; - } - src_line += src_wpl; - dst_line += dst_wpl; - } - } else { - const int last_word_idx = (width - 1) >> 5; - for (int i = height; i > 0; --i) { - int j = 0; - uint32_t next_word = ntohl(src_line[j]); - for (; j < last_word_idx; ++j) { - const uint32_t this_word = next_word; - next_word = ntohl(src_line[j + 1]); - const uint32_t dst_word = (this_word << word1_unused_bits) | (next_word >> word2_unused_bits); - dst_line[j] = dst_word ^ modifier; - } - // The last word needs special attention, because src_line[j + 1] - // might be outside of the image buffer. - uint32_t last_word = next_word << word1_unused_bits; - if (dst_last_word_unused_bits < word1_unused_bits) { - last_word |= ntohl(src_line[j + 1]) >> word2_unused_bits; - } - dst_line[j] = last_word ^ modifier; - - src_line += src_wpl; - dst_line += dst_wpl; - } + const int width = rect.width(); + const int height = rect.height(); + + assert(image.bytesPerLine() % 4 == 0); + const int src_wpl = image.bytesPerLine() / 4; + const auto* src_line = (const uint32_t*) image.bits(); + src_line += rect.top() * src_wpl; + src_line += rect.left() >> 5; + const int word1_unused_bits = rect.left() & 31; + const int word2_unused_bits = 32 - word1_unused_bits; + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int dst_last_word_unused_bits = (dst_wpl << 5) - width; + + uint32_t modifier = ~uint32_t(0); + if (image.colorCount() >= 2) { + if (qGray(image.color(0)) > qGray(image.color(1))) { + // if color 0 is lighter than color 1 + modifier = ~modifier; + } + } + + if (word1_unused_bits == 0) { + // It's not just an optimization. The code in the other branch + // is not going to work for this case because uint32_t << 32 + // does not actually clear the word. + for (int i = height; i > 0; --i) { + for (int j = 0; j < dst_wpl; ++j) { + dst_line[j] = ntohl(src_line[j]) ^ modifier; + } + src_line += src_wpl; + dst_line += dst_wpl; } - - return dst; + } else { + const int last_word_idx = (width - 1) >> 5; + for (int i = height; i > 0; --i) { + int j = 0; + uint32_t next_word = ntohl(src_line[j]); + for (; j < last_word_idx; ++j) { + const uint32_t this_word = next_word; + next_word = ntohl(src_line[j + 1]); + const uint32_t dst_word = (this_word << word1_unused_bits) | (next_word >> word2_unused_bits); + dst_line[j] = dst_word ^ modifier; + } + // The last word needs special attention, because src_line[j + 1] + // might be outside of the image buffer. + uint32_t last_word = next_word << word1_unused_bits; + if (dst_last_word_unused_bits < word1_unused_bits) { + last_word |= ntohl(src_line[j + 1]) >> word2_unused_bits; + } + dst_line[j] = last_word ^ modifier; + + src_line += src_wpl; + dst_line += dst_wpl; + } + } + + return dst; } // BinaryImage::fromMono BinaryImage BinaryImage::fromMonoLSB(const QImage& image) { - return fromMono(image.convertToFormat(QImage::Format_Mono)); + return fromMono(image.convertToFormat(QImage::Format_Mono)); } BinaryImage BinaryImage::fromMonoLSB(const QImage& image, const QRect& rect) { - return fromMono(image.convertToFormat(QImage::Format_Mono), rect); + return fromMono(image.convertToFormat(QImage::Format_Mono), rect); } BinaryImage BinaryImage::fromIndexed8(const QImage& image, const QRect& rect, const int threshold) { - const int width = rect.width(); - const int height = rect.height(); - - const int src_bpl = image.bytesPerLine(); - const uint8_t* src_line = image.bits(); - src_line += rect.top() * src_bpl + rect.left(); - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - const int last_word_idx = (width - 1) >> 5; - const int last_word_bits = width - (last_word_idx << 5); - const int last_word_unused_bits = 32 - last_word_bits; - - const int num_colors = image.colorCount(); - assert(num_colors <= 256); - int color_to_gray[256]; - int color_idx = 0; - for (; color_idx < num_colors; ++color_idx) { - color_to_gray[color_idx] = qGray(image.color(color_idx)); - } - for (; color_idx < 256; ++color_idx) { - color_to_gray[color_idx] = 0; // just in case - } - - for (int i = height; i > 0; --i) { - for (int j = 0; j < last_word_idx; ++j) { - const uint8_t* const src_pos = &src_line[j << 5]; - uint32_t word = 0; - for (int bit = 0; bit < 32; ++bit) { - word <<= 1; - if (color_to_gray[src_pos[bit]] < threshold) { - word |= uint32_t(1); - } - } - dst_line[j] = word; - } - - // Handle the last word. - const uint8_t* const src_pos = &src_line[last_word_idx << 5]; - uint32_t word = 0; - for (int bit = 0; bit < last_word_bits; ++bit) { - word <<= 1; - if (color_to_gray[src_pos[bit]] < threshold) { - word |= uint32_t(1); - } + const int width = rect.width(); + const int height = rect.height(); + + const int src_bpl = image.bytesPerLine(); + const uint8_t* src_line = image.bits(); + src_line += rect.top() * src_bpl + rect.left(); + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int last_word_idx = (width - 1) >> 5; + const int last_word_bits = width - (last_word_idx << 5); + const int last_word_unused_bits = 32 - last_word_bits; + + const int num_colors = image.colorCount(); + assert(num_colors <= 256); + int color_to_gray[256]; + int color_idx = 0; + for (; color_idx < num_colors; ++color_idx) { + color_to_gray[color_idx] = qGray(image.color(color_idx)); + } + for (; color_idx < 256; ++color_idx) { + color_to_gray[color_idx] = 0; // just in case + } + + for (int i = height; i > 0; --i) { + for (int j = 0; j < last_word_idx; ++j) { + const uint8_t* const src_pos = &src_line[j << 5]; + uint32_t word = 0; + for (int bit = 0; bit < 32; ++bit) { + word <<= 1; + if (color_to_gray[src_pos[bit]] < threshold) { + word |= uint32_t(1); } - word <<= last_word_unused_bits; - dst_line[last_word_idx] = word; + } + dst_line[j] = word; + } - dst_line += dst_wpl; - src_line += src_bpl; + // Handle the last word. + const uint8_t* const src_pos = &src_line[last_word_idx << 5]; + uint32_t word = 0; + for (int bit = 0; bit < last_word_bits; ++bit) { + word <<= 1; + if (color_to_gray[src_pos[bit]] < threshold) { + word |= uint32_t(1); + } } + word <<= last_word_unused_bits; + dst_line[last_word_idx] = word; + + dst_line += dst_wpl; + src_line += src_bpl; + } - return dst; + return dst; } // BinaryImage::fromIndexed8 static inline uint32_t thresholdRgb32(const QRgb c, const int threshold) { - // gray = (R * 11 + G * 16 + B * 5) / 32; - // return (gray < threshold) ? 1 : 0; + // gray = (R * 11 + G * 16 + B * 5) / 32; + // return (gray < threshold) ? 1 : 0; - const int sum = qRed(c) * 11 + qGreen(c) * 16 + qBlue(c) * 5; + const int sum = qRed(c) * 11 + qGreen(c) * 16 + qBlue(c) * 5; - return (sum < threshold * 32) ? 1 : 0; + return (sum < threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromRgb32(const QImage& image, const QRect& rect, const int threshold) { - const int width = rect.width(); - const int height = rect.height(); - - assert(image.bytesPerLine() % 4 == 0); - const int src_wpl = image.bytesPerLine() / 4; - const auto* src_line = (const QRgb*) image.bits(); - src_line += rect.top() * src_wpl + rect.left(); - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - const int last_word_idx = (width - 1) >> 5; - const int last_word_bits = width - (last_word_idx << 5); - const int last_word_unused_bits = 32 - last_word_bits; - - for (int i = height; i > 0; --i) { - for (int j = 0; j < last_word_idx; ++j) { - const QRgb* const src_pos = &src_line[j << 5]; - uint32_t word = 0; - for (int bit = 0; bit < 32; ++bit) { - word <<= 1; - word |= thresholdRgb32(src_pos[bit], threshold); - } - dst_line[j] = word; - } - - // Handle the last word. - const QRgb* const src_pos = &src_line[last_word_idx << 5]; - uint32_t word = 0; - for (int bit = 0; bit < last_word_bits; ++bit) { - word <<= 1; - word |= thresholdRgb32(src_pos[bit], threshold); - } - word <<= last_word_unused_bits; - dst_line[last_word_idx] = word; - - dst_line += dst_wpl; - src_line += src_wpl; - } - - return dst; + const int width = rect.width(); + const int height = rect.height(); + + assert(image.bytesPerLine() % 4 == 0); + const int src_wpl = image.bytesPerLine() / 4; + const auto* src_line = (const QRgb*) image.bits(); + src_line += rect.top() * src_wpl + rect.left(); + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int last_word_idx = (width - 1) >> 5; + const int last_word_bits = width - (last_word_idx << 5); + const int last_word_unused_bits = 32 - last_word_bits; + + for (int i = height; i > 0; --i) { + for (int j = 0; j < last_word_idx; ++j) { + const QRgb* const src_pos = &src_line[j << 5]; + uint32_t word = 0; + for (int bit = 0; bit < 32; ++bit) { + word <<= 1; + word |= thresholdRgb32(src_pos[bit], threshold); + } + dst_line[j] = word; + } + + // Handle the last word. + const QRgb* const src_pos = &src_line[last_word_idx << 5]; + uint32_t word = 0; + for (int bit = 0; bit < last_word_bits; ++bit) { + word <<= 1; + word |= thresholdRgb32(src_pos[bit], threshold); + } + word <<= last_word_unused_bits; + dst_line[last_word_idx] = word; + + dst_line += dst_wpl; + src_line += src_wpl; + } + + return dst; } // BinaryImage::fromRgb32 static inline uint32_t thresholdArgbPM(const QRgb pm, const int threshold) { - const int alpha = qAlpha(pm); - if (alpha == 0) { - return 1; // black - } + const int alpha = qAlpha(pm); + if (alpha == 0) { + return 1; // black + } - // R = R_PM * 255 / alpha; - // G = G_PM * 255 / alpha; - // B = B_PM * 255 / alpha; - // gray = (R * 11 + G * 16 + B * 5) / 32; - // return (gray < threshold) ? 1 : 0; + // R = R_PM * 255 / alpha; + // G = G_PM * 255 / alpha; + // B = B_PM * 255 / alpha; + // gray = (R * 11 + G * 16 + B * 5) / 32; + // return (gray < threshold) ? 1 : 0; - const int sum = qRed(pm) * (255 * 11) + qGreen(pm) * (255 * 16) + qBlue(pm) * (255 * 5); + const int sum = qRed(pm) * (255 * 11) + qGreen(pm) * (255 * 16) + qBlue(pm) * (255 * 5); - return (sum < alpha * threshold * 32) ? 1 : 0; + return (sum < alpha * threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromArgb32Premultiplied(const QImage& image, const QRect& rect, const int threshold) { - const int width = rect.width(); - const int height = rect.height(); - - assert(image.bytesPerLine() % 4 == 0); - const int src_wpl = image.bytesPerLine() / 4; - const auto* src_line = (const QRgb*) image.bits(); - src_line += rect.top() * src_wpl + rect.left(); - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - const int last_word_idx = (width - 1) >> 5; - const int last_word_bits = width - (last_word_idx << 5); - const int last_word_unused_bits = 32 - last_word_bits; - - for (int i = height; i > 0; --i) { - for (int j = 0; j < last_word_idx; ++j) { - const QRgb* const src_pos = &src_line[j << 5]; - uint32_t word = 0; - for (int bit = 0; bit < 32; ++bit) { - word <<= 1; - word |= thresholdArgbPM(src_pos[bit], threshold); - } - dst_line[j] = word; - } - - // Handle the last word. - const QRgb* const src_pos = &src_line[last_word_idx << 5]; - uint32_t word = 0; - for (int bit = 0; bit < last_word_bits; ++bit) { - word <<= 1; - word |= thresholdArgbPM(src_pos[bit], threshold); - } - word <<= last_word_unused_bits; - dst_line[last_word_idx] = word; - - dst_line += dst_wpl; - src_line += src_wpl; - } - - return dst; + const int width = rect.width(); + const int height = rect.height(); + + assert(image.bytesPerLine() % 4 == 0); + const int src_wpl = image.bytesPerLine() / 4; + const auto* src_line = (const QRgb*) image.bits(); + src_line += rect.top() * src_wpl + rect.left(); + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int last_word_idx = (width - 1) >> 5; + const int last_word_bits = width - (last_word_idx << 5); + const int last_word_unused_bits = 32 - last_word_bits; + + for (int i = height; i > 0; --i) { + for (int j = 0; j < last_word_idx; ++j) { + const QRgb* const src_pos = &src_line[j << 5]; + uint32_t word = 0; + for (int bit = 0; bit < 32; ++bit) { + word <<= 1; + word |= thresholdArgbPM(src_pos[bit], threshold); + } + dst_line[j] = word; + } + + // Handle the last word. + const QRgb* const src_pos = &src_line[last_word_idx << 5]; + uint32_t word = 0; + for (int bit = 0; bit < last_word_bits; ++bit) { + word <<= 1; + word |= thresholdArgbPM(src_pos[bit], threshold); + } + word <<= last_word_unused_bits; + dst_line[last_word_idx] = word; + + dst_line += dst_wpl; + src_line += src_wpl; + } + + return dst; } // BinaryImage::fromArgb32Premultiplied static inline uint32_t thresholdRgb16(const uint16_t c16, const int threshold) { - const int c = c16; + const int c = c16; - // rgb16: RRRRR GGGGGG BBBBB - // 43210 543210 43210 + // rgb16: RRRRR GGGGGG BBBBB + // 43210 543210 43210 - // r8 = r5 * 8 + r5 / 4 = RRRRR RRR - // 43210 432 - const int r8 = ((c >> 8) & 0xF8) | ((c >> 13) & 0x07); + // r8 = r5 * 8 + r5 / 4 = RRRRR RRR + // 43210 432 + const int r8 = ((c >> 8) & 0xF8) | ((c >> 13) & 0x07); - // g8 = g6 * 4 + g6 / 16 = GGGGGG GG - // 543210 54 - const int g8 = ((c >> 3) & 0xFC) | ((c >> 9) & 0x03); + // g8 = g6 * 4 + g6 / 16 = GGGGGG GG + // 543210 54 + const int g8 = ((c >> 3) & 0xFC) | ((c >> 9) & 0x03); - // b8 = b5 * 8 + b5 / 4 = BBBBB BBB - // 43210 432 - const int b8 = ((c << 3) & 0xF8) | ((c >> 2) & 0x07); + // b8 = b5 * 8 + b5 / 4 = BBBBB BBB + // 43210 432 + const int b8 = ((c << 3) & 0xF8) | ((c >> 2) & 0x07); - // gray = (R * 11 + G * 16 + B * 5) / 32; - // return (gray < threshold) ? 1 : 0; + // gray = (R * 11 + G * 16 + B * 5) / 32; + // return (gray < threshold) ? 1 : 0; - const int sum = r8 * 11 + g8 * 16 + b8 * 5; + const int sum = r8 * 11 + g8 * 16 + b8 * 5; - return (sum < threshold * 32) ? 1 : 0; + return (sum < threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromRgb16(const QImage& image, const QRect& rect, const int threshold) { - const int width = rect.width(); - const int height = rect.height(); - - assert(image.bytesPerLine() % 4 == 0); - const int src_wpl = image.bytesPerLine() / 2; - const auto* src_line = (const uint16_t*) image.bits(); - - BinaryImage dst(width, height); - const int dst_wpl = dst.wordsPerLine(); - uint32_t* dst_line = dst.data(); - const int last_word_idx = (width - 1) >> 5; - const int last_word_bits = width - (last_word_idx << 5); - - for (int i = height; i > 0; --i) { - for (int j = 0; j < last_word_idx; ++j) { - const uint16_t* const src_pos = &src_line[j << 5]; - uint32_t word = 0; - for (int bit = 0; bit < 32; ++bit) { - word <<= 1; - word |= thresholdRgb16(src_pos[bit], threshold); - } - dst_line[j] = word; - } - - // Handle the last word. - const uint16_t* const src_pos = &src_line[last_word_idx << 5]; - uint32_t word = 0; - for (int bit = 0; bit < last_word_bits; ++bit) { - word <<= 1; - word |= thresholdRgb16(src_pos[bit], threshold); - } - word <<= 32 - last_word_bits; - dst_line[last_word_idx] = word; - - dst_line += dst_wpl; - src_line += src_wpl; - } - - return dst; + const int width = rect.width(); + const int height = rect.height(); + + assert(image.bytesPerLine() % 4 == 0); + const int src_wpl = image.bytesPerLine() / 2; + const auto* src_line = (const uint16_t*) image.bits(); + + BinaryImage dst(width, height); + const int dst_wpl = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int last_word_idx = (width - 1) >> 5; + const int last_word_bits = width - (last_word_idx << 5); + + for (int i = height; i > 0; --i) { + for (int j = 0; j < last_word_idx; ++j) { + const uint16_t* const src_pos = &src_line[j << 5]; + uint32_t word = 0; + for (int bit = 0; bit < 32; ++bit) { + word <<= 1; + word |= thresholdRgb16(src_pos[bit], threshold); + } + dst_line[j] = word; + } + + // Handle the last word. + const uint16_t* const src_pos = &src_line[last_word_idx << 5]; + uint32_t word = 0; + for (int bit = 0; bit < last_word_bits; ++bit) { + word <<= 1; + word |= thresholdRgb16(src_pos[bit], threshold); + } + word <<= 32 - last_word_bits; + dst_line[last_word_idx] = word; + + dst_line += dst_wpl; + src_line += src_wpl; + } + + return dst; } // BinaryImage::fromRgb16 /** @@ -1202,113 +1188,113 @@ bool BinaryImage::isLineMonotone(const uint32_t* const line, const int last_word_idx, const uint32_t last_word_mask, const uint32_t modifier) { - for (int i = 0; i < last_word_idx; ++i) { - const uint32_t word = line[i] ^ modifier; - if (word) { - return false; - } - } - // The last (possibly incomplete) word. - const int word = (line[last_word_idx] ^ modifier) & last_word_mask; + for (int i = 0; i < last_word_idx; ++i) { + const uint32_t word = line[i] ^ modifier; if (word) { - return false; + return false; } + } + // The last (possibly incomplete) word. + const int word = (line[last_word_idx] ^ modifier) & last_word_mask; + if (word) { + return false; + } - return true; + return true; } int BinaryImage::leftmostBitOffset(const uint32_t* const line, const int offset_limit, const uint32_t modifier) { - const int num_words = (offset_limit + 31) >> 5; + const int num_words = (offset_limit + 31) >> 5; - int bit_offset = offset_limit; + int bit_offset = offset_limit; - const uint32_t* pword = line; - for (int i = 0; i < num_words; ++i, ++pword) { - const uint32_t word = *pword ^ modifier; - if (word) { - bit_offset = (i << 5) + countMostSignificantZeroes(word); - break; - } + const uint32_t* pword = line; + for (int i = 0; i < num_words; ++i, ++pword) { + const uint32_t word = *pword ^ modifier; + if (word) { + bit_offset = (i << 5) + countMostSignificantZeroes(word); + break; } + } - return std::min(bit_offset, offset_limit); + return std::min(bit_offset, offset_limit); } int BinaryImage::rightmostBitOffset(const uint32_t* const line, const int offset_limit, const uint32_t modifier) { - const int num_words = (offset_limit + 31) >> 5; + const int num_words = (offset_limit + 31) >> 5; - int bit_offset = offset_limit; + int bit_offset = offset_limit; - const uint32_t* pword = line - 1; // line points to last_word_idx, which we skip - for (int i = 0; i < num_words; ++i, --pword) { - const uint32_t word = *pword ^ modifier; - if (word) { - bit_offset = (i << 5) + countLeastSignificantZeroes(word); - break; - } + const uint32_t* pword = line - 1; // line points to last_word_idx, which we skip + for (int i = 0; i < num_words; ++i, --pword) { + const uint32_t word = *pword ^ modifier; + if (word) { + bit_offset = (i << 5) + countLeastSignificantZeroes(word); + break; } + } - return std::min(bit_offset, offset_limit); + return std::min(bit_offset, offset_limit); } bool operator==(const BinaryImage& lhs, const BinaryImage& rhs) { - if (lhs.data() == rhs.data()) { - // This will also catch the case when both are null. - return true; - } - - if ((lhs.width() != rhs.width()) || (lhs.height() != rhs.height())) { - // This will also catch the case when one is null while the other is not. + if (lhs.data() == rhs.data()) { + // This will also catch the case when both are null. + return true; + } + + if ((lhs.width() != rhs.width()) || (lhs.height() != rhs.height())) { + // This will also catch the case when one is null while the other is not. + return false; + } + + const uint32_t* lhs_line = lhs.data(); + const uint32_t* rhs_line = rhs.data(); + const int lhs_wpl = lhs.wordsPerLine(); + const int rhs_wpl = rhs.wordsPerLine(); + const int last_bit_idx = lhs.width() - 1; + const int last_word_idx = last_bit_idx / 32; + const uint32_t last_word_mask = ~uint32_t(0) << (31 - last_bit_idx % 32); + + for (int i = lhs.height(); i > 0; --i) { + int j = 0; + for (; j < last_word_idx; ++j) { + if (lhs_line[j] != rhs_line[j]) { return false; + } } - - const uint32_t* lhs_line = lhs.data(); - const uint32_t* rhs_line = rhs.data(); - const int lhs_wpl = lhs.wordsPerLine(); - const int rhs_wpl = rhs.wordsPerLine(); - const int last_bit_idx = lhs.width() - 1; - const int last_word_idx = last_bit_idx / 32; - const uint32_t last_word_mask = ~uint32_t(0) << (31 - last_bit_idx % 32); - - for (int i = lhs.height(); i > 0; --i) { - int j = 0; - for (; j < last_word_idx; ++j) { - if (lhs_line[j] != rhs_line[j]) { - return false; - } - } - // Handle the last (possibly incomplete) word. - if ((lhs_line[j] & last_word_mask) != (rhs_line[j] & last_word_mask)) { - return false; - } - - lhs_line += lhs_wpl; - rhs_line += rhs_wpl; + // Handle the last (possibly incomplete) word. + if ((lhs_line[j] & last_word_mask) != (rhs_line[j] & last_word_mask)) { + return false; } - return true; + lhs_line += lhs_wpl; + rhs_line += rhs_wpl; + } + + return true; } // == /*====================== BinaryIamge::SharedData ========================*/ void BinaryImage::SharedData::unref() const { - if (!m_refCounter.deref()) { - this->~SharedData(); - free((void*) this); - } + if (!m_counter.deref()) { + this->~SharedData(); + free((void*) this); + } } void* BinaryImage::SharedData::operator new(size_t, const NumWords num_words) { - SharedData* sd = nullptr; - void* addr = malloc(((char*) &sd->m_data[0] - (char*) sd) + num_words.numWords * 4); - if (!addr) { - throw std::bad_alloc(); - } + SharedData* sd = nullptr; + void* addr = malloc(((char*) &sd->m_data[0] - (char*) sd) + num_words.numWords * 4); + if (!addr) { + throw std::bad_alloc(); + } - return addr; + return addr; } void BinaryImage::SharedData::operator delete(void* addr, NumWords) { - free(addr); + free(addr); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/BinaryImage.h b/imageproc/BinaryImage.h index 52870bfd3..f6f64205f 100644 --- a/imageproc/BinaryImage.h +++ b/imageproc/BinaryImage.h @@ -19,13 +19,13 @@ #ifndef IMAGEPROC_BINARYIMAGE_H_ #define IMAGEPROC_BINARYIMAGE_H_ -#include "BWColor.h" -#include "BinaryThreshold.h" +#include #include #include -#include #include #include +#include "BWColor.h" +#include "BinaryThreshold.h" class QImage; @@ -47,267 +47,255 @@ namespace imageproc { * so black pixels are always represented as ones and white pixels as zeros. */ class BinaryImage { -public: - /** - * \brief Creates a null image. - */ - BinaryImage(); - - /** - * \brief Creates a new image. Image data will be uninitialized. - * - * To initialize image data, use fill(). - */ - BinaryImage(int width, int height); - - /** - * \brief Creates a new image. Image data will be uninitialized. - * - * To initialize image data, use fill(). - */ - explicit BinaryImage(QSize size); - - /** - * \brief Creates a new image filled with specified color. - */ - BinaryImage(int width, int height, BWColor color); - - /** - * \brief Creates a new image filled with specified color. - */ - BinaryImage(QSize size, BWColor color); - - /** - * \brief Create a copy of another image. Copy-on-write is used. - */ - BinaryImage(const BinaryImage& other); - - /** - * \brief Create a new image by copying the contents of a QImage. - * - * Colors in a QImage are converted to gray first, and then - * compared against the provided threshold. - */ - explicit BinaryImage(const QImage& image, BinaryThreshold threshold = BinaryThreshold(128)); - - /** - * \brief Create a new image by copying a part of a QImage. - * - * \p rect Must be within image.rect(). If \p rect is empty, - * a null BinaryImage is constructed. - * - * Colors in a QImage are converted to gray first, and then - * compared against the provided threshold. - */ - explicit BinaryImage(const QImage& image, const QRect& rect, BinaryThreshold threshold = BinaryThreshold(128)); - - ~BinaryImage(); - - /** - * \brief Replaces the current image with a copy of another one. - * - * Copy-on-write is used. This means that several images will share - * their data, until one of them accesses it in a non-const way, - * which is when a private copy of data is created for that image. - */ - BinaryImage& operator=(const BinaryImage& other); - - /** - * \brief Returns true if the image is null. - * - * Null images have zero width, height and wordsPerLine. - */ - bool isNull() const { - return !m_pData; - } - - /** - * \brief Swaps two images. - * - * This operations doesn't copy data, it just swaps pointers to it. - */ - void swap(BinaryImage& other); - - /** - * \brief Release the image data and return it as a new image. - * - * This object becomes null and its data is returned as a new image. - */ - BinaryImage release(); - - /** - * \brief Invert black and white colors. - */ - void invert(); - - /** - * \brief Creates an inverted version of this image. - */ - BinaryImage inverted() const; - - /** - * \brief Fills the whole image with either white or black color. - */ - void fill(BWColor color); - - /** - * \brief Fills a portion of the image with either white or black color. - * - * If the bounding rectangle exceedes the image area, it's automatically truncated. - */ - void fill(const QRect& rect, BWColor color); - - /** - * \brief Fills a portion of the image with either white or black color. - * - * If the bounding rectangle exceedes the image area, it's automatically truncated. - */ - void fillExcept(const QRect& rect, BWColor color); - - /** - * \brief Fills the area inside outer_rect but not inside inner_rect. - * - * If inner or outer rectangles exceed the image area, or if inner rectangle - * exceedes the outer rectangle area, they will be automatically truncated. - */ - void fillFrame(const QRect& outer_rect, const QRect& inner_rect, BWColor color); - - int countBlackPixels() const; - - int countWhitePixels() const; - - /** - * \brief Return the number of black pixels in a specified area. - * - * The specified rectangle is allowed to extend beyond the image area. - * In this case, pixels that are outside of the image won't be counted. - */ - int countBlackPixels(const QRect& rect) const; - - /** - * \brief Return the number of white pixels in a specified area. - * - * The specified rectangle is allowed to extend beyond the image area. - * In this case, pixels that are outside of the image won't be counted. - */ - int countWhitePixels(const QRect& rect) const; - - /** - * \brief Calculates the bounding box of either black or white content. - */ - QRect contentBoundingBox(BWColor content_color = BLACK) const; - - void rectangularizeAreas(std::vector& areas, BWColor content_color, int sensitivity); - - int width() const { - return m_width; - } - - int height() const { - return m_height; - } - - QRect rect() const { - return QRect(0, 0, m_width, m_height); - } - - QSize size() const { - return QSize(m_width, m_height); - } - - /** - * \brief Returns the number of 32bit words per line. - * - * This value is usually (width + 31) / 32, but it can also - * be bigger than that. - */ - int wordsPerLine() const { - return m_wpl; - } - - /** - * \brief Returns a pointer to non-const image data. - * \return Image data, or 0 in case of a null image. - * - * This may trigger copy-on-write. The pointer returned is only - * valid until you create a copy of this image. After that, both - * images will share the same data, and you will need to call - * data() again if you want to continue writing to this image. - */ - uint32_t* data(); - - /** - * \brief Returns a pointer to const image data. - * \return Image data, or 0 in case of a null image. - * - * The pointer returned is only valid until call a non-const - * version of data(), because that may trigger copy-on-write. - */ - const uint32_t* data() const; - - /** - * \brief Convert to a QImage with Format_Mono. - */ - QImage toQImage() const; + public: + /** + * \brief Creates a null image. + */ + BinaryImage(); + + /** + * \brief Creates a new image. Image data will be uninitialized. + * + * To initialize image data, use fill(). + */ + BinaryImage(int width, int height); + + /** + * \brief Creates a new image. Image data will be uninitialized. + * + * To initialize image data, use fill(). + */ + explicit BinaryImage(QSize size); + + /** + * \brief Creates a new image filled with specified color. + */ + BinaryImage(int width, int height, BWColor color); + + /** + * \brief Creates a new image filled with specified color. + */ + BinaryImage(QSize size, BWColor color); + + /** + * \brief Create a copy of another image. Copy-on-write is used. + */ + BinaryImage(const BinaryImage& other); + + /** + * \brief Create a new image by copying the contents of a QImage. + * + * Colors in a QImage are converted to gray first, and then + * compared against the provided threshold. + */ + explicit BinaryImage(const QImage& image, BinaryThreshold threshold = BinaryThreshold(128)); + + /** + * \brief Create a new image by copying a part of a QImage. + * + * \p rect Must be within image.rect(). If \p rect is empty, + * a null BinaryImage is constructed. + * + * Colors in a QImage are converted to gray first, and then + * compared against the provided threshold. + */ + explicit BinaryImage(const QImage& image, const QRect& rect, BinaryThreshold threshold = BinaryThreshold(128)); + + ~BinaryImage(); + + /** + * \brief Replaces the current image with a copy of another one. + * + * Copy-on-write is used. This means that several images will share + * their data, until one of them accesses it in a non-const way, + * which is when a private copy of data is created for that image. + */ + BinaryImage& operator=(const BinaryImage& other); + + /** + * \brief Returns true if the image is null. + * + * Null images have zero width, height and wordsPerLine. + */ + bool isNull() const { return !m_data; } + + /** + * \brief Swaps two images. + * + * This operations doesn't copy data, it just swaps pointers to it. + */ + void swap(BinaryImage& other); + + /** + * \brief Release the image data and return it as a new image. + * + * This object becomes null and its data is returned as a new image. + */ + BinaryImage release(); + + /** + * \brief Invert black and white colors. + */ + void invert(); + + /** + * \brief Creates an inverted version of this image. + */ + BinaryImage inverted() const; + + /** + * \brief Fills the whole image with either white or black color. + */ + void fill(BWColor color); + + /** + * \brief Fills a portion of the image with either white or black color. + * + * If the bounding rectangle exceedes the image area, it's automatically truncated. + */ + void fill(const QRect& rect, BWColor color); + + /** + * \brief Fills a portion of the image with either white or black color. + * + * If the bounding rectangle exceedes the image area, it's automatically truncated. + */ + void fillExcept(const QRect& rect, BWColor color); + + /** + * \brief Fills the area inside outer_rect but not inside inner_rect. + * + * If inner or outer rectangles exceed the image area, or if inner rectangle + * exceedes the outer rectangle area, they will be automatically truncated. + */ + void fillFrame(const QRect& outer_rect, const QRect& inner_rect, BWColor color); + + int countBlackPixels() const; + + int countWhitePixels() const; + + /** + * \brief Return the number of black pixels in a specified area. + * + * The specified rectangle is allowed to extend beyond the image area. + * In this case, pixels that are outside of the image won't be counted. + */ + int countBlackPixels(const QRect& rect) const; + + /** + * \brief Return the number of white pixels in a specified area. + * + * The specified rectangle is allowed to extend beyond the image area. + * In this case, pixels that are outside of the image won't be counted. + */ + int countWhitePixels(const QRect& rect) const; + + /** + * \brief Calculates the bounding box of either black or white content. + */ + QRect contentBoundingBox(BWColor content_color = BLACK) const; + + void rectangularizeAreas(std::vector& areas, BWColor content_color, int sensitivity); + + int width() const { return m_width; } + + int height() const { return m_height; } + + QRect rect() const { return QRect(0, 0, m_width, m_height); } + + QSize size() const { return QSize(m_width, m_height); } + + /** + * \brief Returns the number of 32bit words per line. + * + * This value is usually (width + 31) / 32, but it can also + * be bigger than that. + */ + int wordsPerLine() const { return m_wpl; } + + /** + * \brief Returns a pointer to non-const image data. + * \return Image data, or 0 in case of a null image. + * + * This may trigger copy-on-write. The pointer returned is only + * valid until you create a copy of this image. After that, both + * images will share the same data, and you will need to call + * data() again if you want to continue writing to this image. + */ + uint32_t* data(); + + /** + * \brief Returns a pointer to const image data. + * \return Image data, or 0 in case of a null image. + * + * The pointer returned is only valid until call a non-const + * version of data(), because that may trigger copy-on-write. + */ + const uint32_t* data() const; + + /** + * \brief Convert to a QImage with Format_Mono. + */ + QImage toQImage() const; - /** - * \brief Convert to an ARGB32_Premultiplied image, where white pixels become transparent. - * - * Opaque (black) pixels take the specified color. Colors with alpha channel are supported. - */ - QImage toAlphaMask(const QColor& color) const; + /** + * \brief Convert to an ARGB32_Premultiplied image, where white pixels become transparent. + * + * Opaque (black) pixels take the specified color. Colors with alpha channel are supported. + */ + QImage toAlphaMask(const QColor& color) const; - void setPixel(int x, int y, BWColor color); + void setPixel(int x, int y, BWColor color); - BWColor getPixel(int x, int y); + BWColor getPixel(int x, int y); -private: - class SharedData; + private: + class SharedData; - BinaryImage(int width, int height, SharedData* data); + BinaryImage(int width, int height, SharedData* data); - void copyIfShared(); + void copyIfShared(); - void fillRectImpl(uint32_t* data, const QRect& rect, BWColor color); + void fillRectImpl(uint32_t* data, const QRect& rect, BWColor color); - static BinaryImage fromMono(const QImage& image); + static BinaryImage fromMono(const QImage& image); - static BinaryImage fromMono(const QImage& image, const QRect& rect); + static BinaryImage fromMono(const QImage& image, const QRect& rect); - static BinaryImage fromMonoLSB(const QImage& image); + static BinaryImage fromMonoLSB(const QImage& image); - static BinaryImage fromMonoLSB(const QImage& image, const QRect& rect); + static BinaryImage fromMonoLSB(const QImage& image, const QRect& rect); - static BinaryImage fromIndexed8(const QImage& image, const QRect& rect, int threshold); + static BinaryImage fromIndexed8(const QImage& image, const QRect& rect, int threshold); - static BinaryImage fromRgb32(const QImage& image, const QRect& rect, int threshold); + static BinaryImage fromRgb32(const QImage& image, const QRect& rect, int threshold); - static BinaryImage fromArgb32Premultiplied(const QImage& image, const QRect& rect, int threshold); + static BinaryImage fromArgb32Premultiplied(const QImage& image, const QRect& rect, int threshold); - static BinaryImage fromRgb16(const QImage& image, const QRect& rect, int threshold); + static BinaryImage fromRgb16(const QImage& image, const QRect& rect, int threshold); - static bool isLineMonotone(const uint32_t* line, int last_word_idx, uint32_t last_word_mask, uint32_t modifier); + static bool isLineMonotone(const uint32_t* line, int last_word_idx, uint32_t last_word_mask, uint32_t modifier); - static int leftmostBitOffset(const uint32_t* line, int offset_limit, uint32_t modifier); + static int leftmostBitOffset(const uint32_t* line, int offset_limit, uint32_t modifier); - static int rightmostBitOffset(const uint32_t* line, int offset_limit, uint32_t modifier); + static int rightmostBitOffset(const uint32_t* line, int offset_limit, uint32_t modifier); - SharedData* m_pData; - int m_width; - int m_height; - int m_wpl; // words per line + SharedData* m_data; + int m_width; + int m_height; + int m_wpl; // words per line }; inline void swap(BinaryImage& o1, BinaryImage& o2) { - o1.swap(o2); + o1.swap(o2); } inline BinaryImage BinaryImage::release() { - BinaryImage new_img; - new_img.swap(*this); + BinaryImage new_img; + new_img.swap(*this); - return new_img; + return new_img; } /** @@ -319,7 +307,7 @@ bool operator==(const BinaryImage& lhs, const BinaryImage& rhs); * \brief Compares image data. */ inline bool operator!=(const BinaryImage& lhs, const BinaryImage& rhs) { - return !(lhs == rhs); + return !(lhs == rhs); } } // namespace imageproc #endif // ifndef IMAGEPROC_BINARYIMAGE_H_ diff --git a/imageproc/BinaryThreshold.cpp b/imageproc/BinaryThreshold.cpp index 84b829d7d..200691e81 100644 --- a/imageproc/BinaryThreshold.cpp +++ b/imageproc/BinaryThreshold.cpp @@ -17,173 +17,173 @@ */ #include "BinaryThreshold.h" -#include "Grayscale.h" -#include "Morphology.h" #include #include #include +#include "Grayscale.h" +#include "Morphology.h" namespace imageproc { BinaryThreshold BinaryThreshold::otsuThreshold(const QImage& image) { - return otsuThreshold(GrayscaleHistogram(image)); + return otsuThreshold(GrayscaleHistogram(image)); } BinaryThreshold BinaryThreshold::otsuThreshold(const GrayscaleHistogram& pixels_by_color) { - int32_t pixels_by_threshold[256]; - int64_t moment_by_threshold[256]; - - // Note that although BinaryThreshold is defined in such a way - // that everything below the threshold is considered black, - // this algorithm assumes that everything below *or equal* to - // the threshold is considered black. - // That is, pixels_by_threshold[10] holds the number of pixels - // in the image that have a gray_level <= 10 - - pixels_by_threshold[0] = pixels_by_color[0]; - moment_by_threshold[0] = 0; - for (int i = 1; i < 256; ++i) { - pixels_by_threshold[i] = pixels_by_threshold[i - 1] + pixels_by_color[i]; - moment_by_threshold[i] = moment_by_threshold[i - 1] + int64_t(pixels_by_color[i]) * i; + int32_t pixels_by_threshold[256]; + int64_t moment_by_threshold[256]; + + // Note that although BinaryThreshold is defined in such a way + // that everything below the threshold is considered black, + // this algorithm assumes that everything below *or equal* to + // the threshold is considered black. + // That is, pixels_by_threshold[10] holds the number of pixels + // in the image that have a gray_level <= 10 + + pixels_by_threshold[0] = pixels_by_color[0]; + moment_by_threshold[0] = 0; + for (int i = 1; i < 256; ++i) { + pixels_by_threshold[i] = pixels_by_threshold[i - 1] + pixels_by_color[i]; + moment_by_threshold[i] = moment_by_threshold[i - 1] + int64_t(pixels_by_color[i]) * i; + } + + const int total_pixels = pixels_by_threshold[255]; + const int64_t total_moment = moment_by_threshold[255]; + double max_variance = 0.0; + int first_best_threshold = -1; + int last_best_threshold = -1; + for (int i = 0; i < 256; ++i) { + const int pixels_below = pixels_by_threshold[i]; + const int pixels_above = total_pixels - pixels_below; + if ((pixels_below > 0) && (pixels_above > 0)) { // prevent division by zero + const int64_t moment_below = moment_by_threshold[i]; + const int64_t moment_above = total_moment - moment_below; + const double mean_below = (double) moment_below / pixels_below; + const double mean_above = (double) moment_above / pixels_above; + const double mean_diff = mean_below - mean_above; + const double variance = mean_diff * mean_diff * pixels_below * pixels_above; + if (variance > max_variance) { + max_variance = variance; + first_best_threshold = i; + last_best_threshold = i; + } else if (variance == max_variance) { + last_best_threshold = i; + } } + } - const int total_pixels = pixels_by_threshold[255]; - const int64_t total_moment = moment_by_threshold[255]; - double max_variance = 0.0; - int first_best_threshold = -1; - int last_best_threshold = -1; - for (int i = 0; i < 256; ++i) { - const int pixels_below = pixels_by_threshold[i]; - const int pixels_above = total_pixels - pixels_below; - if ((pixels_below > 0) && (pixels_above > 0)) { // prevent division by zero - const int64_t moment_below = moment_by_threshold[i]; - const int64_t moment_above = total_moment - moment_below; - const double mean_below = (double) moment_below / pixels_below; - const double mean_above = (double) moment_above / pixels_above; - const double mean_diff = mean_below - mean_above; - const double variance = mean_diff * mean_diff * pixels_below * pixels_above; - if (variance > max_variance) { - max_variance = variance; - first_best_threshold = i; - last_best_threshold = i; - } else if (variance == max_variance) { - last_best_threshold = i; - } - } - } - - // Compensate the "< threshold" vs "<= threshold" difference. - ++first_best_threshold; - ++last_best_threshold; + // Compensate the "< threshold" vs "<= threshold" difference. + ++first_best_threshold; + ++last_best_threshold; - // The middle between the two. - return BinaryThreshold((first_best_threshold + last_best_threshold) >> 1); + // The middle between the two. + return BinaryThreshold((first_best_threshold + last_best_threshold) >> 1); } // BinaryThreshold::otsuThreshold BinaryThreshold BinaryThreshold::peakThreshold(const QImage& image) { - return peakThreshold(GrayscaleHistogram(image)); + return peakThreshold(GrayscaleHistogram(image)); } BinaryThreshold BinaryThreshold::peakThreshold(const GrayscaleHistogram& pixels_by_color) { - int ri = 255, li = 0; - int right_peak = pixels_by_color[ri]; - int left_peak = pixels_by_color[li]; - - for (int i = 254; i >= 0; --i) { - if (pixels_by_color[i] <= right_peak) { - if (double(pixels_by_color[i]) < (double(right_peak) * 0.66)) { - break; - } - continue; - } - if (pixels_by_color[i] > right_peak) { - right_peak = pixels_by_color[i]; - ri = i; - } + int ri = 255, li = 0; + int right_peak = pixels_by_color[ri]; + int left_peak = pixels_by_color[li]; + + for (int i = 254; i >= 0; --i) { + if (pixels_by_color[i] <= right_peak) { + if (double(pixels_by_color[i]) < (double(right_peak) * 0.66)) { + break; + } + continue; } - - for (int i = 1; i <= 255; ++i) { - if (pixels_by_color[i] <= left_peak) { - if (double(pixels_by_color[i]) < (double(left_peak) * 0.66)) { - break; - } - continue; - } - if (pixels_by_color[i] > left_peak) { - left_peak = pixels_by_color[i]; - li = i; - } + if (pixels_by_color[i] > right_peak) { + right_peak = pixels_by_color[i]; + ri = i; + } + } + + for (int i = 1; i <= 255; ++i) { + if (pixels_by_color[i] <= left_peak) { + if (double(pixels_by_color[i]) < (double(left_peak) * 0.66)) { + break; + } + continue; } + if (pixels_by_color[i] > left_peak) { + left_peak = pixels_by_color[i]; + li = i; + } + } - auto threshold = static_cast(li + (ri - li) * 0.75); + auto threshold = static_cast(li + (ri - li) * 0.75); #ifdef DEBUG - int otsuThreshold = BinaryThreshold::otsuThreshold(pixels_by_color); - std::cout << "li: " << li << " leftPeak: " << left_peak << std::endl; - std::cout << "ri: " << ri << " rightPeak: " << right_peak << std::endl; - std::cout << "threshold: " << threshold << std::endl; - std::cout << "otsuThreshold: " << otsuThreshold << std::endl; + int otsuThreshold = BinaryThreshold::otsuThreshold(pixels_by_color); + std::cout << "li: " << li << " leftPeak: " << left_peak << std::endl; + std::cout << "ri: " << ri << " rightPeak: " << right_peak << std::endl; + std::cout << "threshold: " << threshold << std::endl; + std::cout << "otsuThreshold: " << otsuThreshold << std::endl; #endif - return BinaryThreshold(threshold); + return BinaryThreshold(threshold); } // BinaryThreshold::peakThreshold BinaryThreshold BinaryThreshold::mokjiThreshold(const QImage& image, const unsigned max_edge_width, const unsigned min_edge_magnitude) { - if (max_edge_width < 1) { - throw std::invalid_argument("mokjiThreshold: invalud max_edge_width"); - } - if (min_edge_magnitude < 1) { - throw std::invalid_argument("mokjiThreshold: invalid min_edge_magnitude"); + if (max_edge_width < 1) { + throw std::invalid_argument("mokjiThreshold: invalud max_edge_width"); + } + if (min_edge_magnitude < 1) { + throw std::invalid_argument("mokjiThreshold: invalid min_edge_magnitude"); + } + + const GrayImage gray(image); + + const int dilate_size = (max_edge_width + 1) * 2 - 1; + GrayImage dilated(dilateGray(gray, QSize(dilate_size, dilate_size))); + + unsigned matrix[256][256]; + memset(matrix, 0, sizeof(matrix)); + + const int w = image.width(); + const int h = image.height(); + const unsigned char* src_line = gray.data(); + const int src_stride = gray.stride(); + const unsigned char* dilated_line = dilated.data(); + const int dilated_stride = dilated.stride(); + + src_line += max_edge_width * src_stride; + dilated_line += max_edge_width * dilated_stride; + for (int y = max_edge_width; y < h - (int) max_edge_width; ++y) { + for (int x = max_edge_width; x < w - (int) max_edge_width; ++x) { + const unsigned pixel = src_line[x]; + const unsigned darkest_neighbor = dilated_line[x]; + assert(darkest_neighbor <= pixel); + + ++matrix[darkest_neighbor][pixel]; } - - const GrayImage gray(image); - - const int dilate_size = (max_edge_width + 1) * 2 - 1; - GrayImage dilated(dilateGray(gray, QSize(dilate_size, dilate_size))); - - unsigned matrix[256][256]; - memset(matrix, 0, sizeof(matrix)); - - const int w = image.width(); - const int h = image.height(); - const unsigned char* src_line = gray.data(); - const int src_stride = gray.stride(); - const unsigned char* dilated_line = dilated.data(); - const int dilated_stride = dilated.stride(); - - src_line += max_edge_width * src_stride; - dilated_line += max_edge_width * dilated_stride; - for (int y = max_edge_width; y < h - (int) max_edge_width; ++y) { - for (int x = max_edge_width; x < w - (int) max_edge_width; ++x) { - const unsigned pixel = src_line[x]; - const unsigned darkest_neighbor = dilated_line[x]; - assert(darkest_neighbor <= pixel); - - ++matrix[darkest_neighbor][pixel]; - } - src_line += src_stride; - dilated_line += dilated_stride; + src_line += src_stride; + dilated_line += dilated_stride; + } + + unsigned nominator = 0; + unsigned denominator = 0; + for (unsigned m = 0; m < 256 - min_edge_magnitude; ++m) { + for (unsigned n = m + min_edge_magnitude; n < 256; ++n) { + assert(n >= m); + + const unsigned val = matrix[m][n]; + nominator += (m + n) * val; + denominator += val; } + } - unsigned nominator = 0; - unsigned denominator = 0; - for (unsigned m = 0; m < 256 - min_edge_magnitude; ++m) { - for (unsigned n = m + min_edge_magnitude; n < 256; ++n) { - assert(n >= m); - - const unsigned val = matrix[m][n]; - nominator += (m + n) * val; - denominator += val; - } - } - - if (denominator == 0) { - return BinaryThreshold(128); - } + if (denominator == 0) { + return BinaryThreshold(128); + } - const double threshold = 0.5 * nominator / denominator; + const double threshold = 0.5 * nominator / denominator; - return BinaryThreshold((int) (threshold + 0.5)); + return BinaryThreshold((int) (threshold + 0.5)); } // BinaryThreshold::mokjiThreshold } // namespace imageproc \ No newline at end of file diff --git a/imageproc/BinaryThreshold.h b/imageproc/BinaryThreshold.h index 6a0bd7507..09f4a0d40 100644 --- a/imageproc/BinaryThreshold.h +++ b/imageproc/BinaryThreshold.h @@ -34,52 +34,47 @@ class GrayscaleHistogram; * itself is considered to be white. */ class BinaryThreshold { - // Member-wise copying is OK. -public: - /** - * \brief Finds the threshold using Otsu’s thresholding method. - */ - static BinaryThreshold otsuThreshold(const QImage& image); - - /** - * \brief Finds the threshold using Otsu’s thresholding method. - */ - static BinaryThreshold otsuThreshold(const GrayscaleHistogram& pixels_by_color); - - static BinaryThreshold peakThreshold(const QImage& image); - - static BinaryThreshold peakThreshold(const GrayscaleHistogram& pixels_by_color); - - /** - * \brief Image binarization using Mokji's global thresholding method. - * - * M. M. Mokji, S. A. R. Abu-Bakar: Adaptive Thresholding Based on - * Co-occurrence Matrix Edge Information. Asia International Conference on - * Modelling and Simulation 2007: 444-450 - * http://www.academypublisher.com/jcp/vol02/no08/jcp02084452.pdf - * - * \param image The source image. May be in any format. - * \param max_edge_width The maximum gradient length to consider. - * \param min_edge_magnitude The minimum color difference in a gradient. - * \return A black and white image. - */ - static BinaryThreshold mokjiThreshold(const QImage& image, - unsigned max_edge_width = 3, - unsigned min_edge_magnitude = 20); - - BinaryThreshold(int threshold) : m_threshold(threshold) { - } - - operator int() const { - return m_threshold; - } - - BWColor grayToBW(int gray) const { - return gray < m_threshold ? BLACK : WHITE; - } - -private: - int m_threshold; + // Member-wise copying is OK. + public: + /** + * \brief Finds the threshold using Otsu’s thresholding method. + */ + static BinaryThreshold otsuThreshold(const QImage& image); + + /** + * \brief Finds the threshold using Otsu’s thresholding method. + */ + static BinaryThreshold otsuThreshold(const GrayscaleHistogram& pixels_by_color); + + static BinaryThreshold peakThreshold(const QImage& image); + + static BinaryThreshold peakThreshold(const GrayscaleHistogram& pixels_by_color); + + /** + * \brief Image binarization using Mokji's global thresholding method. + * + * M. M. Mokji, S. A. R. Abu-Bakar: Adaptive Thresholding Based on + * Co-occurrence Matrix Edge Information. Asia International Conference on + * Modelling and Simulation 2007: 444-450 + * http://www.academypublisher.com/jcp/vol02/no08/jcp02084452.pdf + * + * \param image The source image. May be in any format. + * \param max_edge_width The maximum gradient length to consider. + * \param min_edge_magnitude The minimum color difference in a gradient. + * \return A black and white image. + */ + static BinaryThreshold mokjiThreshold(const QImage& image, + unsigned max_edge_width = 3, + unsigned min_edge_magnitude = 20); + + BinaryThreshold(int threshold) : m_threshold(threshold) {} + + operator int() const { return m_threshold; } + + BWColor grayToBW(int gray) const { return gray < m_threshold ? BLACK : WHITE; } + + private: + int m_threshold; }; } // namespace imageproc #endif // ifndef IMAGEPROC_BINARYTHRESHOLD_H_ diff --git a/imageproc/BitOps.cpp b/imageproc/BitOps.cpp index 93643d17e..6ca0c9c6e 100644 --- a/imageproc/BitOps.cpp +++ b/imageproc/BitOps.cpp @@ -20,30 +20,29 @@ namespace imageproc { namespace detail { -const unsigned char bitCounts[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, - 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, - 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, - 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, - 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, - 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, - 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; +const unsigned char bitCounts[256] + = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, + 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, + 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, + 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, + 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, + 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, + 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; const unsigned char reversedBits[256] - = {0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, - 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, - 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, - 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, - 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, - 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, - 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, - 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, - 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, - 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, - 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, - 0x3f, 0xbf, 0x7f, 0xff}; + = {0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, + 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, + 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, + 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, + 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, + 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, + 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, + 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, + 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, + 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, + 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, + 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, + 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff}; } // namespace detail } // namespace imageproc diff --git a/imageproc/BitOps.h b/imageproc/BitOps.h index 101a31173..eafea2659 100644 --- a/imageproc/BitOps.h +++ b/imageproc/BitOps.h @@ -24,159 +24,149 @@ namespace detail { extern const unsigned char bitCounts[256]; extern const unsigned char reversedBits[256]; -template +template class NonZeroBits { -public: - static unsigned char count(T val) { - return bitCounts[static_cast(val)] + NonZeroBits::count(val >> 8); - } + public: + static unsigned char count(T val) { + return bitCounts[static_cast(val)] + NonZeroBits::count(val >> 8); + } }; -template +template class NonZeroBits { -public: - static unsigned char count(T val) { - return bitCounts[static_cast(val)]; - } + public: + static unsigned char count(T val) { return bitCounts[static_cast(val)]; } }; -template +template struct ReverseBytes { - static T result(const T val) { - const int left_shift = (TotalBytes - Offset - 1) * 8; - const int right_shift = Offset * 8; + static T result(const T val) { + const int left_shift = (TotalBytes - Offset - 1) * 8; + const int right_shift = Offset * 8; - typedef unsigned char Byte; - const Byte left_byte = static_cast(val >> left_shift); - const Byte right_byte = static_cast(val >> right_shift); + typedef unsigned char Byte; + const Byte left_byte = static_cast(val >> left_shift); + const Byte right_byte = static_cast(val >> right_shift); - T res(ReverseBytes::result(val)); - res |= T(reversedBits[left_byte]) << right_shift; - res |= T(reversedBits[right_byte]) << left_shift; + T res(ReverseBytes::result(val)); + res |= T(reversedBits[left_byte]) << right_shift; + res |= T(reversedBits[right_byte]) << left_shift; - return res; - } + return res; + } }; -template +template struct ReverseBytes { - static T result(T) { - return T(); - } + static T result(T) { return T(); } }; -template +template struct ReverseBytes { - static T result(const T val) { - typedef unsigned char Byte; + static T result(const T val) { + typedef unsigned char Byte; - return T(reversedBits[static_cast(val)]); - } + return T(reversedBits[static_cast(val)]); + } }; -template +template struct StripedMaskMSB1 { - static const T value - = (((T(1) << STRIPE_LEN) - 1) << (BITS_DONE + STRIPE_LEN)) - | StripedMaskMSB1:: - value; + static const T value + = (((T(1) << STRIPE_LEN) - 1) << (BITS_DONE + STRIPE_LEN)) + | StripedMaskMSB1::value; }; -template +template struct StripedMaskMSB1 { - static const T value = 0; + static const T value = 0; }; -template +template struct StripedMaskLSB1 { - static const T value - = (((T(1) << STRIPE_LEN) - 1) << BITS_DONE) - | StripedMaskLSB1:: - value; + static const T value + = (((T(1) << STRIPE_LEN) - 1) << BITS_DONE) + | StripedMaskLSB1::value; }; -template +template struct StripedMaskLSB1 { - static const T value = 0; + static const T value = 0; }; -template +template struct MostSignificantZeroes { - static int reduce(T val, int count) { - if (T tmp = val & StripedMaskMSB1::value) { - val = tmp; - count -= STRIPE_LEN; - } - - return MostSignificantZeroes::reduce(val, count); + static int reduce(T val, int count) { + if (T tmp = val & StripedMaskMSB1::value) { + val = tmp; + count -= STRIPE_LEN; } + + return MostSignificantZeroes::reduce(val, count); + } }; -template +template struct MostSignificantZeroes { - static int reduce(T val, int count) { - return count - 1; - } + static int reduce(T val, int count) { return count - 1; } }; -template +template struct LeastSignificantZeroes { - static int reduce(T val, int count) { - if (T tmp = val & StripedMaskLSB1::value) { - val = tmp; - count -= STRIPE_LEN; - } - - return LeastSignificantZeroes::reduce(val, count); + static int reduce(T val, int count) { + if (T tmp = val & StripedMaskLSB1::value) { + val = tmp; + count -= STRIPE_LEN; } + + return LeastSignificantZeroes::reduce(val, count); + } }; -template +template struct LeastSignificantZeroes { - static int reduce(T val, int count) { - return count - 1; - } + static int reduce(T val, int count) { return count - 1; } }; } // namespace detail -template +template int countNonZeroBits(const T val) { - return detail::NonZeroBits::count(val); + return detail::NonZeroBits::count(val); } -template +template T reverseBits(const T val) { - return detail::ReverseBytes::result(val); + return detail::ReverseBytes::result(val); } -template +template int countMostSignificantZeroes(const T val) { - static const int total_bits = sizeof(T) * 8; - int zeroes = total_bits; + static const int total_bits = sizeof(T) * 8; + int zeroes = total_bits; - if (val) { - zeroes = detail::MostSignificantZeroes::reduce(val, zeroes); - } + if (val) { + zeroes = detail::MostSignificantZeroes::reduce(val, zeroes); + } - return zeroes; + return zeroes; } -template +template int countLeastSignificantZeroes(const T val) { - static const int total_bits = sizeof(T) * 8; - int zeroes = total_bits; + static const int total_bits = sizeof(T) * 8; + int zeroes = total_bits; - if (val) { - zeroes = detail::LeastSignificantZeroes::reduce(val, zeroes); - } + if (val) { + zeroes = detail::LeastSignificantZeroes::reduce(val, zeroes); + } - return zeroes; + return zeroes; } } // namespace imageproc #endif // ifndef IMAGEPROC_BITOPS_H_ diff --git a/imageproc/CMakeLists.txt b/imageproc/CMakeLists.txt index 4479efb74..f5b1b5cd0 100644 --- a/imageproc/CMakeLists.txt +++ b/imageproc/CMakeLists.txt @@ -1,61 +1,61 @@ -PROJECT(imageproc) +project(imageproc) -SET( - sources - Constants.h Constants.cpp - BinaryImage.cpp BinaryImage.h - BinaryThreshold.cpp BinaryThreshold.h - SlicedHistogram.cpp SlicedHistogram.h - ByteOrder.h BWColor.h - ConnComp.h Connectivity.h - BitOps.cpp BitOps.h - SeedFill.cpp SeedFill.h - ConnCompEraser.cpp ConnCompEraser.h - ConnCompEraserExt.cpp ConnCompEraserExt.h - GrayImage.cpp GrayImage.h - Grayscale.cpp Grayscale.h - RasterOp.h GrayRasterOp.h RasterOpGeneric.h - UpscaleIntegerTimes.cpp UpscaleIntegerTimes.h - ReduceThreshold.cpp ReduceThreshold.h - Shear.cpp Shear.h - SkewFinder.cpp SkewFinder.h - OrthogonalRotation.cpp OrthogonalRotation.h - Scale.cpp Scale.h - Transform.cpp Transform.h - Morphology.cpp Morphology.h - IntegralImage.h - Binarize.cpp Binarize.h - PolygonUtils.cpp PolygonUtils.h - PolygonRasterizer.cpp PolygonRasterizer.h - HoughLineDetector.cpp HoughLineDetector.h - GaussBlur.cpp GaussBlur.h - Sobel.h - MorphGradientDetect.cpp MorphGradientDetect.h - PolynomialLine.cpp PolynomialLine.h - PolynomialSurface.cpp PolynomialSurface.h - SavGolKernel.cpp SavGolKernel.h - SavGolFilter.cpp SavGolFilter.h - DrawOver.cpp DrawOver.h - AdjustBrightness.cpp AdjustBrightness.h - SEDM.cpp SEDM.h - ConnectivityMap.cpp ConnectivityMap.h - InfluenceMap.cpp InfluenceMap.h - MaxWhitespaceFinder.cpp MaxWhitespaceFinder.h - RastLineFinder.cpp RastLineFinder.h - ColorInterpolation.cpp ColorInterpolation.h - LocalMinMaxGeneric.h - SeedFillGeneric.cpp SeedFillGeneric.h - FindPeaksGeneric.h - ColorMixer.h - ColorForId.h - BackgroundColorCalculator.cpp BackgroundColorCalculator.h - BadAllocIfNull.cpp BadAllocIfNull.h - ColorSegmenter.cpp ColorSegmenter.h - ColorTable.cpp ColorTable.h - ImageCombination.h ImageCombination.cpp) +set( + sources + Constants.h Constants.cpp + BinaryImage.cpp BinaryImage.h + BinaryThreshold.cpp BinaryThreshold.h + SlicedHistogram.cpp SlicedHistogram.h + ByteOrder.h BWColor.h + ConnComp.h Connectivity.h + BitOps.cpp BitOps.h + SeedFill.cpp SeedFill.h + ConnCompEraser.cpp ConnCompEraser.h + ConnCompEraserExt.cpp ConnCompEraserExt.h + GrayImage.cpp GrayImage.h + Grayscale.cpp Grayscale.h + RasterOp.h GrayRasterOp.h RasterOpGeneric.h + UpscaleIntegerTimes.cpp UpscaleIntegerTimes.h + ReduceThreshold.cpp ReduceThreshold.h + Shear.cpp Shear.h + SkewFinder.cpp SkewFinder.h + OrthogonalRotation.cpp OrthogonalRotation.h + Scale.cpp Scale.h + Transform.cpp Transform.h + Morphology.cpp Morphology.h + IntegralImage.h + Binarize.cpp Binarize.h + PolygonUtils.cpp PolygonUtils.h + PolygonRasterizer.cpp PolygonRasterizer.h + HoughLineDetector.cpp HoughLineDetector.h + GaussBlur.cpp GaussBlur.h + Sobel.h + MorphGradientDetect.cpp MorphGradientDetect.h + PolynomialLine.cpp PolynomialLine.h + PolynomialSurface.cpp PolynomialSurface.h + SavGolKernel.cpp SavGolKernel.h + SavGolFilter.cpp SavGolFilter.h + DrawOver.cpp DrawOver.h + AdjustBrightness.cpp AdjustBrightness.h + SEDM.cpp SEDM.h + ConnectivityMap.cpp ConnectivityMap.h + InfluenceMap.cpp InfluenceMap.h + MaxWhitespaceFinder.cpp MaxWhitespaceFinder.h + RastLineFinder.cpp RastLineFinder.h + ColorInterpolation.cpp ColorInterpolation.h + LocalMinMaxGeneric.h + SeedFillGeneric.cpp SeedFillGeneric.h + FindPeaksGeneric.h + ColorMixer.h + ColorForId.h + BackgroundColorCalculator.cpp BackgroundColorCalculator.h + BadAllocIfNull.cpp BadAllocIfNull.h + ColorSegmenter.cpp ColorSegmenter.h + ColorTable.cpp ColorTable.h + ImageCombination.h ImageCombination.cpp) -SOURCE_GROUP(Sources FILES ${sources}) +source_group(Sources FILES ${sources}) -ADD_LIBRARY(imageproc STATIC ${sources}) +add_library(imageproc STATIC ${sources}) -ADD_SUBDIRECTORY(tests) +add_subdirectory(tests) diff --git a/imageproc/ColorForId.h b/imageproc/ColorForId.h index 8dfcd1e97..b252b75bd 100644 --- a/imageproc/ColorForId.h +++ b/imageproc/ColorForId.h @@ -19,8 +19,8 @@ #ifndef IMAGEPROC_COLOR_FOR_ID_H_ #define IMAGEPROC_COLOR_FOR_ID_H_ -#include "BitOps.h" #include +#include "BitOps.h" namespace imageproc { /** @@ -29,20 +29,20 @@ namespace imageproc { * Colors for IDs that are numerically close will tend to be significantly * different. Positive IDs are handled better. */ -template +template QColor colorForId(T id) { - const int bits_unused = countMostSignificantZeroes(id); - const int bits_used = sizeof(T) * 8 - bits_unused; - const T reversed = reverseBits(id) >> bits_unused; - const T mask = (T(1) << bits_used) - 1; - - const double H = 0.99 * double(reversed + 1) / (mask + 1); - const double S = 1.0; - const double V = 1.0; - QColor color; - color.setHsvF(H, S, V); - - return color; + const int bits_unused = countMostSignificantZeroes(id); + const int bits_used = sizeof(T) * 8 - bits_unused; + const T reversed = reverseBits(id) >> bits_unused; + const T mask = (T(1) << bits_used) - 1; + + const double H = 0.99 * double(reversed + 1) / (mask + 1); + const double S = 1.0; + const double V = 1.0; + QColor color; + color.setHsvF(H, S, V); + + return color; } } // namespace imageproc #endif diff --git a/imageproc/ColorInterpolation.cpp b/imageproc/ColorInterpolation.cpp index f3eb9010a..50fa57f6c 100644 --- a/imageproc/ColorInterpolation.cpp +++ b/imageproc/ColorInterpolation.cpp @@ -20,17 +20,17 @@ namespace imageproc { QColor colorInterpolation(const QColor& from, const QColor& to, double dist) { - dist = qBound(0.0, dist, 1.0); + dist = qBound(0.0, dist, 1.0); - qreal r1, g1, b1, a1, r2, g2, b2, a2; - from.getRgbF(&r1, &g1, &b1, &a1); - to.getRgbF(&r2, &g2, &b2, &a2); + qreal r1, g1, b1, a1, r2, g2, b2, a2; + from.getRgbF(&r1, &g1, &b1, &a1); + to.getRgbF(&r2, &g2, &b2, &a2); - const qreal r = r1 + (r2 - r1) * dist; - const qreal g = g1 + (g2 - g1) * dist; - const qreal b = b1 + (b2 - b1) * dist; - const qreal a = a1 + (a2 - a1) * dist; + const qreal r = r1 + (r2 - r1) * dist; + const qreal g = g1 + (g2 - g1) * dist; + const qreal b = b1 + (b2 - b1) * dist; + const qreal a = a1 + (a2 - a1) * dist; - return QColor::fromRgbF(r, g, b, a); + return QColor::fromRgbF(r, g, b, a); } } // namespace imageproc diff --git a/imageproc/ColorMixer.h b/imageproc/ColorMixer.h index a1457922f..1880f0019 100644 --- a/imageproc/ColorMixer.h +++ b/imageproc/ColorMixer.h @@ -19,30 +19,26 @@ #ifndef IMAGEPROC_COLOR_MIXER_H_ #define IMAGEPROC_COLOR_MIXER_H_ -#include -#include #include +#include +#include namespace imageproc { namespace color_mixer_impl { -template +template struct Switcher { - typedef typename Mixer::accum_type accum_type; - typedef typename Mixer::result_type result_type; + typedef typename Mixer::accum_type accum_type; + typedef typename Mixer::result_type result_type; - static result_type mix(const Mixer* mixer, accum_type total_weight) { - return mixer->nonIntegerMix(total_weight); - } + static result_type mix(const Mixer* mixer, accum_type total_weight) { return mixer->nonIntegerMix(total_weight); } }; -template +template struct Switcher { - typedef typename Mixer::accum_type accum_type; - typedef typename Mixer::result_type result_type; + typedef typename Mixer::accum_type accum_type; + typedef typename Mixer::result_type result_type; - static result_type mix(const Mixer* mixer, accum_type total_weight) { - return mixer->integerMix(total_weight); - } + static result_type mix(const Mixer* mixer, accum_type total_weight) { return mixer->integerMix(total_weight); } }; } // namespace color_mixer_impl @@ -52,56 +48,53 @@ struct Switcher { * @tparam AccumType An integer or a floating type to be used for pixel weights * and internally for accumulating weighted pixel values. */ -template +template class GrayColorMixer { - template - friend struct color_mixer_impl::Switcher; - -public: - typedef AccumType accum_type; - typedef uint8_t result_type; - - GrayColorMixer() : m_accum() { - } - - /** - * @brief Adds a weighted pixel into the mix. - * - * The weight must be non-negative. - */ - void add(uint8_t gray_level, AccumType weight) { - m_accum += AccumType(gray_level) * weight; - } - - /** - * @brief Returns a color intepolated from previously added ones. - * - * @param total_weight The sum of individual weights passed to add(). - * While an individual weight can be zero, the sum has to be above zero. - * @return Interpolated color. - */ - result_type mix(AccumType total_weight) const { - assert(total_weight > 0); - - using namespace color_mixer_impl; - typedef std::numeric_limits traits; - return Switcher, traits::is_integer>::mix(this, total_weight); - } - -private: - uint8_t nonIntegerMix(AccumType total_weight) const { - assert(total_weight > 0); - return static_cast(m_accum / total_weight + AccumType(0.5)); - } - - uint8_t integerMix(AccumType total_weight) const { - assert(total_weight > 0); - const AccumType half_weight = total_weight >> 1; - const AccumType mixed = (m_accum + half_weight) / total_weight; - return static_cast(mixed); - } - - AccumType m_accum; + template + friend struct color_mixer_impl::Switcher; + + public: + typedef AccumType accum_type; + typedef uint8_t result_type; + + GrayColorMixer() : m_accum() {} + + /** + * @brief Adds a weighted pixel into the mix. + * + * The weight must be non-negative. + */ + void add(uint8_t gray_level, AccumType weight) { m_accum += AccumType(gray_level) * weight; } + + /** + * @brief Returns a color intepolated from previously added ones. + * + * @param total_weight The sum of individual weights passed to add(). + * While an individual weight can be zero, the sum has to be above zero. + * @return Interpolated color. + */ + result_type mix(AccumType total_weight) const { + assert(total_weight > 0); + + using namespace color_mixer_impl; + typedef std::numeric_limits traits; + return Switcher, traits::is_integer>::mix(this, total_weight); + } + + private: + uint8_t nonIntegerMix(AccumType total_weight) const { + assert(total_weight > 0); + return static_cast(m_accum / total_weight + AccumType(0.5)); + } + + uint8_t integerMix(AccumType total_weight) const { + assert(total_weight > 0); + const AccumType half_weight = total_weight >> 1; + const AccumType mixed = (m_accum + half_weight) / total_weight; + return static_cast(mixed); + } + + AccumType m_accum; }; @@ -111,56 +104,55 @@ class GrayColorMixer { * @tparam AccumType An integer or a floating type to be used for pixel weights * and internally for accumulating weighted pixel values. */ -template +template class RgbColorMixer { - template - friend struct color_mixer_impl::Switcher; - -public: - typedef AccumType accum_type; - typedef uint32_t result_type; - - RgbColorMixer() : m_redAccum(), m_greenAccum(), m_blueAccum() { - } - - /** @see GrayColorMixer::add() */ - void add(uint32_t rgb, AccumType weight) { - m_redAccum += AccumType((rgb >> 16) & 0xFF) * weight; - m_greenAccum += AccumType((rgb >> 8) & 0xFF) * weight; - m_blueAccum += AccumType(rgb & 0xFF) * weight; - } - - /** @see GrayColorMixer::mix() */ - result_type mix(AccumType total_weight) const { - assert(total_weight > 0); - - using namespace color_mixer_impl; - typedef std::numeric_limits traits; - return Switcher, traits::is_integer>::mix(this, total_weight); - } - -private: - uint32_t nonIntegerMix(AccumType total_weight) const { - assert(total_weight > 0); - const AccumType scale = AccumType(1) / total_weight; - const uint32_t r = uint32_t(AccumType(0.5) + m_redAccum * scale); - const uint32_t g = uint32_t(AccumType(0.5) + m_greenAccum * scale); - const uint32_t b = uint32_t(AccumType(0.5) + m_blueAccum * scale); - return uint32_t(0xff000000) | (r << 16) | (g << 8) | b; - } - - uint32_t integerMix(AccumType total_weight) const { - assert(total_weight > 0); - const AccumType half_weight = total_weight >> 1; - const uint32_t r = uint32_t((m_redAccum + half_weight) / total_weight); - const uint32_t g = uint32_t((m_greenAccum + half_weight) / total_weight); - const uint32_t b = uint32_t((m_blueAccum + half_weight) / total_weight); - return uint32_t(0xff000000) | (r << 16) | (g << 8) | b; - } - - AccumType m_redAccum; - AccumType m_greenAccum; - AccumType m_blueAccum; + template + friend struct color_mixer_impl::Switcher; + + public: + typedef AccumType accum_type; + typedef uint32_t result_type; + + RgbColorMixer() : m_redAccum(), m_greenAccum(), m_blueAccum() {} + + /** @see GrayColorMixer::add() */ + void add(uint32_t rgb, AccumType weight) { + m_redAccum += AccumType((rgb >> 16) & 0xFF) * weight; + m_greenAccum += AccumType((rgb >> 8) & 0xFF) * weight; + m_blueAccum += AccumType(rgb & 0xFF) * weight; + } + + /** @see GrayColorMixer::mix() */ + result_type mix(AccumType total_weight) const { + assert(total_weight > 0); + + using namespace color_mixer_impl; + typedef std::numeric_limits traits; + return Switcher, traits::is_integer>::mix(this, total_weight); + } + + private: + uint32_t nonIntegerMix(AccumType total_weight) const { + assert(total_weight > 0); + const AccumType scale = AccumType(1) / total_weight; + const uint32_t r = uint32_t(AccumType(0.5) + m_redAccum * scale); + const uint32_t g = uint32_t(AccumType(0.5) + m_greenAccum * scale); + const uint32_t b = uint32_t(AccumType(0.5) + m_blueAccum * scale); + return uint32_t(0xff000000) | (r << 16) | (g << 8) | b; + } + + uint32_t integerMix(AccumType total_weight) const { + assert(total_weight > 0); + const AccumType half_weight = total_weight >> 1; + const uint32_t r = uint32_t((m_redAccum + half_weight) / total_weight); + const uint32_t g = uint32_t((m_greenAccum + half_weight) / total_weight); + const uint32_t b = uint32_t((m_blueAccum + half_weight) / total_weight); + return uint32_t(0xff000000) | (r << 16) | (g << 8) | b; + } + + AccumType m_redAccum; + AccumType m_greenAccum; + AccumType m_blueAccum; }; @@ -174,72 +166,71 @@ class RgbColorMixer { * AccumType if we plan to do significant downscaling. uint64_t or a floating * point type would be fine. */ -template +template class ArgbColorMixer { - template - friend struct color_mixer_impl::Switcher; - -public: - typedef AccumType accum_type; - typedef uint32_t result_type; - - ArgbColorMixer() : m_alphaAccum(), m_redAccum(), m_greenAccum(), m_blueAccum() { - } - - /** @see GrayColorMixer:add() */ - void add(const uint32_t argb, const AccumType weight) { - const AccumType alpha = AccumType((argb >> 24) & 0xFF); - const AccumType alpha_weight = alpha * weight; - m_alphaAccum += alpha_weight; - m_redAccum += AccumType((argb >> 16) & 0xFF) * alpha_weight; - m_greenAccum += AccumType((argb >> 8) & 0xFF) * alpha_weight; - m_blueAccum += AccumType(argb & 0xFF) * alpha_weight; - } - - /** @see GrayColorMixer::mix() */ - result_type mix(AccumType total_weight) const { - assert(total_weight > 0); - if (m_alphaAccum == AccumType(0)) { - // A totally transparent color. This can happen when mixing - // a bunch of (possibly different) colors with alpha == 0. - // This branch prevents a division by zero in *IntegerMix(). - return 0; - } - - using namespace color_mixer_impl; - typedef std::numeric_limits traits; - return Switcher, traits::is_integer>::mix(this, total_weight); - } - -private: - uint32_t nonIntegerMix(AccumType total_weight) const { - assert(total_weight > 0); - assert(m_alphaAccum > 0); - const AccumType scale1 = AccumType(1) / total_weight; - const AccumType scale2 = AccumType(1) / m_alphaAccum; - const uint32_t a = uint32_t(AccumType(0.5) + m_alphaAccum * scale1); - const uint32_t r = uint32_t(AccumType(0.5) + m_redAccum * scale2); - const uint32_t g = uint32_t(AccumType(0.5) + m_greenAccum * scale2); - const uint32_t b = uint32_t(AccumType(0.5) + m_blueAccum * scale2); - return (a << 24) | (r << 16) | (g << 8) | b; - } - - uint32_t integerMix(AccumType total_weight) const { - assert(total_weight > 0); - assert(m_alphaAccum > 0); - const AccumType half_weight1 = total_weight >> 1; - const AccumType half_weight2 = m_alphaAccum >> 1; - const uint32_t a = uint32_t((m_alphaAccum + half_weight1) / total_weight); - const uint32_t r = uint32_t((m_redAccum + half_weight2) / m_alphaAccum); - const uint32_t g = uint32_t((m_greenAccum + half_weight2) / m_alphaAccum); - const uint32_t b = uint32_t((m_blueAccum + half_weight2) / m_alphaAccum); - return (a << 24) | (r << 16) | (g << 8) | b; - } - - AccumType m_alphaAccum; - AccumType m_redAccum; - AccumType m_greenAccum; - AccumType m_blueAccum; + template + friend struct color_mixer_impl::Switcher; + + public: + typedef AccumType accum_type; + typedef uint32_t result_type; + + ArgbColorMixer() : m_alphaAccum(), m_redAccum(), m_greenAccum(), m_blueAccum() {} + + /** @see GrayColorMixer:add() */ + void add(const uint32_t argb, const AccumType weight) { + const AccumType alpha = AccumType((argb >> 24) & 0xFF); + const AccumType alpha_weight = alpha * weight; + m_alphaAccum += alpha_weight; + m_redAccum += AccumType((argb >> 16) & 0xFF) * alpha_weight; + m_greenAccum += AccumType((argb >> 8) & 0xFF) * alpha_weight; + m_blueAccum += AccumType(argb & 0xFF) * alpha_weight; + } + + /** @see GrayColorMixer::mix() */ + result_type mix(AccumType total_weight) const { + assert(total_weight > 0); + if (m_alphaAccum == AccumType(0)) { + // A totally transparent color. This can happen when mixing + // a bunch of (possibly different) colors with alpha == 0. + // This branch prevents a division by zero in *IntegerMix(). + return 0; + } + + using namespace color_mixer_impl; + typedef std::numeric_limits traits; + return Switcher, traits::is_integer>::mix(this, total_weight); + } + + private: + uint32_t nonIntegerMix(AccumType total_weight) const { + assert(total_weight > 0); + assert(m_alphaAccum > 0); + const AccumType scale1 = AccumType(1) / total_weight; + const AccumType scale2 = AccumType(1) / m_alphaAccum; + const uint32_t a = uint32_t(AccumType(0.5) + m_alphaAccum * scale1); + const uint32_t r = uint32_t(AccumType(0.5) + m_redAccum * scale2); + const uint32_t g = uint32_t(AccumType(0.5) + m_greenAccum * scale2); + const uint32_t b = uint32_t(AccumType(0.5) + m_blueAccum * scale2); + return (a << 24) | (r << 16) | (g << 8) | b; + } + + uint32_t integerMix(AccumType total_weight) const { + assert(total_weight > 0); + assert(m_alphaAccum > 0); + const AccumType half_weight1 = total_weight >> 1; + const AccumType half_weight2 = m_alphaAccum >> 1; + const uint32_t a = uint32_t((m_alphaAccum + half_weight1) / total_weight); + const uint32_t r = uint32_t((m_redAccum + half_weight2) / m_alphaAccum); + const uint32_t g = uint32_t((m_greenAccum + half_weight2) / m_alphaAccum); + const uint32_t b = uint32_t((m_blueAccum + half_weight2) / m_alphaAccum); + return (a << 24) | (r << 16) | (g << 8) | b; + } + + AccumType m_alphaAccum; + AccumType m_redAccum; + AccumType m_greenAccum; + AccumType m_blueAccum; }; } // namespace imageproc diff --git a/imageproc/ColorSegmenter.cpp b/imageproc/ColorSegmenter.cpp index 3e0242d19..cc5c44230 100644 --- a/imageproc/ColorSegmenter.cpp +++ b/imageproc/ColorSegmenter.cpp @@ -1,82 +1,74 @@ #include "ColorSegmenter.h" +#include +#include #include "BinaryImage.h" -#include "GrayImage.h" #include "Dpi.h" -#include "RasterOp.h" +#include "GrayImage.h" #include "InfluenceMap.h" -#include -#include +#include "RasterOp.h" namespace imageproc { struct ColorSegmenter::Component { - uint32_t pixelsCount; + uint32_t pixelsCount; - Component() : pixelsCount(0) { - } + Component() : pixelsCount(0) {} - inline int square() const { - return pixelsCount; - } + inline int square() const { return pixelsCount; } }; struct ColorSegmenter::BoundingBox { - int top; - int left; - int bottom; - int right; - - BoundingBox() { - top = left = std::numeric_limits::max(); - bottom = right = std::numeric_limits::min(); - } - - inline int width() const { - return right - left + 1; - } - - inline int height() const { - return bottom - top + 1; - } - - inline void extend(int x, int y) { - top = std::min(top, y); - left = std::min(left, x); - bottom = std::max(bottom, y); - right = std::max(right, x); - } + int top; + int left; + int bottom; + int right; + + BoundingBox() { + top = left = std::numeric_limits::max(); + bottom = right = std::numeric_limits::min(); + } + + inline int width() const { return right - left + 1; } + + inline int height() const { return bottom - top + 1; } + + inline void extend(int x, int y) { + top = std::min(top, y); + left = std::min(left, x); + bottom = std::max(bottom, y); + right = std::max(right, x); + } }; struct RgbColor { - uint32_t red; - uint32_t green; - uint32_t blue; + uint32_t red; + uint32_t green; + uint32_t blue; - RgbColor() : red(0), green(0), blue(0) { - } + RgbColor() : red(0), green(0), blue(0) {} }; /*=============================== ColorSegmenter::Settings ==================================*/ ColorSegmenter::Settings::Settings(const Dpi& dpi, const int noiseThreshold) { - const int average_dpi = (dpi.horizontal() + dpi.vertical()) / 2; - const double dpi_factor = average_dpi / 300.0; + const int average_dpi = (dpi.horizontal() + dpi.vertical()) / 2; + const double dpi_factor = average_dpi / 300.0; - minAverageWidthThreshold = 1.5 * dpi_factor; - bigObjectThreshold = qRound(std::pow(noiseThreshold, std::sqrt(2)) * dpi_factor); + m_minAverageWidthThreshold = 1.5 * dpi_factor; + m_bigObjectThreshold = qRound(std::pow(noiseThreshold, std::sqrt(2)) * dpi_factor); } inline bool ColorSegmenter::Settings::eligibleForDelete(const ColorSegmenter::Component& component, const ColorSegmenter::BoundingBox& boundingBox) const { - if (component.pixelsCount <= bigObjectThreshold) { - return true; - } + if (component.pixelsCount <= m_bigObjectThreshold) { + return true; + } - double squareRelation = double(component.square()) / (boundingBox.height() * boundingBox.width()); - double averageWidth = std::min(boundingBox.height(), boundingBox.width()) * squareRelation; + double squareRelation = double(component.square()) / (boundingBox.height() * boundingBox.width()); + double averageWidth = std::min(boundingBox.height(), boundingBox.width()) * squareRelation; - return (averageWidth <= minAverageWidthThreshold); + return (averageWidth <= m_minAverageWidthThreshold); } /*=============================== ColorSegmenter ==================================*/ @@ -88,76 +80,76 @@ ColorSegmenter::ColorSegmenter(const BinaryImage& image, const int redThresholdAdjustment, const int greenThresholdAdjustment, const int blueThresholdAdjustment) - : settings(dpi, noiseThreshold) { - if (image.size() != originalImage.size()) { - throw std::invalid_argument("ColorSegmenter: images size doesn't match."); - } - if ((originalImage.format() != QImage::Format_Indexed8) && (originalImage.format() != QImage::Format_RGB32) - && (originalImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("Error: wrong image format."); - } - if (originalImage.format() == QImage::Format_Indexed8) { - if (originalImage.isGrayscale()) { - fromGrayscale(image, GrayImage(originalImage)); - return; - } else { - throw std::invalid_argument("Error: wrong image format."); - } + : m_settings(dpi, noiseThreshold) { + if (image.size() != originalImage.size()) { + throw std::invalid_argument("ColorSegmenter: images size doesn't match."); + } + if ((originalImage.format() != QImage::Format_Indexed8) && (originalImage.format() != QImage::Format_RGB32) + && (originalImage.format() != QImage::Format_ARGB32)) { + throw std::invalid_argument("Error: wrong image format."); + } + if (originalImage.format() == QImage::Format_Indexed8) { + if (originalImage.isGrayscale()) { + fromGrayscale(image, GrayImage(originalImage)); + return; + } else { + throw std::invalid_argument("Error: wrong image format."); } - fromRgb(image, originalImage, redThresholdAdjustment, greenThresholdAdjustment, blueThresholdAdjustment); + } + fromRgb(image, originalImage, redThresholdAdjustment, greenThresholdAdjustment, blueThresholdAdjustment); } ColorSegmenter::ColorSegmenter(const BinaryImage& image, const GrayImage& originalImage, const Dpi& dpi, const int noiseThreshold) - : settings(dpi, noiseThreshold) { - if (image.size() != originalImage.size()) { - throw std::invalid_argument("ColorSegmenter: images size doesn't match."); - } - fromGrayscale(image, originalImage); + : m_settings(dpi, noiseThreshold) { + if (image.size() != originalImage.size()) { + throw std::invalid_argument("ColorSegmenter: images size doesn't match."); + } + fromGrayscale(image, originalImage); } QImage ColorSegmenter::getImage() const { - if (originalImage.format() == QImage::Format_Indexed8) { - return buildGrayImage(); - } else { - return buildRgbImage(); - } + if (m_originalImage.format() == QImage::Format_Indexed8) { + return buildGrayImage(); + } else { + return buildRgbImage(); + } } GrayImage ColorSegmenter::getRgbChannel(const QImage& image, const ColorSegmenter::RgbChannel channel) { - if ((image.format() != QImage::Format_RGB32) && (image.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ColorSegmenter: wrong image format."); - } - - GrayImage dst(image.size()); - const auto* img_line = reinterpret_cast(image.bits()); - const int img_stride = image.bytesPerLine() / sizeof(uint32_t); - uint8_t* dst_line = dst.data(); - const int dst_stride = dst.stride(); - const int width = image.width(); - const int height = image.height(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - switch (channel) { - case RED_CHANNEL: - dst_line[x] = static_cast((img_line[x] >> 16) & 0xff); - break; - case GREEN_CHANNEL: - dst_line[x] = static_cast((img_line[x] >> 8) & 0xff); - break; - case BLUE_CHANNEL: - dst_line[x] = static_cast(img_line[x] & 0xff); - break; - } - } - img_line += img_stride; - dst_line += dst_stride; + if ((image.format() != QImage::Format_RGB32) && (image.format() != QImage::Format_ARGB32)) { + throw std::invalid_argument("ColorSegmenter: wrong image format."); + } + + GrayImage dst(image.size()); + const auto* img_line = reinterpret_cast(image.bits()); + const int img_stride = image.bytesPerLine() / sizeof(uint32_t); + uint8_t* dst_line = dst.data(); + const int dst_stride = dst.stride(); + const int width = image.width(); + const int height = image.height(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (channel) { + case RED_CHANNEL: + dst_line[x] = static_cast((img_line[x] >> 16) & 0xff); + break; + case GREEN_CHANNEL: + dst_line[x] = static_cast((img_line[x] >> 8) & 0xff); + break; + case BLUE_CHANNEL: + dst_line[x] = static_cast(img_line[x] & 0xff); + break; + } } + img_line += img_stride; + dst_line += dst_stride; + } - return dst; + return dst; } void ColorSegmenter::fromRgb(const BinaryImage& image, @@ -165,248 +157,246 @@ void ColorSegmenter::fromRgb(const BinaryImage& image, const int redThresholdAdjustment, const int greenThresholdAdjustment, const int blueThresholdAdjustment) { - this->originalImage = originalImage; - - BinaryImage redComponent; - { - GrayImage redChannel = getRgbChannel(originalImage, RED_CHANNEL); - BinaryThreshold redThreshold = BinaryThreshold::otsuThreshold(redChannel); - redComponent = BinaryImage(redChannel, adjustThreshold(redThreshold, redThresholdAdjustment)); - rasterOp>(redComponent, image); - } - BinaryImage greenComponent; - { - GrayImage greenChannel = getRgbChannel(originalImage, GREEN_CHANNEL); - BinaryThreshold greenThreshold = BinaryThreshold::otsuThreshold(greenChannel); - greenComponent = BinaryImage(greenChannel, adjustThreshold(greenThreshold, greenThresholdAdjustment)); - rasterOp>(greenComponent, image); - } - BinaryImage blueComponent; - { - GrayImage blueChannel = getRgbChannel(originalImage, BLUE_CHANNEL); - BinaryThreshold blueThreshold = BinaryThreshold::otsuThreshold(blueChannel); - blueComponent = BinaryImage(blueChannel, adjustThreshold(blueThreshold, blueThresholdAdjustment)); - rasterOp>(blueComponent, image); - } - BinaryImage yellowComponent(redComponent); - rasterOp>(yellowComponent, greenComponent); - BinaryImage magentaComponent(redComponent); - rasterOp>(magentaComponent, blueComponent); - BinaryImage cyanComponent(greenComponent); - rasterOp>(cyanComponent, blueComponent); - - BinaryImage blackComponent(blueComponent); - rasterOp>(blackComponent, yellowComponent); - - rasterOp>(redComponent, blackComponent); - rasterOp>(greenComponent, blackComponent); - rasterOp>(blueComponent, blackComponent); - rasterOp>(yellowComponent, blackComponent); - rasterOp>(magentaComponent, blackComponent); - rasterOp>(cyanComponent, blackComponent); - - rasterOp>(redComponent, yellowComponent); - rasterOp>(redComponent, magentaComponent); - rasterOp>(greenComponent, yellowComponent); - rasterOp>(greenComponent, cyanComponent); - rasterOp>(blueComponent, magentaComponent); - rasterOp>(blueComponent, cyanComponent); - - segmentsMap = ConnectivityMap(blackComponent, CONN8); - segmentsMap.addComponents(yellowComponent, CONN8); - segmentsMap.addComponents(magentaComponent, CONN8); - segmentsMap.addComponents(cyanComponent, CONN8); - segmentsMap.addComponents(redComponent, CONN8); - segmentsMap.addComponents(greenComponent, CONN8); - segmentsMap.addComponents(blueComponent, CONN8); - - reduceNoise(); - - { - // extend the map and fill unlabeled components. - InfluenceMap influenceMap(segmentsMap, image); - segmentsMap = influenceMap; - } - - BinaryImage remainingComponents(image); - rasterOp>(remainingComponents, segmentsMap.getBinaryMask()); - segmentsMap.addComponents(remainingComponents, CONN8); + this->m_originalImage = originalImage; + + BinaryImage redComponent; + { + GrayImage redChannel = getRgbChannel(originalImage, RED_CHANNEL); + BinaryThreshold redThreshold = BinaryThreshold::otsuThreshold(redChannel); + redComponent = BinaryImage(redChannel, adjustThreshold(redThreshold, redThresholdAdjustment)); + rasterOp>(redComponent, image); + } + BinaryImage greenComponent; + { + GrayImage greenChannel = getRgbChannel(originalImage, GREEN_CHANNEL); + BinaryThreshold greenThreshold = BinaryThreshold::otsuThreshold(greenChannel); + greenComponent = BinaryImage(greenChannel, adjustThreshold(greenThreshold, greenThresholdAdjustment)); + rasterOp>(greenComponent, image); + } + BinaryImage blueComponent; + { + GrayImage blueChannel = getRgbChannel(originalImage, BLUE_CHANNEL); + BinaryThreshold blueThreshold = BinaryThreshold::otsuThreshold(blueChannel); + blueComponent = BinaryImage(blueChannel, adjustThreshold(blueThreshold, blueThresholdAdjustment)); + rasterOp>(blueComponent, image); + } + BinaryImage yellowComponent(redComponent); + rasterOp>(yellowComponent, greenComponent); + BinaryImage magentaComponent(redComponent); + rasterOp>(magentaComponent, blueComponent); + BinaryImage cyanComponent(greenComponent); + rasterOp>(cyanComponent, blueComponent); + + BinaryImage blackComponent(blueComponent); + rasterOp>(blackComponent, yellowComponent); + + rasterOp>(redComponent, blackComponent); + rasterOp>(greenComponent, blackComponent); + rasterOp>(blueComponent, blackComponent); + rasterOp>(yellowComponent, blackComponent); + rasterOp>(magentaComponent, blackComponent); + rasterOp>(cyanComponent, blackComponent); + + rasterOp>(redComponent, yellowComponent); + rasterOp>(redComponent, magentaComponent); + rasterOp>(greenComponent, yellowComponent); + rasterOp>(greenComponent, cyanComponent); + rasterOp>(blueComponent, magentaComponent); + rasterOp>(blueComponent, cyanComponent); + + m_segmentsMap = ConnectivityMap(blackComponent, CONN8); + m_segmentsMap.addComponents(yellowComponent, CONN8); + m_segmentsMap.addComponents(magentaComponent, CONN8); + m_segmentsMap.addComponents(cyanComponent, CONN8); + m_segmentsMap.addComponents(redComponent, CONN8); + m_segmentsMap.addComponents(greenComponent, CONN8); + m_segmentsMap.addComponents(blueComponent, CONN8); + + reduceNoise(); + + { + // extend the map and fill unlabeled components. + InfluenceMap influenceMap(m_segmentsMap, image); + m_segmentsMap = influenceMap; + } + + BinaryImage remainingComponents(image); + rasterOp>(remainingComponents, m_segmentsMap.getBinaryMask()); + m_segmentsMap.addComponents(remainingComponents, CONN8); } void ColorSegmenter::fromGrayscale(const BinaryImage& image, const GrayImage& originalImage) { - this->originalImage = originalImage; - this->segmentsMap = ConnectivityMap(image, CONN8); + this->m_originalImage = originalImage; + this->m_segmentsMap = ConnectivityMap(image, CONN8); } void ColorSegmenter::reduceNoise() { - std::vector components(segmentsMap.maxLabel() + 1); - std::vector boundingBoxes(segmentsMap.maxLabel() + 1); - - const QSize size = segmentsMap.size(); - const int width = size.width(); - const int height = size.height(); - - // Count the number of pixels and the bounding rect of each component. - const uint32_t* map_line = segmentsMap.data(); - const int map_stride = segmentsMap.stride(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = map_line[x]; - ++components[label].pixelsCount; - boundingBoxes[label].extend(x, y); - } - map_line += map_stride; + std::vector components(m_segmentsMap.maxLabel() + 1); + std::vector boundingBoxes(m_segmentsMap.maxLabel() + 1); + + const QSize size = m_segmentsMap.size(); + const int width = size.width(); + const int height = size.height(); + + // Count the number of pixels and the bounding rect of each component. + const uint32_t* map_line = m_segmentsMap.data(); + const int map_stride = m_segmentsMap.stride(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = map_line[x]; + ++components[label].pixelsCount; + boundingBoxes[label].extend(x, y); } - - // creating set of labels determining components to be removed - std::unordered_set labels; - for (uint32_t label = 1; label <= segmentsMap.maxLabel(); ++label) { - if (settings.eligibleForDelete(components[label], boundingBoxes[label])) { - labels.insert(label); - } + map_line += map_stride; + } + + // creating set of labels determining components to be removed + std::unordered_set labels; + for (uint32_t label = 1; label <= m_segmentsMap.maxLabel(); ++label) { + if (m_settings.eligibleForDelete(components[label], boundingBoxes[label])) { + labels.insert(label); } + } - segmentsMap.removeComponents(labels); + m_segmentsMap.removeComponents(labels); } QImage ColorSegmenter::buildRgbImage() const { - if (originalImage.size().isEmpty()) { - return QImage(); - } + if (m_originalImage.size().isEmpty()) { + return QImage(); + } - const int width = originalImage.width(); - const int height = originalImage.height(); - - std::vector colorMap(segmentsMap.maxLabel() + 1, 0); - - { - const uint32_t* map_line = segmentsMap.data(); - const int map_stride = segmentsMap.stride(); - - const auto* img_line = reinterpret_cast(originalImage.bits()); - const int img_stride = originalImage.bytesPerLine() / sizeof(uint32_t); - - std::vector components(segmentsMap.maxLabel() + 1); - std::vector rgbSumMap(segmentsMap.maxLabel() + 1); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = map_line[x]; - if (label == 0) { - continue; - } - - ++components[label].pixelsCount; - rgbSumMap[label].red += static_cast((img_line[x] >> 16) & 0xff); - rgbSumMap[label].green += static_cast((img_line[x] >> 8) & 0xff); - rgbSumMap[label].blue += static_cast(img_line[x] & 0xff); - } - map_line += map_stride; - img_line += img_stride; - } + const int width = m_originalImage.width(); + const int height = m_originalImage.height(); + + std::vector colorMap(m_segmentsMap.maxLabel() + 1, 0); + + { + const uint32_t* map_line = m_segmentsMap.data(); + const int map_stride = m_segmentsMap.stride(); - for (int label = 1; label <= segmentsMap.maxLabel(); label++) { - const auto red - = static_cast(std::round(double(rgbSumMap[label].red) / components[label].pixelsCount)); - const auto green - = static_cast(std::round(double(rgbSumMap[label].green) / components[label].pixelsCount)); - const auto blue - = static_cast(std::round(double(rgbSumMap[label].blue) / components[label].pixelsCount)); + const auto* img_line = reinterpret_cast(m_originalImage.bits()); + const int img_stride = m_originalImage.bytesPerLine() / sizeof(uint32_t); - colorMap[label] = (red << 16) | (green << 8) | (blue); + std::vector components(m_segmentsMap.maxLabel() + 1); + std::vector rgbSumMap(m_segmentsMap.maxLabel() + 1); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = map_line[x]; + if (label == 0) { + continue; } + + ++components[label].pixelsCount; + rgbSumMap[label].red += static_cast((img_line[x] >> 16) & 0xff); + rgbSumMap[label].green += static_cast((img_line[x] >> 8) & 0xff); + rgbSumMap[label].blue += static_cast(img_line[x] & 0xff); + } + map_line += map_stride; + img_line += img_stride; } - QImage dst(originalImage.size(), QImage::Format_ARGB32_Premultiplied); - dst.fill(Qt::white); + for (int label = 1; label <= m_segmentsMap.maxLabel(); label++) { + const auto red = static_cast(std::round(double(rgbSumMap[label].red) / components[label].pixelsCount)); + const auto green + = static_cast(std::round(double(rgbSumMap[label].green) / components[label].pixelsCount)); + const auto blue + = static_cast(std::round(double(rgbSumMap[label].blue) / components[label].pixelsCount)); - auto* dst_line = reinterpret_cast(dst.bits()); - const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); + colorMap[label] = (red << 16) | (green << 8) | (blue); + } + } - const uint32_t* map_line = segmentsMap.data(); - const int map_stride = segmentsMap.stride(); + QImage dst(m_originalImage.size(), QImage::Format_ARGB32_Premultiplied); + dst.fill(Qt::white); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = map_line[x]; - if (label == 0) { - continue; - } + auto* dst_line = reinterpret_cast(dst.bits()); + const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); - dst_line[x] = colorMap[label]; - } - map_line += map_stride; - dst_line += dst_stride; + const uint32_t* map_line = m_segmentsMap.data(); + const int map_stride = m_segmentsMap.stride(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = map_line[x]; + if (label == 0) { + continue; + } + + dst_line[x] = colorMap[label]; } + map_line += map_stride; + dst_line += dst_stride; + } - return dst.convertToFormat(QImage::Format_RGB32); + return dst.convertToFormat(QImage::Format_RGB32); } QImage ColorSegmenter::buildGrayImage() const { - if (originalImage.size().isEmpty()) { - return QImage(); - } + if (m_originalImage.size().isEmpty()) { + return QImage(); + } - const int width = originalImage.width(); - const int height = originalImage.height(); + const int width = m_originalImage.width(); + const int height = m_originalImage.height(); - std::vector colorMap(segmentsMap.maxLabel() + 1, 0); + std::vector colorMap(m_segmentsMap.maxLabel() + 1, 0); - { - const uint32_t* map_line = segmentsMap.data(); - const int map_stride = segmentsMap.stride(); + { + const uint32_t* map_line = m_segmentsMap.data(); + const int map_stride = m_segmentsMap.stride(); - const auto* img_line = originalImage.bits(); - const int img_stride = originalImage.bytesPerLine(); + const auto* img_line = m_originalImage.bits(); + const int img_stride = m_originalImage.bytesPerLine(); - std::vector components(segmentsMap.maxLabel() + 1); - std::vector graySumMap(segmentsMap.maxLabel() + 1, 0); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = map_line[x]; - if (label == 0) { - continue; - } - - ++components[label].pixelsCount; - graySumMap[label] += img_line[x]; - } - map_line += map_stride; - img_line += img_stride; + std::vector components(m_segmentsMap.maxLabel() + 1); + std::vector graySumMap(m_segmentsMap.maxLabel() + 1, 0); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = map_line[x]; + if (label == 0) { + continue; } - for (int label = 1; label <= segmentsMap.maxLabel(); label++) { - colorMap[label] - = static_cast(std::round(double(graySumMap[label]) / components[label].pixelsCount)); - } + ++components[label].pixelsCount; + graySumMap[label] += img_line[x]; + } + map_line += map_stride; + img_line += img_stride; } - GrayImage dst(originalImage.size()); - dst.fill(0xff); + for (int label = 1; label <= m_segmentsMap.maxLabel(); label++) { + colorMap[label] = static_cast(std::round(double(graySumMap[label]) / components[label].pixelsCount)); + } + } - uint8_t* dst_line = dst.data(); - const int dst_stride = dst.stride(); + GrayImage dst(m_originalImage.size()); + dst.fill(0xff); - const uint32_t* map_line = segmentsMap.data(); - const int map_stride = segmentsMap.stride(); + uint8_t* dst_line = dst.data(); + const int dst_stride = dst.stride(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t label = map_line[x]; - if (label == 0) { - continue; - } + const uint32_t* map_line = m_segmentsMap.data(); + const int map_stride = m_segmentsMap.stride(); - dst_line[x] = colorMap[label]; - } - map_line += map_stride; - dst_line += dst_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t label = map_line[x]; + if (label == 0) { + continue; + } + + dst_line[x] = colorMap[label]; } + map_line += map_stride; + dst_line += dst_stride; + } - return dst; + return dst; } inline BinaryThreshold ColorSegmenter::adjustThreshold(const BinaryThreshold threshold, const int adjustment) { - return qBound(1, int(threshold) + adjustment, 255); + return qBound(1, int(threshold) + adjustment, 255); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ColorSegmenter.h b/imageproc/ColorSegmenter.h index fb1de5f04..85a988966 100644 --- a/imageproc/ColorSegmenter.h +++ b/imageproc/ColorSegmenter.h @@ -2,9 +2,9 @@ #ifndef SCANTAILOR_COLORSEGMENTER_H #define SCANTAILOR_COLORSEGMENTER_H -#include "ConnectivityMap.h" -#include "BinaryThreshold.h" #include +#include "BinaryThreshold.h" +#include "ConnectivityMap.h" class Dpi; class QRect; @@ -14,67 +14,66 @@ class BinaryImage; class GrayImage; class ColorSegmenter { -private: - struct Component; - struct BoundingBox; + public: + ColorSegmenter(const BinaryImage& image, + const QImage& originalImage, + const Dpi& dpi, + int noiseThreshold, + int redThresholdAdjustment, + int greenThresholdAdjustment, + int blueThresholdAdjustment); - enum RgbChannel { RED_CHANNEL, GREEN_CHANNEL, BLUE_CHANNEL }; + ColorSegmenter(const BinaryImage& image, const GrayImage& originalImage, const Dpi& dpi, int noiseThreshold); - class Settings { - private: - /** - * Defines the minimum average width threshold. - * When a component has lower that, it will be erased. - */ - double minAverageWidthThreshold; + QImage getImage() const; - /** - * Defines the minimum square in pixels. - * If a component has lower that, it will be erased. - */ - int bigObjectThreshold; + private: + struct Component; + struct BoundingBox; - public: - explicit Settings(const Dpi& dpi, int noiseThreshold); + enum RgbChannel { RED_CHANNEL, GREEN_CHANNEL, BLUE_CHANNEL }; - bool eligibleForDelete(const Component& component, const BoundingBox& boundingBox) const; - }; + class Settings { + public: + explicit Settings(const Dpi& dpi, int noiseThreshold); -public: - ColorSegmenter(const BinaryImage& image, - const QImage& originalImage, - const Dpi& dpi, - int noiseThreshold, - int redThresholdAdjustment, - int greenThresholdAdjustment, - int blueThresholdAdjustment); + bool eligibleForDelete(const Component& component, const BoundingBox& boundingBox) const; - ColorSegmenter(const BinaryImage& image, const GrayImage& originalImage, const Dpi& dpi, int noiseThreshold); + private: + /** + * Defines the minimum average width threshold. + * When a component has lower that, it will be erased. + */ + double m_minAverageWidthThreshold; - QImage getImage() const; + /** + * Defines the minimum square in pixels. + * If a component has lower that, it will be erased. + */ + int m_bigObjectThreshold; + }; -private: - static GrayImage getRgbChannel(const QImage& image, RgbChannel channel); + static GrayImage getRgbChannel(const QImage& image, RgbChannel channel); - void reduceNoise(); + void reduceNoise(); - void fromRgb(const BinaryImage& image, - const QImage& originalImage, - int redThresholdAdjustment, - int greenThresholdAdjustment, - int blueThresholdAdjustment); + void fromRgb(const BinaryImage& image, + const QImage& originalImage, + int redThresholdAdjustment, + int greenThresholdAdjustment, + int blueThresholdAdjustment); - void fromGrayscale(const BinaryImage& image, const GrayImage& originalImage); + void fromGrayscale(const BinaryImage& image, const GrayImage& originalImage); - QImage buildRgbImage() const; + QImage buildRgbImage() const; - QImage buildGrayImage() const; + QImage buildGrayImage() const; - BinaryThreshold adjustThreshold(BinaryThreshold threshold, int adjustment); + BinaryThreshold adjustThreshold(BinaryThreshold threshold, int adjustment); - Settings settings; - ConnectivityMap segmentsMap; - QImage originalImage; + Settings m_settings; + ConnectivityMap m_segmentsMap; + QImage m_originalImage; }; } // namespace imageproc diff --git a/imageproc/ColorTable.cpp b/imageproc/ColorTable.cpp index c80fd353e..7d2cc6502 100644 --- a/imageproc/ColorTable.cpp +++ b/imageproc/ColorTable.cpp @@ -1,45 +1,45 @@ +#include "ColorTable.h" #include -#include #include -#include "ColorTable.h" +#include #include "BinaryImage.h" namespace imageproc { ColorTable::ColorTable(const QImage& image) { - if ((image.format() != QImage::Format_Indexed8) && (image.format() != QImage::Format_RGB32) - && (image.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ColorTable: Image format not supported."); - } + if ((image.format() != QImage::Format_Indexed8) && (image.format() != QImage::Format_RGB32) + && (image.format() != QImage::Format_ARGB32)) { + throw std::invalid_argument("ColorTable: Image format not supported."); + } - this->image = image; + this->m_image = image; } QImage ColorTable::getImage() const { - return image; + return m_image; } QVector ColorTable::getPalette() const { - std::unordered_map paletteMap; - switch (image.format()) { - case QImage::Format_Indexed8: - paletteMap = paletteFromIndexedWithStatistics(); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - paletteMap = paletteFromRgbWithStatistics(); - break; - default: - return QVector(); - } - - QVector palette(static_cast(paletteMap.size())); - for (const auto& colorAndStat : paletteMap) { - palette.push_back(colorAndStat.first); - } - - return palette; + std::unordered_map paletteMap; + switch (m_image.format()) { + case QImage::Format_Indexed8: + paletteMap = paletteFromIndexedWithStatistics(); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + paletteMap = paletteFromRgbWithStatistics(); + break; + default: + return QVector(); + } + + QVector palette(static_cast(paletteMap.size())); + for (const auto& colorAndStat : paletteMap) { + palette.push_back(colorAndStat.first); + } + + return palette; } @@ -48,347 +48,346 @@ ColorTable& ColorTable::posterize(const int level, const bool forceBlackAndWhite, const int normalizeBlackLevel, const int normalizeWhiteLevel) { - if ((level < 2) || (level > 255)) { - throw std::invalid_argument("ColorTable: level must be a value between 2 and 255 inclusive"); - } - if ((level == 255) && !normalize && !forceBlackAndWhite) { + if ((level < 2) || (level > 255)) { + throw std::invalid_argument("ColorTable: level must be a value between 2 and 255 inclusive"); + } + if ((level == 255) && !normalize && !forceBlackAndWhite) { + return *this; + } + + std::unordered_map oldToNewColorMap; + size_t newColorTableSize; + + { + // Get the palette with statistics. + std::unordered_map paletteStatMap; + switch (m_image.format()) { + case QImage::Format_Indexed8: + paletteStatMap = paletteFromIndexedWithStatistics(); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + paletteStatMap = paletteFromRgbWithStatistics(); + break; + default: return *this; } - std::unordered_map oldToNewColorMap; - size_t newColorTableSize; - - { - // Get the palette with statistics. - std::unordered_map paletteStatMap; - switch (image.format()) { - case QImage::Format_Indexed8: - paletteStatMap = paletteFromIndexedWithStatistics(); - break; - case QImage::Format_RGB32: - case QImage::Format_ARGB32: - paletteStatMap = paletteFromRgbWithStatistics(); - break; - default: - return *this; - } + // We have to normalize palette in order posterization to work with pale images. + std::unordered_map colorToNormalizedMap + = normalizePalette(paletteStatMap, normalizeBlackLevel, normalizeWhiteLevel); - // We have to normalize palette in order posterization to work with pale images. - std::unordered_map colorToNormalizedMap - = normalizePalette(paletteStatMap, normalizeBlackLevel, normalizeWhiteLevel); + // Build color groups resulted from splitting RGB space + std::unordered_map> groupMap; + const double levelStride = 255.0 / level; + for (const auto& colorAndStat : paletteStatMap) { + const uint32_t color = colorAndStat.first; + const uint32_t normalized_color = colorToNormalizedMap[color]; - // Build color groups resulted from splitting RGB space - std::unordered_map> groupMap; - const double levelStride = 255.0 / level; - for (const auto& colorAndStat : paletteStatMap) { - const uint32_t color = colorAndStat.first; - const uint32_t normalized_color = colorToNormalizedMap[color]; + auto redGroupIdx = static_cast(qRed(normalized_color) / levelStride); + auto blueGroupIdx = static_cast(qGreen(normalized_color) / levelStride); + auto greenGroupIdx = static_cast(qBlue(normalized_color) / levelStride); - auto redGroupIdx = static_cast(qRed(normalized_color) / levelStride); - auto blueGroupIdx = static_cast(qGreen(normalized_color) / levelStride); - auto greenGroupIdx = static_cast(qBlue(normalized_color) / levelStride); + auto group = static_cast((redGroupIdx << 16) | (greenGroupIdx << 8) | (blueGroupIdx)); - auto group = static_cast((redGroupIdx << 16) | (greenGroupIdx << 8) | (blueGroupIdx)); + groupMap[group].push_back(color); + } + + // Find the most often occurring color in the group and map the other colors in the group to that. + for (const auto& groupAndColors : groupMap) { + const std::list& colors = groupAndColors.second; + assert(!colors.empty()); - groupMap[group].push_back(color); + uint32_t mostOftenColorInGroup = *colors.begin(); + for (auto it = ++colors.begin(); it != colors.end(); ++it) { + if (paletteStatMap[*it] > paletteStatMap[mostOftenColorInGroup]) { + mostOftenColorInGroup = *it; } + } - // Find the most often occurring color in the group and map the other colors in the group to that. - for (const auto& groupAndColors : groupMap) { - const std::list& colors = groupAndColors.second; - assert(!colors.empty()); - - uint32_t mostOftenColorInGroup = *colors.begin(); - for (auto it = ++colors.begin(); it != colors.end(); ++it) { - if (paletteStatMap[*it] > paletteStatMap[mostOftenColorInGroup]) { - mostOftenColorInGroup = *it; - } - } - - if (forceBlackAndWhite) { - if (normalize) { - colorToNormalizedMap[0xff000000u] = 0xff000000u; - colorToNormalizedMap[0xffffffffu] = 0xffffffffu; - } - makeGrayBlackOrWhiteInPlace(mostOftenColorInGroup, colorToNormalizedMap[mostOftenColorInGroup]); - } - - for (const uint32_t& color : colors) { - oldToNewColorMap[color] - = normalize ? colorToNormalizedMap[mostOftenColorInGroup] : mostOftenColorInGroup; - } + if (forceBlackAndWhite) { + if (normalize) { + colorToNormalizedMap[0xff000000u] = 0xff000000u; + colorToNormalizedMap[0xffffffffu] = 0xffffffffu; } + makeGrayBlackOrWhiteInPlace(mostOftenColorInGroup, colorToNormalizedMap[mostOftenColorInGroup]); + } - newColorTableSize = groupMap.size(); + for (const uint32_t& color : colors) { + oldToNewColorMap[color] = normalize ? colorToNormalizedMap[mostOftenColorInGroup] : mostOftenColorInGroup; + } } - if (image.format() == QImage::Format_Indexed8) { - remapColorsInIndexedImage(oldToNewColorMap); + newColorTableSize = groupMap.size(); + } + + if (m_image.format() == QImage::Format_Indexed8) { + remapColorsInIndexedImage(oldToNewColorMap); + } else { + if (newColorTableSize <= 256) { + buildIndexedImageFromRgb(oldToNewColorMap); } else { - if (newColorTableSize <= 256) { - buildIndexedImageFromRgb(oldToNewColorMap); - } else { - remapColorsInRgbImage(oldToNewColorMap); - } + remapColorsInRgbImage(oldToNewColorMap); } + } - return *this; + return *this; } std::unordered_map ColorTable::paletteFromIndexedWithStatistics() const { - std::unordered_map palette; + std::unordered_map palette; - const int width = image.width(); - const int height = image.height(); + const int width = m_image.width(); + const int height = m_image.height(); - const uint8_t* img_line = image.bits(); - const int img_stride = image.bytesPerLine(); + const uint8_t* img_line = m_image.bits(); + const int img_stride = m_image.bytesPerLine(); - QVector colorTable = image.colorTable(); + QVector colorTable = m_image.colorTable(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint8_t colorIndex = img_line[x]; - uint32_t color = colorTable[colorIndex]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint8_t colorIndex = img_line[x]; + uint32_t color = colorTable[colorIndex]; - ++palette[color]; - } - img_line += img_stride; + ++palette[color]; } + img_line += img_stride; + } - return palette; + return palette; } std::unordered_map ColorTable::paletteFromRgbWithStatistics() const { - std::unordered_map palette; + std::unordered_map palette; - const int width = image.width(); - const int height = image.height(); + const int width = m_image.width(); + const int height = m_image.height(); - const auto* img_line = reinterpret_cast(image.bits()); - const int img_stride = image.bytesPerLine() / sizeof(uint32_t); + const auto* img_line = reinterpret_cast(m_image.bits()); + const int img_stride = m_image.bytesPerLine() / sizeof(uint32_t); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t color = img_line[x]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t color = img_line[x]; - ++palette[color]; - } - img_line += img_stride; + ++palette[color]; } + img_line += img_stride; + } - return palette; + return palette; } void ColorTable::remapColorsInIndexedImage(const std::unordered_map& colorMap) { - std::unordered_map colorToIndexMap; - uint8_t index = 0; - for (const auto& srcAndDstColors : colorMap) { - if (colorToIndexMap.find(srcAndDstColors.second) == colorToIndexMap.end()) { - colorToIndexMap[srcAndDstColors.second] = index++; - } + std::unordered_map colorToIndexMap; + uint8_t index = 0; + for (const auto& srcAndDstColors : colorMap) { + if (colorToIndexMap.find(srcAndDstColors.second) == colorToIndexMap.end()) { + colorToIndexMap[srcAndDstColors.second] = index++; } + } - { - const int width = image.width(); - const int height = image.height(); + { + const int width = m_image.width(); + const int height = m_image.height(); - uint8_t* img_line = image.bits(); - const int img_stride = image.bytesPerLine(); + uint8_t* img_line = m_image.bits(); + const int img_stride = m_image.bytesPerLine(); - QVector colorTable = image.colorTable(); + QVector colorTable = m_image.colorTable(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t color = colorTable[img_line[x]]; - uint32_t newColor = colorMap.at(color); - img_line[x] = colorToIndexMap[newColor]; - } - img_line += img_stride; - } + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t color = colorTable[img_line[x]]; + uint32_t newColor = colorMap.at(color); + img_line[x] = colorToIndexMap[newColor]; + } + img_line += img_stride; } + } - QVector newColorTable(static_cast(colorToIndexMap.size())); - for (const auto& colorAndIndex : colorToIndexMap) { - newColorTable[colorAndIndex.second] = colorAndIndex.first; - } + QVector newColorTable(static_cast(colorToIndexMap.size())); + for (const auto& colorAndIndex : colorToIndexMap) { + newColorTable[colorAndIndex.second] = colorAndIndex.first; + } - image.setColorTable(newColorTable); + m_image.setColorTable(newColorTable); } void ColorTable::remapColorsInRgbImage(const std::unordered_map& colorMap) { - const int width = image.width(); - const int height = image.height(); + const int width = m_image.width(); + const int height = m_image.height(); - auto* img_line = reinterpret_cast(image.bits()); - const int img_stride = image.bytesPerLine() / sizeof(uint32_t); + auto* img_line = reinterpret_cast(m_image.bits()); + const int img_stride = m_image.bytesPerLine() / sizeof(uint32_t); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t color = img_line[x]; - img_line[x] = colorMap.at(color); - } - img_line += img_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t color = img_line[x]; + img_line[x] = colorMap.at(color); } + img_line += img_stride; + } } void ColorTable::buildIndexedImageFromRgb(const std::unordered_map& colorMap) { - remapColorsInRgbImage(colorMap); + remapColorsInRgbImage(colorMap); - std::unordered_set colorSet; - for (const auto& srcAndDstColors : colorMap) { - colorSet.insert(srcAndDstColors.second); - } + std::unordered_set colorSet; + for (const auto& srcAndDstColors : colorMap) { + colorSet.insert(srcAndDstColors.second); + } - QVector newColorTable(static_cast(colorSet.size())); - for (const auto& color : colorSet) { - newColorTable.push_back(color); - } + QVector newColorTable(static_cast(colorSet.size())); + for (const auto& color : colorSet) { + newColorTable.push_back(color); + } - image = toIndexedImage(&newColorTable); + m_image = toIndexedImage(&newColorTable); } std::unordered_map ColorTable::normalizePalette(const std::unordered_map& palette, const int normalizeBlackLevel, const int normalizeWhiteLevel) const { - const int pixelCount = image.width() * image.height(); - const double threshold = 0.0005; // mustn't be larger than (1 / 256) - - int min_level = 255; - int max_level = 0; - { - // Build RGB histogram from colors with statistics - int red_hist[256] = {}; - int green_hist[256] = {}; - int blue_hist[256] = {}; - for (const auto& colorAndStat : palette) { - const uint32_t color = colorAndStat.first; - const int statistics = colorAndStat.second; - - if (color == 0xff000000u) { - red_hist[normalizeBlackLevel] += statistics; - green_hist[normalizeBlackLevel] += statistics; - blue_hist[normalizeBlackLevel] += statistics; - continue; - } - if (color == 0xffffffffu) { - red_hist[normalizeWhiteLevel] += statistics; - green_hist[normalizeWhiteLevel] += statistics; - blue_hist[normalizeWhiteLevel] += statistics; - continue; - } - - red_hist[qRed(color)] += statistics; - green_hist[qGreen(color)] += statistics; - blue_hist[qBlue(color)] += statistics; - } - - // Find the max and min levels discarding a noise - for (int level = 0; level < 256; ++level) { - if (((double(red_hist[level]) / pixelCount) >= threshold) - || ((double(green_hist[level]) / pixelCount) >= threshold) - || ((double(blue_hist[level]) / pixelCount) >= threshold)) { - if (level < min_level) { - min_level = level; - } - if (level > max_level) { - max_level = level; - } - } - } - - assert(max_level >= min_level); + const int pixelCount = m_image.width() * m_image.height(); + const double threshold = 0.0005; // mustn't be larger than (1 / 256) + + int min_level = 255; + int max_level = 0; + { + // Build RGB histogram from colors with statistics + int red_hist[256] = {}; + int green_hist[256] = {}; + int blue_hist[256] = {}; + for (const auto& colorAndStat : palette) { + const uint32_t color = colorAndStat.first; + const int statistics = colorAndStat.second; + + if (color == 0xff000000u) { + red_hist[normalizeBlackLevel] += statistics; + green_hist[normalizeBlackLevel] += statistics; + blue_hist[normalizeBlackLevel] += statistics; + continue; + } + if (color == 0xffffffffu) { + red_hist[normalizeWhiteLevel] += statistics; + green_hist[normalizeWhiteLevel] += statistics; + blue_hist[normalizeWhiteLevel] += statistics; + continue; + } + + red_hist[qRed(color)] += statistics; + green_hist[qGreen(color)] += statistics; + blue_hist[qBlue(color)] += statistics; } - std::unordered_map colorToNormalizedMap; - for (const auto& colorAndStat : palette) { - const uint32_t color = colorAndStat.first; - if (color == 0xff000000u) { - colorToNormalizedMap[0xff000000u] = 0xff000000u; - continue; + // Find the max and min levels discarding a noise + for (int level = 0; level < 256; ++level) { + if (((double(red_hist[level]) / pixelCount) >= threshold) + || ((double(green_hist[level]) / pixelCount) >= threshold) + || ((double(blue_hist[level]) / pixelCount) >= threshold)) { + if (level < min_level) { + min_level = level; } - if (color == 0xffffffffu) { - colorToNormalizedMap[0xffffffffu] = 0xffffffffu; - continue; + if (level > max_level) { + max_level = level; } + } + } - int normalizedRed = qRound((double(qRed(color) - min_level) / (max_level - min_level)) * 255); - int normalizedGreen = qRound((double(qGreen(color) - min_level) / (max_level - min_level)) * 255); - int normalizedBlue = qRound((double(qBlue(color) - min_level) / (max_level - min_level)) * 255); - normalizedRed = qBound(0, normalizedRed, 255); - normalizedGreen = qBound(0, normalizedGreen, 255); - normalizedBlue = qBound(0, normalizedBlue, 255); + assert(max_level >= min_level); + } - colorToNormalizedMap[color] = qRgb(normalizedRed, normalizedGreen, normalizedBlue); + std::unordered_map colorToNormalizedMap; + for (const auto& colorAndStat : palette) { + const uint32_t color = colorAndStat.first; + if (color == 0xff000000u) { + colorToNormalizedMap[0xff000000u] = 0xff000000u; + continue; + } + if (color == 0xffffffffu) { + colorToNormalizedMap[0xffffffffu] = 0xffffffffu; + continue; } - return colorToNormalizedMap; + int normalizedRed = qRound((double(qRed(color) - min_level) / (max_level - min_level)) * 255); + int normalizedGreen = qRound((double(qGreen(color) - min_level) / (max_level - min_level)) * 255); + int normalizedBlue = qRound((double(qBlue(color) - min_level) / (max_level - min_level)) * 255); + normalizedRed = qBound(0, normalizedRed, 255); + normalizedGreen = qBound(0, normalizedGreen, 255); + normalizedBlue = qBound(0, normalizedBlue, 255); + + colorToNormalizedMap[color] = qRgb(normalizedRed, normalizedGreen, normalizedBlue); + } + + return colorToNormalizedMap; } namespace { bool isGray(const QColor& color) { - const double saturation = color.saturationF(); - const double value = color.valueF(); + const double saturation = color.saturationF(); + const double value = color.valueF(); - const double coefficient = std::max(.0, ((std::max(saturation, value) - 0.28) / 0.72)) + 1; + const double coefficient = std::max(.0, ((std::max(saturation, value) - 0.28) / 0.72)) + 1; - return (saturation * value) < (0.1 * coefficient); + return (saturation * value) < (0.1 * coefficient); } } // namespace void ColorTable::makeGrayBlackOrWhiteInPlace(QRgb& rgb, const QRgb& normalized) const { - const QColor color = QColor(normalized).toHsv(); - - if (isGray(color)) { - const int grayLevel = qGray(normalized); - const QColor grayColor = QColor(grayLevel, grayLevel, grayLevel).toHsl(); - if (grayColor.lightnessF() <= 0.5) { - rgb = 0xff000000u; - } else if (grayColor.lightnessF() >= 0.8) { - rgb = 0xffffffffu; - } + const QColor color = QColor(normalized).toHsv(); + + if (isGray(color)) { + const int grayLevel = qGray(normalized); + const QColor grayColor = QColor(grayLevel, grayLevel, grayLevel).toHsl(); + if (grayColor.lightnessF() <= 0.5) { + rgb = 0xff000000u; + } else if (grayColor.lightnessF() >= 0.8) { + rgb = 0xffffffffu; } + } } QImage ColorTable::toIndexedImage(const QVector* colorTable) const { - if (image.format() == QImage::Format_Indexed8) { - return image; - } + if (m_image.format() == QImage::Format_Indexed8) { + return m_image; + } - const QVector& palette = (colorTable) ? *colorTable : getPalette(); - if (palette.size() > 256) { - return image; - } + const QVector& palette = (colorTable) ? *colorTable : getPalette(); + if (palette.size() > 256) { + return m_image; + } - QImage dst(image.size(), QImage::Format_Indexed8); - dst.setColorTable(palette); + QImage dst(m_image.size(), QImage::Format_Indexed8); + dst.setColorTable(palette); - const int width = image.width(); - const int height = image.height(); + const int width = m_image.width(); + const int height = m_image.height(); - const auto* img_line = reinterpret_cast(image.bits()); - const int img_stride = image.bytesPerLine() / sizeof(uint32_t); + const auto* img_line = reinterpret_cast(m_image.bits()); + const int img_stride = m_image.bytesPerLine() / sizeof(uint32_t); - uint8_t* dst_line = dst.bits(); - const int dst_stride = dst.bytesPerLine(); + uint8_t* dst_line = dst.bits(); + const int dst_stride = dst.bytesPerLine(); - std::unordered_map colorToIndex; - for (int i = 0; i < palette.size(); ++i) { - colorToIndex[palette[i]] = static_cast(i); - } + std::unordered_map colorToIndex; + for (int i = 0; i < palette.size(); ++i) { + colorToIndex[palette[i]] = static_cast(i); + } - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - dst_line[x] = colorToIndex[img_line[x]]; - } - img_line += img_stride; - dst_line += dst_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + dst_line[x] = colorToIndex[img_line[x]]; } + img_line += img_stride; + dst_line += dst_stride; + } - dst.setDotsPerMeterX(image.dotsPerMeterX()); - dst.setDotsPerMeterY(image.dotsPerMeterY()); + dst.setDotsPerMeterX(m_image.dotsPerMeterX()); + dst.setDotsPerMeterY(m_image.dotsPerMeterY()); - return dst; + return dst; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ColorTable.h b/imageproc/ColorTable.h index 35847846d..75a27f83c 100644 --- a/imageproc/ColorTable.h +++ b/imageproc/ColorTable.h @@ -8,40 +8,39 @@ namespace imageproc { class ColorTable { -private: - QImage image; + public: + explicit ColorTable(const QImage& image); -public: - explicit ColorTable(const QImage& image); + ColorTable& posterize(int level, + bool normalize = false, + bool forceBlackAndWhite = false, + int normalizeBlackLevel = 0, + int normalizeWhiteLevel = 255); - ColorTable& posterize(int level, - bool normalize = false, - bool forceBlackAndWhite = false, - int normalizeBlackLevel = 0, - int normalizeWhiteLevel = 255); + QVector getPalette() const; - QVector getPalette() const; + QImage getImage() const; - QImage getImage() const; + QImage toIndexedImage(const QVector* colorTable = nullptr) const; - QImage toIndexedImage(const QVector* colorTable = nullptr) const; + private: + std::unordered_map paletteFromIndexedWithStatistics() const; -private: - std::unordered_map paletteFromIndexedWithStatistics() const; + std::unordered_map paletteFromRgbWithStatistics() const; - std::unordered_map paletteFromRgbWithStatistics() const; + void remapColorsInIndexedImage(const std::unordered_map& colorMap); - void remapColorsInIndexedImage(const std::unordered_map& colorMap); + void remapColorsInRgbImage(const std::unordered_map& colorMap); - void remapColorsInRgbImage(const std::unordered_map& colorMap); + void buildIndexedImageFromRgb(const std::unordered_map& colorMap); - void buildIndexedImageFromRgb(const std::unordered_map& colorMap); + std::unordered_map normalizePalette(const std::unordered_map& palette, + int normalizeBlackLevel = 0, + int normalizeWhiteLevel = 255) const; - std::unordered_map normalizePalette(const std::unordered_map& palette, - int normalizeBlackLevel = 0, - int normalizeWhiteLevel = 255) const; + void makeGrayBlackOrWhiteInPlace(QRgb& rgb, const QRgb& normalized) const; - void makeGrayBlackOrWhiteInPlace(QRgb& rgb, const QRgb& normalized) const; + QImage m_image; }; } // namespace imageproc diff --git a/imageproc/ConnComp.h b/imageproc/ConnComp.h index 764c0455e..f767d9cce 100644 --- a/imageproc/ConnComp.h +++ b/imageproc/ConnComp.h @@ -26,47 +26,33 @@ namespace imageproc { * \brief Represents a connected group of pixels. */ class ConnComp { -public: - ConnComp() : m_pixCount(0) { - } + public: + ConnComp() : m_pixCount(0) {} - ConnComp(const QPoint& seed, const QRect& rect, int pix_count) : m_seed(seed), m_rect(rect), m_pixCount(pix_count) { - } + ConnComp(const QPoint& seed, const QRect& rect, int pix_count) : m_seed(seed), m_rect(rect), m_pixCount(pix_count) {} - bool isNull() const { - return m_rect.isNull(); - } + bool isNull() const { return m_rect.isNull(); } - /** - * \brief Get an arbitrary black pixel position. - * - * The position is in containing image coordinates, - * not in the bounding box coordinates. - */ - const QPoint& seed() const { - return m_seed; - } + /** + * \brief Get an arbitrary black pixel position. + * + * The position is in containing image coordinates, + * not in the bounding box coordinates. + */ + const QPoint& seed() const { return m_seed; } - int width() const { - return m_rect.width(); - } + int width() const { return m_rect.width(); } - int height() const { - return m_rect.height(); - } + int height() const { return m_rect.height(); } - const QRect& rect() const { - return m_rect; - } + const QRect& rect() const { return m_rect; } - int pixCount() const { - return m_pixCount; - } + int pixCount() const { return m_pixCount; } -private: - QPoint m_seed; - QRect m_rect; - int m_pixCount; + private: + QPoint m_seed; + QRect m_rect; + int m_pixCount; }; } // namespace imageproc #endif // ifndef IMAGEPROC_CONNCOMP_H_ diff --git a/imageproc/ConnCompEraser.cpp b/imageproc/ConnCompEraser.cpp index a11ce2318..db320db2a 100644 --- a/imageproc/ConnCompEraser.cpp +++ b/imageproc/ConnCompEraser.cpp @@ -21,321 +21,316 @@ * This version is optimized to elliminate all multiplications. */ #include "ConnCompEraser.h" -#include "BitOps.h" #include +#include "BitOps.h" namespace imageproc { struct ConnCompEraser::BBox { - int xmin; - int xmax; - int ymin; - int ymax; + int xmin; + int xmax; + int ymin; + int ymax; - BBox(int x, int y) : xmin(x), xmax(x), ymin(y), ymax(y) { - } + BBox(int x, int y) : xmin(x), xmax(x), ymin(y), ymax(y) {} - int width() const { - return xmax - xmin + 1; - } + int width() const { return xmax - xmin + 1; } - int height() const { - return ymax - ymin + 1; - } + int height() const { return ymax - ymin + 1; } }; inline uint32_t ConnCompEraser::getBit(const uint32_t* const line, const int x) { - const uint32_t mask = (uint32_t(1) << 31) >> (x & 31); + const uint32_t mask = (uint32_t(1) << 31) >> (x & 31); - return line[x >> 5] & mask; + return line[x >> 5] & mask; } inline void ConnCompEraser::clearBit(uint32_t* const line, const int x) { - const uint32_t mask = (uint32_t(1) << 31) >> (x & 31); - line[x >> 5] &= ~mask; + const uint32_t mask = (uint32_t(1) << 31) >> (x & 31); + line[x >> 5] &= ~mask; } ConnCompEraser::ConnCompEraser(const BinaryImage& image, Connectivity conn) - : m_image(image), - m_pLine(nullptr), - m_width(m_image.width()), - m_height(m_image.height()), - m_wpl(m_image.wordsPerLine()), - m_connectivity(conn), - m_x(0), - m_y(0) { - // By initializing m_pLine with 0 instead of m_image.data(), - // we avoid copy-on-write, provided that the caller used image.release(). + : m_image(image), + m_line(nullptr), + m_width(m_image.width()), + m_height(m_image.height()), + m_wpl(m_image.wordsPerLine()), + m_connectivity(conn), + m_x(0), + m_y(0) { + // By initializing m_line with 0 instead of m_image.data(), + // we avoid copy-on-write, provided that the caller used image.release(). } ConnComp ConnCompEraser::nextConnComp() { - if (!moveToNextBlackPixel()) { - return ConnComp(); - } - - if (m_connectivity == CONN4) { - return eraseConnComp4(); - } else { - return eraseConnComp8(); - } + if (!moveToNextBlackPixel()) { + return ConnComp(); + } + + if (m_connectivity == CONN4) { + return eraseConnComp4(); + } else { + return eraseConnComp8(); + } } void ConnCompEraser::pushSegSameDir(const Segment& seg, int xleft, int xright, BBox& bbox) { - bbox.xmin = std::min(bbox.xmin, xleft); - bbox.xmax = std::max(bbox.xmax, xright); - bbox.ymin = std::min(bbox.ymin, seg.y); - bbox.ymax = std::max(bbox.ymax, seg.y); - - const int new_dy = seg.dy; - const int new_dy_wpl = seg.dy_wpl; - const int new_y = seg.y + new_dy; - if ((new_y >= 0) && (new_y < m_height)) { - Segment new_seg{}; - new_seg.line = seg.line + new_dy_wpl; - new_seg.xleft = xleft; - new_seg.xright = xright; - new_seg.y = new_y; - new_seg.dy = new_dy; - new_seg.dy_wpl = new_dy_wpl; - m_segStack.push(new_seg); - } + bbox.xmin = std::min(bbox.xmin, xleft); + bbox.xmax = std::max(bbox.xmax, xright); + bbox.ymin = std::min(bbox.ymin, seg.y); + bbox.ymax = std::max(bbox.ymax, seg.y); + + const int new_dy = seg.dy; + const int new_dy_wpl = seg.dy_wpl; + const int new_y = seg.y + new_dy; + if ((new_y >= 0) && (new_y < m_height)) { + Segment new_seg{}; + new_seg.line = seg.line + new_dy_wpl; + new_seg.xleft = xleft; + new_seg.xright = xright; + new_seg.y = new_y; + new_seg.dy = new_dy; + new_seg.dy_wpl = new_dy_wpl; + m_segStack.push(new_seg); + } } void ConnCompEraser::pushSegInvDir(const Segment& seg, int xleft, int xright, BBox& bbox) { - bbox.xmin = std::min(bbox.xmin, xleft); - bbox.xmax = std::max(bbox.xmax, xright); - bbox.ymin = std::min(bbox.ymin, seg.y); - bbox.ymax = std::max(bbox.ymax, seg.y); - - const int new_dy = -seg.dy; - const int new_dy_wpl = -seg.dy_wpl; - const int new_y = seg.y + new_dy; - if ((new_y >= 0) && (new_y < m_height)) { - Segment new_seg{}; - new_seg.line = seg.line + new_dy_wpl; - new_seg.xleft = xleft; - new_seg.xright = xright; - new_seg.y = new_y; - new_seg.dy = new_dy; - new_seg.dy_wpl = new_dy_wpl; - m_segStack.push(new_seg); - } + bbox.xmin = std::min(bbox.xmin, xleft); + bbox.xmax = std::max(bbox.xmax, xright); + bbox.ymin = std::min(bbox.ymin, seg.y); + bbox.ymax = std::max(bbox.ymax, seg.y); + + const int new_dy = -seg.dy; + const int new_dy_wpl = -seg.dy_wpl; + const int new_y = seg.y + new_dy; + if ((new_y >= 0) && (new_y < m_height)) { + Segment new_seg{}; + new_seg.line = seg.line + new_dy_wpl; + new_seg.xleft = xleft; + new_seg.xright = xright; + new_seg.y = new_y; + new_seg.dy = new_dy; + new_seg.dy_wpl = new_dy_wpl; + m_segStack.push(new_seg); + } } void ConnCompEraser::pushInitialSegments() { - assert(m_x >= 0 && m_x < m_width); - assert(m_y >= 0 && m_y < m_height); - - if (m_y + 1 < m_height) { - Segment seg1{}; - seg1.line = m_pLine + m_wpl; - seg1.xleft = m_x; - seg1.xright = m_x; - seg1.y = m_y + 1; - seg1.dy = 1; - seg1.dy_wpl = m_wpl; - m_segStack.push(seg1); - } - - Segment seg2{}; - seg2.line = m_pLine; - seg2.xleft = m_x; - seg2.xright = m_x; - seg2.y = m_y; - seg2.dy = -1; - seg2.dy_wpl = -m_wpl; - m_segStack.push(seg2); + assert(m_x >= 0 && m_x < m_width); + assert(m_y >= 0 && m_y < m_height); + + if (m_y + 1 < m_height) { + Segment seg1{}; + seg1.line = m_line + m_wpl; + seg1.xleft = m_x; + seg1.xright = m_x; + seg1.y = m_y + 1; + seg1.dy = 1; + seg1.dy_wpl = m_wpl; + m_segStack.push(seg1); + } + + Segment seg2{}; + seg2.line = m_line; + seg2.xleft = m_x; + seg2.xright = m_x; + seg2.y = m_y; + seg2.dy = -1; + seg2.dy_wpl = -m_wpl; + m_segStack.push(seg2); } bool ConnCompEraser::moveToNextBlackPixel() { - if (m_image.isNull()) { - return false; - } - - if (!m_pLine) { - // By initializing m_pLine with 0 instead of m_image.data(), - // we allow the caller to delete his copy of the image - // to avoid copy-on-write. - // We could also try to avoid copy-on-write in the case of - // a completely white image, but I don't think it's worth it. - m_pLine = m_image.data(); - } - - uint32_t* line = m_pLine; - const uint32_t* pword = line + (m_x >> 5); - - // Stop word is a last word in line that holds data. - const int last_bit_idx = m_width - 1; - const uint32_t* p_stop_word = line + (last_bit_idx >> 5); - const uint32_t stop_word_mask = ~uint32_t(0) << (31 - (last_bit_idx & 31)); - - uint32_t word = *pword; - if (pword == p_stop_word) { - word &= stop_word_mask; - } - word <<= (m_x & 31); - if (word) { + if (m_image.isNull()) { + return false; + } + + if (!m_line) { + // By initializing m_line with 0 instead of m_image.data(), + // we allow the caller to delete his copy of the image + // to avoid copy-on-write. + // We could also try to avoid copy-on-write in the case of + // a completely white image, but I don't think it's worth it. + m_line = m_image.data(); + } + + uint32_t* line = m_line; + const uint32_t* pword = line + (m_x >> 5); + + // Stop word is a last word in line that holds data. + const int last_bit_idx = m_width - 1; + const uint32_t* p_stop_word = line + (last_bit_idx >> 5); + const uint32_t stop_word_mask = ~uint32_t(0) << (31 - (last_bit_idx & 31)); + + uint32_t word = *pword; + if (pword == p_stop_word) { + word &= stop_word_mask; + } + word <<= (m_x & 31); + if (word) { + const int shift = countMostSignificantZeroes(word); + m_x += shift; + assert(m_x < m_width); + + return true; + } + + int y = m_y; + if (pword != p_stop_word) { + ++pword; + } else { + ++y; + line += m_wpl; + p_stop_word += m_wpl; + pword = line; + } + + for (; y < m_height; ++y) { + for (; pword != p_stop_word; ++pword) { + word = *pword; + if (word) { const int shift = countMostSignificantZeroes(word); - m_x += shift; + m_x = static_cast(((pword - line) << 5) + shift); assert(m_x < m_width); + m_y = y; + m_line = line; return true; + } } + // Handle the stop word (some bits need to be ignored). + assert(pword == p_stop_word); + word = *pword & stop_word_mask; + if (word) { + const int shift = countMostSignificantZeroes(word); + m_x = static_cast(((pword - line) << 5) + shift); + assert(m_x < m_width); + m_y = y; + m_line = line; - int y = m_y; - if (pword != p_stop_word) { - ++pword; - } else { - ++y; - line += m_wpl; - p_stop_word += m_wpl; - pword = line; + return true; } - for (; y < m_height; ++y) { - for (; pword != p_stop_word; ++pword) { - word = *pword; - if (word) { - const int shift = countMostSignificantZeroes(word); - m_x = static_cast(((pword - line) << 5) + shift); - assert(m_x < m_width); - m_y = y; - m_pLine = line; - - return true; - } - } - // Handle the stop word (some bits need to be ignored). - assert(pword == p_stop_word); - word = *pword & stop_word_mask; - if (word) { - const int shift = countMostSignificantZeroes(word); - m_x = static_cast(((pword - line) << 5) + shift); - assert(m_x < m_width); - m_y = y; - m_pLine = line; - - return true; - } - - line += m_wpl; - p_stop_word += m_wpl; - pword = line; - } + line += m_wpl; + p_stop_word += m_wpl; + pword = line; + } - return false; + return false; } // ConnCompEraser::moveToNextBlackPixel ConnComp ConnCompEraser::eraseConnComp4() { - pushInitialSegments(); - - BBox bbox(m_x, m_y); - int pix_count = 0; - - while (!m_segStack.empty()) { - // Pop a segment off the stack. - const Segment seg(m_segStack.top()); - m_segStack.pop(); - - const int xmax = std::min(seg.xright, m_width - 1); - - int x = seg.xleft; - for (; x >= 0 && getBit(seg.line, x); --x) { - clearBit(seg.line, x); - ++pix_count; - } - - int xstart = x + 1; - - if (x >= seg.xleft) { - // Pixel at seg.xleft was off and was not cleared. - goto skip; - } - - if (xstart < seg.xleft - 1) { - // Leak on left. - pushSegInvDir(seg, xstart, seg.xleft - 1, bbox); - } - - x = seg.xleft + 1; - - do { - for (; x < m_width && getBit(seg.line, x); ++x) { - clearBit(seg.line, x); - ++pix_count; - } - pushSegSameDir(seg, xstart, x - 1, bbox); - if (x > seg.xright + 1) { - // Leak on right. - pushSegInvDir(seg, seg.xright + 1, x - 1, bbox); - } - - skip: - for (++x; x <= xmax && !getBit(seg.line, x); ++x) { - // Skip white pixels. - } - xstart = x; - } while (x <= xmax); + pushInitialSegments(); + + BBox bbox(m_x, m_y); + int pix_count = 0; + + while (!m_segStack.empty()) { + // Pop a segment off the stack. + const Segment seg(m_segStack.top()); + m_segStack.pop(); + + const int xmax = std::min(seg.xright, m_width - 1); + + int x = seg.xleft; + for (; x >= 0 && getBit(seg.line, x); --x) { + clearBit(seg.line, x); + ++pix_count; + } + + int xstart = x + 1; + + if (x >= seg.xleft) { + // Pixel at seg.xleft was off and was not cleared. + goto skip; } - QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); + if (xstart < seg.xleft - 1) { + // Leak on left. + pushSegInvDir(seg, xstart, seg.xleft - 1, bbox); + } - return ConnComp(QPoint(m_x, m_y), rect, pix_count); + x = seg.xleft + 1; + + do { + for (; x < m_width && getBit(seg.line, x); ++x) { + clearBit(seg.line, x); + ++pix_count; + } + pushSegSameDir(seg, xstart, x - 1, bbox); + if (x > seg.xright + 1) { + // Leak on right. + pushSegInvDir(seg, seg.xright + 1, x - 1, bbox); + } + + skip: + for (++x; x <= xmax && !getBit(seg.line, x); ++x) { + // Skip white pixels. + } + xstart = x; + } while (x <= xmax); + } + + QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); + + return ConnComp(QPoint(m_x, m_y), rect, pix_count); } // ConnCompEraser::eraseConnComp4 ConnComp ConnCompEraser::eraseConnComp8() { - pushInitialSegments(); - - BBox bbox(m_x, m_y); - int pix_count = 0; - - while (!m_segStack.empty()) { - // Pop a segment off the stack. - const Segment seg(m_segStack.top()); - m_segStack.pop(); - - const int xmax = std::min(seg.xright + 1, m_width - 1); - - int x = seg.xleft - 1; - for (; x >= 0 && getBit(seg.line, x); --x) { - clearBit(seg.line, x); - ++pix_count; - } - - int xstart = x + 1; - - if (x >= seg.xleft - 1) { - // Pixel at seg.xleft - 1 was off and was not cleared. - goto skip; - } - - if (xstart < seg.xleft) { - // Leak on left. - pushSegInvDir(seg, xstart, seg.xleft - 1, bbox); - } - - x = seg.xleft; - do { - for (; x < m_width && getBit(seg.line, x); ++x) { - clearBit(seg.line, x); - ++pix_count; - } - pushSegSameDir(seg, xstart, x - 1, bbox); - if (x > seg.xright) { - // Leak on right. - pushSegInvDir(seg, seg.xright + 1, x - 1, bbox); - } - - skip: - for (++x; x <= xmax && !getBit(seg.line, x); ++x) { - // Skip white pixels. - } - xstart = x; - } while (x <= xmax); + pushInitialSegments(); + + BBox bbox(m_x, m_y); + int pix_count = 0; + + while (!m_segStack.empty()) { + // Pop a segment off the stack. + const Segment seg(m_segStack.top()); + m_segStack.pop(); + + const int xmax = std::min(seg.xright + 1, m_width - 1); + + int x = seg.xleft - 1; + for (; x >= 0 && getBit(seg.line, x); --x) { + clearBit(seg.line, x); + ++pix_count; } - QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); + int xstart = x + 1; + + if (x >= seg.xleft - 1) { + // Pixel at seg.xleft - 1 was off and was not cleared. + goto skip; + } + + if (xstart < seg.xleft) { + // Leak on left. + pushSegInvDir(seg, xstart, seg.xleft - 1, bbox); + } - return ConnComp(QPoint(m_x, m_y), rect, pix_count); + x = seg.xleft; + do { + for (; x < m_width && getBit(seg.line, x); ++x) { + clearBit(seg.line, x); + ++pix_count; + } + pushSegSameDir(seg, xstart, x - 1, bbox); + if (x > seg.xright) { + // Leak on right. + pushSegInvDir(seg, seg.xright + 1, x - 1, bbox); + } + + skip: + for (++x; x <= xmax && !getBit(seg.line, x); ++x) { + // Skip white pixels. + } + xstart = x; + } while (x <= xmax); + } + + QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); + + return ConnComp(QPoint(m_x, m_y), rect, pix_count); } // ConnCompEraser::eraseConnComp8 } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ConnCompEraser.h b/imageproc/ConnCompEraser.h index bad85978b..3cade4b76 100644 --- a/imageproc/ConnCompEraser.h +++ b/imageproc/ConnCompEraser.h @@ -19,12 +19,12 @@ #ifndef IMAGEPROC_CONNCOMPERASER_H_ #define IMAGEPROC_CONNCOMPERASER_H_ -#include "NonCopyable.h" -#include "Connectivity.h" -#include "ConnComp.h" -#include "BinaryImage.h" -#include #include +#include +#include "BinaryImage.h" +#include "ConnComp.h" +#include "Connectivity.h" +#include "NonCopyable.h" namespace imageproc { class ConnComp; @@ -33,83 +33,81 @@ class ConnComp; * \brief Erases connected components one by one and returns their bounding boxes. */ class ConnCompEraser { - DECLARE_NON_COPYABLE(ConnCompEraser) + DECLARE_NON_COPYABLE(ConnCompEraser) -public: - /** - * \brief Constructor. - * - * \param image The image from which connected components are to be erased. - * If you don't need the original image, pass image.release(), to - * avoid unnecessary copy-on-write. - * \param conn Defines which neighbouring pixels are considered to be connected. - */ - ConnCompEraser(const BinaryImage& image, Connectivity conn); + public: + /** + * \brief Constructor. + * + * \param image The image from which connected components are to be erased. + * If you don't need the original image, pass image.release(), to + * avoid unnecessary copy-on-write. + * \param conn Defines which neighbouring pixels are considered to be connected. + */ + ConnCompEraser(const BinaryImage& image, Connectivity conn); - /** - * \brief Erase the next connected component and return its bounding box. - * - * If there are no black pixels remaining, returns a null ConnComp. - */ - ConnComp nextConnComp(); + /** + * \brief Erase the next connected component and return its bounding box. + * + * If there are no black pixels remaining, returns a null ConnComp. + */ + ConnComp nextConnComp(); - /** - * \brief Returns the image in its present state. - * - * Every time nextConnComp() is called, a connected component - * is erased from the image, assuming there was one. - */ - const BinaryImage& image() const { - return m_image; - } + /** + * \brief Returns the image in its present state. + * + * Every time nextConnComp() is called, a connected component + * is erased from the image, assuming there was one. + */ + const BinaryImage& image() const { return m_image; } -private: - struct Segment { - uint32_t* line; + private: + struct Segment { + uint32_t* line; - /**< Pointer to the beginning of the line. */ - int xleft; + /**< Pointer to the beginning of the line. */ + int xleft; - /**< Leftmost pixel to process. */ - int xright; + /**< Leftmost pixel to process. */ + int xright; - /**< Rightmost pixel to process. */ - int y; + /**< Rightmost pixel to process. */ + int y; - /**< y value of the line to be processed. */ - int dy; + /**< y value of the line to be processed. */ + int dy; - /**< Vertical direction: 1 or -1. */ - int dy_wpl; /**< words_per_line or -words_per_line. */ - }; + /**< Vertical direction: 1 or -1. */ + int dy_wpl; /**< words_per_line or -words_per_line. */ + }; - struct BBox; + struct BBox; - void pushSegSameDir(const Segment& seg, int xleft, int xright, BBox& bbox); + void pushSegSameDir(const Segment& seg, int xleft, int xright, BBox& bbox); - void pushSegInvDir(const Segment& seg, int xleft, int xright, BBox& bbox); + void pushSegInvDir(const Segment& seg, int xleft, int xright, BBox& bbox); - void pushInitialSegments(); + void pushInitialSegments(); - bool moveToNextBlackPixel(); + bool moveToNextBlackPixel(); - ConnComp eraseConnComp4(); + ConnComp eraseConnComp4(); - ConnComp eraseConnComp8(); + ConnComp eraseConnComp8(); - static uint32_t getBit(const uint32_t* line, int x); + static uint32_t getBit(const uint32_t* line, int x); - static void clearBit(uint32_t* line, int x); + static void clearBit(uint32_t* line, int x); - BinaryImage m_image; - uint32_t* m_pLine; - const int m_width; - const int m_height; - const int m_wpl; - const Connectivity m_connectivity; - std::stack m_segStack; - int m_x; - int m_y; + BinaryImage m_image; + uint32_t* m_line; + const int m_width; + const int m_height; + const int m_wpl; + const Connectivity m_connectivity; + std::stack m_segStack; + int m_x; + int m_y; }; } // namespace imageproc #endif // ifndef IMAGEPROC_CONNCOMPERASER_H_ diff --git a/imageproc/ConnCompEraserExt.cpp b/imageproc/ConnCompEraserExt.cpp index ddda6a611..7a8cfd5ba 100644 --- a/imageproc/ConnCompEraserExt.cpp +++ b/imageproc/ConnCompEraserExt.cpp @@ -21,64 +21,63 @@ namespace imageproc { ConnCompEraserExt::ConnCompEraserExt(const BinaryImage& image, const Connectivity conn) - : m_eraser(image, conn), m_lastImage(image) { -} + : m_eraser(image, conn), m_lastImage(image) {} ConnComp ConnCompEraserExt::nextConnComp() { - if (!m_lastCC.isNull()) { - // Propagate the changes from m_eraser.image() to m_lastImage. - // We could copy the whole image, but instead we copy just - // the affected area, extending it to word boundries. - const QRect& rect = m_lastCC.rect(); - const BinaryImage& src = m_eraser.image(); - const size_t src_wpl = src.wordsPerLine(); - const size_t dst_wpl = m_lastImage.wordsPerLine(); - const size_t first_word_idx = rect.left() / 32; - // Note: rect.right() == rect.x() + rect.width() - 1 - const size_t span_length = (rect.right() + 31) / 32 - first_word_idx; - const size_t src_initial_offset = rect.top() * src_wpl + first_word_idx; - const size_t dst_initial_offset = rect.top() * dst_wpl + first_word_idx; - const uint32_t* src_pos = src.data() + src_initial_offset; - uint32_t* dst_pos = m_lastImage.data() + dst_initial_offset; - for (int i = rect.height(); i > 0; --i) { - memcpy(dst_pos, src_pos, span_length * 4); - src_pos += src_wpl; - dst_pos += dst_wpl; - } + if (!m_lastCC.isNull()) { + // Propagate the changes from m_eraser.image() to m_lastImage. + // We could copy the whole image, but instead we copy just + // the affected area, extending it to word boundries. + const QRect& rect = m_lastCC.rect(); + const BinaryImage& src = m_eraser.image(); + const size_t src_wpl = src.wordsPerLine(); + const size_t dst_wpl = m_lastImage.wordsPerLine(); + const size_t first_word_idx = rect.left() / 32; + // Note: rect.right() == rect.x() + rect.width() - 1 + const size_t span_length = (rect.right() + 31) / 32 - first_word_idx; + const size_t src_initial_offset = rect.top() * src_wpl + first_word_idx; + const size_t dst_initial_offset = rect.top() * dst_wpl + first_word_idx; + const uint32_t* src_pos = src.data() + src_initial_offset; + uint32_t* dst_pos = m_lastImage.data() + dst_initial_offset; + for (int i = rect.height(); i > 0; --i) { + memcpy(dst_pos, src_pos, span_length * 4); + src_pos += src_wpl; + dst_pos += dst_wpl; } + } - m_lastCC = m_eraser.nextConnComp(); + m_lastCC = m_eraser.nextConnComp(); - return m_lastCC; + return m_lastCC; } BinaryImage ConnCompEraserExt::computeConnCompImage() const { - if (m_lastCC.isNull()) { - return BinaryImage(); - } + if (m_lastCC.isNull()) { + return BinaryImage(); + } - return computeDiffImage(m_lastCC.rect()); + return computeDiffImage(m_lastCC.rect()); } BinaryImage ConnCompEraserExt::computeConnCompImageAligned(QRect* rect) const { - if (m_lastCC.isNull()) { - return BinaryImage(); - } + if (m_lastCC.isNull()) { + return BinaryImage(); + } - QRect r(m_lastCC.rect()); - r.setX((r.x() >> 5) << 5); - if (rect) { - *rect = r; - } + QRect r(m_lastCC.rect()); + r.setX((r.x() >> 5) << 5); + if (rect) { + *rect = r; + } - return computeDiffImage(r); + return computeDiffImage(r); } BinaryImage ConnCompEraserExt::computeDiffImage(const QRect& rect) const { - BinaryImage diff(rect.width(), rect.height()); - rasterOp(diff, diff.rect(), m_eraser.image(), rect.topLeft()); - rasterOp>(diff, diff.rect(), m_lastImage, rect.topLeft()); + BinaryImage diff(rect.width(), rect.height()); + rasterOp(diff, diff.rect(), m_eraser.image(), rect.topLeft()); + rasterOp>(diff, diff.rect(), m_lastImage, rect.topLeft()); - return diff; + return diff; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ConnCompEraserExt.h b/imageproc/ConnCompEraserExt.h index f01818dc9..d181ef737 100644 --- a/imageproc/ConnCompEraserExt.h +++ b/imageproc/ConnCompEraserExt.h @@ -19,11 +19,11 @@ #ifndef IMAGEPROC_CONNCOMPERASEREXT_H_ #define IMAGEPROC_CONNCOMPERASEREXT_H_ -#include "NonCopyable.h" -#include "Connectivity.h" -#include "ConnCompEraser.h" -#include "ConnComp.h" #include "BinaryImage.h" +#include "ConnComp.h" +#include "ConnCompEraser.h" +#include "Connectivity.h" +#include "NonCopyable.h" class QRect; @@ -32,63 +32,63 @@ namespace imageproc { * \brief Same as ConnCompEraser, except it provides images of connected components. */ class ConnCompEraserExt { - DECLARE_NON_COPYABLE(ConnCompEraserExt) + DECLARE_NON_COPYABLE(ConnCompEraserExt) -public: - /** - * \brief Constructor. - * - * \param image The image from which connected components are to be erased. - * If you don't need the original image, pass image.release(), to - * avoid unnecessary copy-on-write. - * \param conn Defines which neighbouring pixels are considered to be connected. - */ - ConnCompEraserExt(const BinaryImage& image, Connectivity conn); + public: + /** + * \brief Constructor. + * + * \param image The image from which connected components are to be erased. + * If you don't need the original image, pass image.release(), to + * avoid unnecessary copy-on-write. + * \param conn Defines which neighbouring pixels are considered to be connected. + */ + ConnCompEraserExt(const BinaryImage& image, Connectivity conn); - /** - * \brief Erase the next connected component and return its bounding box. - * - * If there are no black pixels remaining, returns a null ConnComp. - */ - ConnComp nextConnComp(); + /** + * \brief Erase the next connected component and return its bounding box. + * + * If there are no black pixels remaining, returns a null ConnComp. + */ + ConnComp nextConnComp(); - /** - * \brief Computes the image of the last connected component - * returned by nextConnComp(). - * - * In case nextConnComp() returned a null component or was never called, - * a null BinaryImage is returned. - */ - BinaryImage computeConnCompImage() const; + /** + * \brief Computes the image of the last connected component + * returned by nextConnComp(). + * + * In case nextConnComp() returned a null component or was never called, + * a null BinaryImage is returned. + */ + BinaryImage computeConnCompImage() const; - /** - * \brief Computes the image of the last connected component - * returned by nextConnComp(). - * - * The image may have some white padding on the left, to make - * its left coordinate word-aligned. This is useful if you - * are going to draw the component back to its position. - * Word-aligned connected components are faster to both - * extract and draw than non-aligned ones. - * \param rect If specified, the position and size of the - * aligned image, including padding, will be written into it. - * - * In case nextConnComp() returned a null component or was never called, - * a null BinaryImage is returned. - */ - BinaryImage computeConnCompImageAligned(QRect* rect = nullptr) const; + /** + * \brief Computes the image of the last connected component + * returned by nextConnComp(). + * + * The image may have some white padding on the left, to make + * its left coordinate word-aligned. This is useful if you + * are going to draw the component back to its position. + * Word-aligned connected components are faster to both + * extract and draw than non-aligned ones. + * \param rect If specified, the position and size of the + * aligned image, including padding, will be written into it. + * + * In case nextConnComp() returned a null component or was never called, + * a null BinaryImage is returned. + */ + BinaryImage computeConnCompImageAligned(QRect* rect = nullptr) const; -private: - ConnCompEraser m_eraser; + private: + ConnCompEraser m_eraser; - BinaryImage computeDiffImage(const QRect& rect) const; + BinaryImage computeDiffImage(const QRect& rect) const; - /** - * m_lastImage is always one step behind of m_eraser.image(). - * It contains the last connected component erased from m_eraser.image(). - */ - BinaryImage m_lastImage; - ConnComp m_lastCC; + /** + * m_lastImage is always one step behind of m_eraser.image(). + * It contains the last connected component erased from m_eraser.image(). + */ + BinaryImage m_lastImage; + ConnComp m_lastCC; }; } // namespace imageproc #endif // ifndef IMAGEPROC_CONNCOMPERASEREXT_H_ diff --git a/imageproc/Connectivity.h b/imageproc/Connectivity.h index 6a76e9598..a449cb24c 100644 --- a/imageproc/Connectivity.h +++ b/imageproc/Connectivity.h @@ -24,12 +24,12 @@ namespace imageproc { * \brief Defines which neighbouring pixels are considered to be connected. */ enum Connectivity { - /** North, east, south and west neighbours of a pixel - are considered to be connected to it. */ - CONN4, + /** North, east, south and west neighbours of a pixel + are considered to be connected to it. */ + CONN4, - /** All 8 neighbours are considered to be connected. */ - CONN8 + /** All 8 neighbours are considered to be connected. */ + CONN8 }; } // namespace imageproc #endif diff --git a/imageproc/ConnectivityMap.cpp b/imageproc/ConnectivityMap.cpp index 031b43ae1..74dfe9a98 100644 --- a/imageproc/ConnectivityMap.cpp +++ b/imageproc/ConnectivityMap.cpp @@ -17,579 +17,577 @@ */ #include "ConnectivityMap.h" +#include +#include #include "BinaryImage.h" -#include "InfluenceMap.h" #include "BitOps.h" -#include -#include +#include "InfluenceMap.h" namespace imageproc { const uint32_t ConnectivityMap::BACKGROUND = ~uint32_t(0); const uint32_t ConnectivityMap::UNTAGGED_FG = BACKGROUND - 1; -ConnectivityMap::ConnectivityMap() : m_pData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { -} +ConnectivityMap::ConnectivityMap() : m_plainData(nullptr), m_size(), m_stride(0), m_maxLabel(0) {} -ConnectivityMap::ConnectivityMap(const QSize& size) : m_pData(nullptr), m_size(size), m_stride(0), m_maxLabel(0) { - if (m_size.isEmpty()) { - return; - } +ConnectivityMap::ConnectivityMap(const QSize& size) : m_plainData(nullptr), m_size(size), m_stride(0), m_maxLabel(0) { + if (m_size.isEmpty()) { + return; + } - const int width = m_size.width(); - const int height = m_size.height(); + const int width = m_size.width(); + const int height = m_size.height(); - m_data.resize((width + 2) * (height + 2), 0); - m_stride = width + 2; - m_pData = &m_data[0] + 1 + m_stride; + m_data.resize((width + 2) * (height + 2), 0); + m_stride = width + 2; + m_plainData = &m_data[0] + 1 + m_stride; } ConnectivityMap::ConnectivityMap(const BinaryImage& image, const Connectivity conn) - : m_pData(nullptr), m_size(image.size()), m_stride(0), m_maxLabel(0) { - if (m_size.isEmpty()) { - return; - } + : m_plainData(nullptr), m_size(image.size()), m_stride(0), m_maxLabel(0) { + if (m_size.isEmpty()) { + return; + } - const int width = m_size.width(); - const int height = m_size.height(); + const int width = m_size.width(); + const int height = m_size.height(); - m_data.resize((width + 2) * (height + 2), BACKGROUND); - m_stride = width + 2; - m_pData = &m_data[0] + 1 + m_stride; + m_data.resize((width + 2) * (height + 2), BACKGROUND); + m_stride = width + 2; + m_plainData = &m_data[0] + 1 + m_stride; - uint32_t* dst = m_pData; - const int dst_stride = m_stride; + uint32_t* dst = m_plainData; + const int dst_stride = m_stride; - const uint32_t* src = image.data(); - const int src_stride = image.wordsPerLine(); + const uint32_t* src = image.data(); + const int src_stride = image.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (src[x >> 5] & (msb >> (x & 31))) { - dst[x] = UNTAGGED_FG; - } - } - src += src_stride; - dst += dst_stride; + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (src[x >> 5] & (msb >> (x & 31))) { + dst[x] = UNTAGGED_FG; + } } + src += src_stride; + dst += dst_stride; + } - assignIds(conn); + assignIds(conn); } ConnectivityMap::ConnectivityMap(const ConnectivityMap& other) - : m_data(other.m_data), - m_pData(nullptr), - m_size(other.size()), - m_stride(other.stride()), - m_maxLabel(other.m_maxLabel) { - if (!m_size.isEmpty()) { - m_pData = &m_data[0] + m_stride + 1; - } + : m_data(other.m_data), + m_plainData(nullptr), + m_size(other.size()), + m_stride(other.stride()), + m_maxLabel(other.m_maxLabel) { + if (!m_size.isEmpty()) { + m_plainData = &m_data[0] + m_stride + 1; + } } ConnectivityMap::ConnectivityMap(const InfluenceMap& imap) - : m_pData(nullptr), m_size(imap.size()), m_stride(imap.stride()), m_maxLabel(imap.maxLabel()) { - if (m_size.isEmpty()) { - return; - } + : m_plainData(nullptr), m_size(imap.size()), m_stride(imap.stride()), m_maxLabel(imap.maxLabel()) { + if (m_size.isEmpty()) { + return; + } - m_data.resize((m_size.width() + 2) * (m_size.height() + 2)); - copyFromInfluenceMap(imap); + m_data.resize((m_size.width() + 2) * (m_size.height() + 2)); + copyFromInfluenceMap(imap); } ConnectivityMap& ConnectivityMap::operator=(const ConnectivityMap& other) { - ConnectivityMap(other).swap(*this); + ConnectivityMap(other).swap(*this); - return *this; + return *this; } ConnectivityMap& ConnectivityMap::operator=(const InfluenceMap& imap) { - if ((m_size == imap.size()) && !m_size.isEmpty()) { - // Common case optimization. - copyFromInfluenceMap(imap); - } else { - ConnectivityMap(imap).swap(*this); - } + if ((m_size == imap.size()) && !m_size.isEmpty()) { + // Common case optimization. + copyFromInfluenceMap(imap); + } else { + ConnectivityMap(imap).swap(*this); + } - return *this; + return *this; } void ConnectivityMap::swap(ConnectivityMap& other) { - m_data.swap(other.m_data); - std::swap(m_pData, other.m_pData); - std::swap(m_size, other.m_size); - std::swap(m_stride, other.m_stride); - std::swap(m_maxLabel, other.m_maxLabel); + m_data.swap(other.m_data); + std::swap(m_plainData, other.m_plainData); + std::swap(m_size, other.m_size); + std::swap(m_stride, other.m_stride); + std::swap(m_maxLabel, other.m_maxLabel); } void ConnectivityMap::addComponent(const BinaryImage& image) { - if (m_size != image.size()) { - throw std::invalid_argument("ConnectivityMap::addComponent: sizes dont match"); - } - if (m_size.isEmpty()) { - return; - } - - const int width = m_size.width(); - const int height = m_size.height(); - - uint32_t* dst = m_pData; - const int dst_stride = m_stride; - - const uint32_t* src = image.data(); - const int src_stride = image.wordsPerLine(); - - const uint32_t new_label = m_maxLabel + 1; - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (src[x >> 5] & (msb >> (x & 31))) { - dst[x] = new_label; - } - } - src += src_stride; - dst += dst_stride; - } - - m_maxLabel = new_label; + if (m_size != image.size()) { + throw std::invalid_argument("ConnectivityMap::addComponent: sizes dont match"); + } + if (m_size.isEmpty()) { + return; + } + + const int width = m_size.width(); + const int height = m_size.height(); + + uint32_t* dst = m_plainData; + const int dst_stride = m_stride; + + const uint32_t* src = image.data(); + const int src_stride = image.wordsPerLine(); + + const uint32_t new_label = m_maxLabel + 1; + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (src[x >> 5] & (msb >> (x & 31))) { + dst[x] = new_label; + } + } + src += src_stride; + dst += dst_stride; + } + + m_maxLabel = new_label; } // ConnectivityMap::addComponent void ConnectivityMap::addComponents(const BinaryImage& image, const Connectivity conn) { - if (m_size != image.size()) { - throw std::invalid_argument("ConnectivityMap::addComponents: sizes don't match"); - } - if (m_size.isEmpty()) { - return; - } - - addComponents(ConnectivityMap(image, conn)); + if (m_size != image.size()) { + throw std::invalid_argument("ConnectivityMap::addComponents: sizes don't match"); + } + if (m_size.isEmpty()) { + return; + } + + addComponents(ConnectivityMap(image, conn)); } void ConnectivityMap::addComponents(const ConnectivityMap& other) { - if (m_size != other.m_size) { - throw std::invalid_argument("ConnectivityMap::addComponents: sizes don't match"); - } - if (m_size.isEmpty()) { - return; - } + if (m_size != other.m_size) { + throw std::invalid_argument("ConnectivityMap::addComponents: sizes don't match"); + } + if (m_size.isEmpty()) { + return; + } - const int width = m_size.width(); - const int height = m_size.height(); + const int width = m_size.width(); + const int height = m_size.height(); - uint32_t* dst_line = m_pData; - const int dst_stride = m_stride; + uint32_t* dst_line = m_plainData; + const int dst_stride = m_stride; - const uint32_t* src_line = other.m_pData; - const int src_stride = other.m_stride; + const uint32_t* src_line = other.m_plainData; + const int src_stride = other.m_stride; - uint32_t new_max_label = m_maxLabel; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t src_label = src_line[x]; - if (src_label == 0) { - continue; - } + uint32_t new_max_label = m_maxLabel; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t src_label = src_line[x]; + if (src_label == 0) { + continue; + } - const uint32_t dst_label = m_maxLabel + src_label; - new_max_label = std::max(new_max_label, dst_label); + const uint32_t dst_label = m_maxLabel + src_label; + new_max_label = std::max(new_max_label, dst_label); - dst_line[x] = dst_label; - } - src_line += src_stride; - dst_line += dst_stride; + dst_line[x] = dst_label; } + src_line += src_stride; + dst_line += dst_stride; + } - m_maxLabel = new_max_label; + m_maxLabel = new_max_label; } void ConnectivityMap::removeComponents(const std::unordered_set& labelsSet) { - if (m_size.isEmpty() || labelsSet.empty()) { - return; - } + if (m_size.isEmpty() || labelsSet.empty()) { + return; + } - std::vector map(m_maxLabel, 0); - uint32_t next_label = 1; - for (uint32_t i = 0; i < m_maxLabel; i++) { - if (labelsSet.find(i + 1) == labelsSet.end()) { - map[i] = next_label; - next_label++; - } + std::vector map(m_maxLabel, 0); + uint32_t next_label = 1; + for (uint32_t i = 0; i < m_maxLabel; i++) { + if (labelsSet.find(i + 1) == labelsSet.end()) { + map[i] = next_label; + next_label++; } + } - for (uint32_t& label : m_data) { - if (label != 0) { - label = map[label - 1]; - } + for (uint32_t& label : m_data) { + if (label != 0) { + label = map[label - 1]; } + } - m_maxLabel = next_label - 1; + m_maxLabel = next_label - 1; } BinaryImage ConnectivityMap::getBinaryMask() const { - if (m_size.isEmpty()) { - return BinaryImage(); - } + if (m_size.isEmpty()) { + return BinaryImage(); + } - BinaryImage dst(m_size, WHITE); + BinaryImage dst(m_size, WHITE); - const int width = m_size.width(); - const int height = m_size.height(); + const int width = m_size.width(); + const int height = m_size.height(); - uint32_t* dst_line = dst.data(); - const int dst_stride = dst.wordsPerLine(); + uint32_t* dst_line = dst.data(); + const int dst_stride = dst.wordsPerLine(); - const uint32_t* src_line = m_pData; - const int src_stride = m_stride; + const uint32_t* src_line = m_plainData; + const int src_stride = m_stride; - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (src_line[x] != 0) { - dst_line[x >> 5] |= (msb >> (x & 31)); - } - } - src_line += src_stride; - dst_line += dst_stride; + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (src_line[x] != 0) { + dst_line[x >> 5] |= (msb >> (x & 31)); + } } + src_line += src_stride; + dst_line += dst_stride; + } - return dst; + return dst; } QImage ConnectivityMap::visualized(QColor bg_color) const { - if (m_size.isEmpty()) { - return QImage(); - } - - const int width = m_size.width(); - const int height = m_size.height(); - // Convert to premultiplied RGBA. - bg_color = bg_color.toRgb(); - bg_color.setRedF(bg_color.redF() * bg_color.alphaF()); - bg_color.setGreenF(bg_color.greenF() * bg_color.alphaF()); - bg_color.setBlueF(bg_color.blueF() * bg_color.alphaF()); - - QImage dst(m_size, QImage::Format_ARGB32); - dst.fill(bg_color.rgba()); - - const uint32_t* src_line = m_pData; - const int src_stride = m_stride; - - auto* dst_line = reinterpret_cast(dst.bits()); - const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t val = src_line[x]; - if (val == 0) { - continue; - } - - const int bits_unused = countMostSignificantZeroes(val); - const uint32_t reversed = reverseBits(val) >> bits_unused; - const uint32_t mask = ~uint32_t(0) >> bits_unused; - - const double H = 0.99 * (double(reversed) / mask); - const double S = 1.0; - const double V = 1.0; - QColor color; - color.setHsvF(H, S, V, 1.0); - - dst_line[x] = color.rgba(); - } - src_line += src_stride; - dst_line += dst_stride; - } - - return dst; + if (m_size.isEmpty()) { + return QImage(); + } + + const int width = m_size.width(); + const int height = m_size.height(); + // Convert to premultiplied RGBA. + bg_color = bg_color.toRgb(); + bg_color.setRedF(bg_color.redF() * bg_color.alphaF()); + bg_color.setGreenF(bg_color.greenF() * bg_color.alphaF()); + bg_color.setBlueF(bg_color.blueF() * bg_color.alphaF()); + + QImage dst(m_size, QImage::Format_ARGB32); + dst.fill(bg_color.rgba()); + + const uint32_t* src_line = m_plainData; + const int src_stride = m_stride; + + auto* dst_line = reinterpret_cast(dst.bits()); + const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t val = src_line[x]; + if (val == 0) { + continue; + } + + const int bits_unused = countMostSignificantZeroes(val); + const uint32_t reversed = reverseBits(val) >> bits_unused; + const uint32_t mask = ~uint32_t(0) >> bits_unused; + + const double H = 0.99 * (double(reversed) / mask); + const double S = 1.0; + const double V = 1.0; + QColor color; + color.setHsvF(H, S, V, 1.0); + + dst_line[x] = color.rgba(); + } + src_line += src_stride; + dst_line += dst_stride; + } + + return dst; } // ConnectivityMap::visualized void ConnectivityMap::copyFromInfluenceMap(const InfluenceMap& imap) { - assert(!imap.size().isEmpty()); - assert(imap.size() == m_size); - - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - uint32_t* dst = &m_data[0]; - const InfluenceMap::Cell* src = imap.paddedData(); - for (int todo = width * height; todo > 0; --todo) { - *dst = src->label; - ++dst; - ++src; - } + assert(!imap.size().isEmpty()); + assert(imap.size() == m_size); + + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + uint32_t* dst = &m_data[0]; + const InfluenceMap::Cell* src = imap.paddedData(); + for (int todo = width * height; todo > 0; --todo) { + *dst = src->label; + ++dst; + ++src; + } } void ConnectivityMap::assignIds(const Connectivity conn) { - const uint32_t num_initial_tags = initialTagging(); - std::vector table(num_initial_tags, 0); - - switch (conn) { - case CONN4: - spreadMin4(); - break; - case CONN8: - spreadMin8(); - break; - } + const uint32_t num_initial_tags = initialTagging(); + std::vector table(num_initial_tags, 0); - markUsedIds(table); + switch (conn) { + case CONN4: + spreadMin4(); + break; + case CONN8: + spreadMin8(); + break; + } - uint32_t next_label = 1; - for (uint32_t i = 0; i < num_initial_tags; ++i) { - if (table[i]) { - table[i] = next_label; - ++next_label; - } + markUsedIds(table); + + uint32_t next_label = 1; + for (uint32_t i = 0; i < num_initial_tags; ++i) { + if (table[i]) { + table[i] = next_label; + ++next_label; } + } - remapIds(table); + remapIds(table); - m_maxLabel = next_label - 1; + m_maxLabel = next_label - 1; } /** * Tags every object pixel that has a non-object pixel to the left. */ uint32_t ConnectivityMap::initialTagging() { - const int width = m_size.width(); - const int height = m_size.height(); + const int width = m_size.width(); + const int height = m_size.height(); - uint32_t next_label = 1; + uint32_t next_label = 1; - uint32_t* line = m_pData; - const int stride = m_stride; + uint32_t* line = m_plainData; + const int stride = m_stride; - for (int y = 0; y < height; ++y, line += stride) { - for (int x = 0; x < width; ++x) { - if ((line[x - 1] == BACKGROUND) && (line[x] == UNTAGGED_FG)) { - line[x] = next_label; - ++next_label; - } - } + for (int y = 0; y < height; ++y, line += stride) { + for (int x = 0; x < width; ++x) { + if ((line[x - 1] == BACKGROUND) && (line[x] == UNTAGGED_FG)) { + line[x] = next_label; + ++next_label; + } } + } - return next_label - 1; + return next_label - 1; } void ConnectivityMap::spreadMin4() { - const int width = m_size.width(); - const int height = m_size.height(); - const int stride = m_stride; - - uint32_t* line = m_pData; - uint32_t* prev_line = m_pData - stride; - // Top to bottom. - for (int y = 0; y < height; ++y) { - // Left to right. - for (int x = 0; x < width; ++x) { - if (line[x] == BACKGROUND) { - continue; - } - line[x] = std::min(prev_line[x], std::min(line[x - 1], line[x])); - } - - prev_line = line; - line += stride; + const int width = m_size.width(); + const int height = m_size.height(); + const int stride = m_stride; + + uint32_t* line = m_plainData; + uint32_t* prev_line = m_plainData - stride; + // Top to bottom. + for (int y = 0; y < height; ++y) { + // Left to right. + for (int x = 0; x < width; ++x) { + if (line[x] == BACKGROUND) { + continue; + } + line[x] = std::min(prev_line[x], std::min(line[x - 1], line[x])); } prev_line = line; - line -= stride; + line += stride; + } + + prev_line = line; + line -= stride; + + FastQueue queue; + + // Bottom to top. + for (int y = height - 1; y >= 0; --y) { + // Right to left. + for (int x = width - 1; x >= 0; --x) { + if (line[x] == BACKGROUND) { + continue; + } + + const uint32_t new_val = std::min(line[x + 1], prev_line[x]); - FastQueue queue; - - // Bottom to top. - for (int y = height - 1; y >= 0; --y) { - // Right to left. - for (int x = width - 1; x >= 0; --x) { - if (line[x] == BACKGROUND) { - continue; - } - - const uint32_t new_val = std::min(line[x + 1], prev_line[x]); - - if (new_val >= line[x]) { - continue; - } - - line[x] = new_val; - // We compare new_val + 1 < neighbor + 1 to - // make BACKGROUND neighbors overflow and become - // zero. - const uint32_t nvp1 = new_val + 1; - if ((nvp1 < line[x + 1] + 1) || (nvp1 < prev_line[x] + 1)) { - queue.push(&line[x]); - } - } - prev_line = line; - line -= stride; + if (new_val >= line[x]) { + continue; + } + + line[x] = new_val; + // We compare new_val + 1 < neighbor + 1 to + // make BACKGROUND neighbors overflow and become + // zero. + const uint32_t nvp1 = new_val + 1; + if ((nvp1 < line[x + 1] + 1) || (nvp1 < prev_line[x] + 1)) { + queue.push(&line[x]); + } } + prev_line = line; + line -= stride; + } - processQueue4(queue); + processQueue4(queue); } // ConnectivityMap::spreadMin4 void ConnectivityMap::spreadMin8() { - const int width = m_size.width(); - const int height = m_size.height(); - const int stride = m_stride; - - uint32_t* line = m_pData; - uint32_t* prev_line = m_pData - stride; - // Top to bottom. - for (int y = 0; y < height; ++y) { - // Left to right. - for (int x = 0; x < width; ++x) { - if (line[x] == BACKGROUND) { - continue; - } - line[x] = std::min( - std::min(std::min(prev_line[x - 1], prev_line[x]), std::min(prev_line[x + 1], line[x - 1])), - line[x]); - } - - prev_line = line; - line += stride; + const int width = m_size.width(); + const int height = m_size.height(); + const int stride = m_stride; + + uint32_t* line = m_plainData; + uint32_t* prev_line = m_plainData - stride; + // Top to bottom. + for (int y = 0; y < height; ++y) { + // Left to right. + for (int x = 0; x < width; ++x) { + if (line[x] == BACKGROUND) { + continue; + } + line[x] = std::min(std::min(std::min(prev_line[x - 1], prev_line[x]), std::min(prev_line[x + 1], line[x - 1])), + line[x]); } prev_line = line; - line -= stride; + line += stride; + } - FastQueue queue; + prev_line = line; + line -= stride; - // Bottom to top. - for (int y = height - 1; y >= 0; --y) { - for (int x = width - 1; x >= 0; --x) { - if (line[x] == BACKGROUND) { - continue; - } + FastQueue queue; - const uint32_t new_val - = std::min(std::min(prev_line[x - 1], prev_line[x]), std::min(prev_line[x + 1], line[x + 1])); + // Bottom to top. + for (int y = height - 1; y >= 0; --y) { + for (int x = width - 1; x >= 0; --x) { + if (line[x] == BACKGROUND) { + continue; + } - if (new_val >= line[x]) { - continue; - } + const uint32_t new_val + = std::min(std::min(prev_line[x - 1], prev_line[x]), std::min(prev_line[x + 1], line[x + 1])); - line[x] = new_val; + if (new_val >= line[x]) { + continue; + } - // We compare new_val + 1 < neighbor + 1 to - // make BACKGROUND neighbors overflow and become - // zero. - const uint32_t nvp1 = new_val + 1; - if ((nvp1 < prev_line[x - 1] + 1) || (nvp1 < prev_line[x] + 1) || (nvp1 < prev_line[x + 1] + 1) - || (nvp1 < line[x + 1] + 1)) { - queue.push(&line[x]); - } - } + line[x] = new_val; - prev_line = line; - line -= stride; + // We compare new_val + 1 < neighbor + 1 to + // make BACKGROUND neighbors overflow and become + // zero. + const uint32_t nvp1 = new_val + 1; + if ((nvp1 < prev_line[x - 1] + 1) || (nvp1 < prev_line[x] + 1) || (nvp1 < prev_line[x + 1] + 1) + || (nvp1 < line[x + 1] + 1)) { + queue.push(&line[x]); + } } - processQueue8(queue); + prev_line = line; + line -= stride; + } + + processQueue8(queue); } // ConnectivityMap::spreadMin8 void ConnectivityMap::processNeighbor(FastQueue& queue, const uint32_t this_val, uint32_t* neighbor) { - // *neighbor + 1 will overflow if *neighbor == BACKGROUND, - // which is what we want. - if (this_val + 1 < *neighbor + 1) { - *neighbor = this_val; - queue.push(neighbor); - } + // *neighbor + 1 will overflow if *neighbor == BACKGROUND, + // which is what we want. + if (this_val + 1 < *neighbor + 1) { + *neighbor = this_val; + queue.push(neighbor); + } } void ConnectivityMap::processQueue4(FastQueue& queue) { - const int stride = m_stride; + const int stride = m_stride; - while (!queue.empty()) { - uint32_t* p = queue.front(); - queue.pop(); + while (!queue.empty()) { + uint32_t* p = queue.front(); + queue.pop(); - const uint32_t this_val = *p; + const uint32_t this_val = *p; - // Northern neighbor. - p -= stride; - processNeighbor(queue, this_val, p); + // Northern neighbor. + p -= stride; + processNeighbor(queue, this_val, p); - // Eastern neighbor. - p += stride + 1; - processNeighbor(queue, this_val, p); + // Eastern neighbor. + p += stride + 1; + processNeighbor(queue, this_val, p); - // Southern neighbor. - p += stride - 1; - processNeighbor(queue, this_val, p); - // Western neighbor. - p -= stride + 1; - processNeighbor(queue, this_val, p); - } + // Southern neighbor. + p += stride - 1; + processNeighbor(queue, this_val, p); + // Western neighbor. + p -= stride + 1; + processNeighbor(queue, this_val, p); + } } void ConnectivityMap::processQueue8(FastQueue& queue) { - const int stride = m_stride; + const int stride = m_stride; - while (!queue.empty()) { - uint32_t* p = queue.front(); - queue.pop(); + while (!queue.empty()) { + uint32_t* p = queue.front(); + queue.pop(); - const uint32_t this_val = *p; + const uint32_t this_val = *p; - // Northern neighbor. - p -= stride; - processNeighbor(queue, this_val, p); + // Northern neighbor. + p -= stride; + processNeighbor(queue, this_val, p); - // North-eastern neighbor. - ++p; - processNeighbor(queue, this_val, p); + // North-eastern neighbor. + ++p; + processNeighbor(queue, this_val, p); - // Eastern neighbor. - p += stride; - processNeighbor(queue, this_val, p); + // Eastern neighbor. + p += stride; + processNeighbor(queue, this_val, p); - // South-eastern neighbor. - p += stride; - processNeighbor(queue, this_val, p); + // South-eastern neighbor. + p += stride; + processNeighbor(queue, this_val, p); - // Southern neighbor. - --p; - processNeighbor(queue, this_val, p); + // Southern neighbor. + --p; + processNeighbor(queue, this_val, p); - // South-western neighbor. - --p; - processNeighbor(queue, this_val, p); + // South-western neighbor. + --p; + processNeighbor(queue, this_val, p); - // Western neighbor. - p -= stride; - processNeighbor(queue, this_val, p); - // North-western neighbor. - p -= stride; - processNeighbor(queue, this_val, p); - } + // Western neighbor. + p -= stride; + processNeighbor(queue, this_val, p); + // North-western neighbor. + p -= stride; + processNeighbor(queue, this_val, p); + } } // ConnectivityMap::processQueue8 void ConnectivityMap::markUsedIds(std::vector& used_map) const { - const int width = m_size.width(); - const int height = m_size.height(); - const int stride = m_stride; - - const uint32_t* line = m_pData; - // Top to bottom. - for (int y = 0; y < height; ++y, line += stride) { - // Left to right. - for (int x = 0; x < width; ++x) { - if (line[x] == BACKGROUND) { - continue; - } - used_map[line[x] - 1] = 1; - } - } + const int width = m_size.width(); + const int height = m_size.height(); + const int stride = m_stride; + + const uint32_t* line = m_plainData; + // Top to bottom. + for (int y = 0; y < height; ++y, line += stride) { + // Left to right. + for (int x = 0; x < width; ++x) { + if (line[x] == BACKGROUND) { + continue; + } + used_map[line[x] - 1] = 1; + } + } } void ConnectivityMap::remapIds(const std::vector& map) { - for (uint32_t& label : m_data) { - if (label == BACKGROUND) { - label = 0; - } else { - label = map[label - 1]; - } + for (uint32_t& label : m_data) { + if (label == BACKGROUND) { + label = 0; + } else { + label = map[label - 1]; } + } } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ConnectivityMap.h b/imageproc/ConnectivityMap.h index 0adc6fc29..781322247 100644 --- a/imageproc/ConnectivityMap.h +++ b/imageproc/ConnectivityMap.h @@ -19,14 +19,14 @@ #ifndef IMAGEPROC_CONNECTIVITY_MAP_H_ #define IMAGEPROC_CONNECTIVITY_MAP_H_ -#include "Connectivity.h" -#include "FastQueue.h" -#include #include +#include #include -#include #include #include +#include +#include "Connectivity.h" +#include "FastQueue.h" class QImage; @@ -45,256 +45,240 @@ class InfluenceMap; * labels are guaranteed not to have gaps. */ class ConnectivityMap { -public: - /** - * \brief Constructs a null connectivity map. - * - * The data() and paddedData() methods return null on such maps. - */ - ConnectivityMap(); - - /** - * \brief Constructs an empty connectivity map. - * - * All cells will have the label of zero. - */ - explicit ConnectivityMap(const QSize& size); - - /** - * \brief Labels components in a binary image. - */ - ConnectivityMap(const BinaryImage& image, Connectivity conn); - - /** - * \brief Same as the version working with BinaryImage - * but allows pixels to be represented by any data type. - */ - template - ConnectivityMap(QSize size, const T* data, int units_per_line, Connectivity conn); - - ConnectivityMap(const ConnectivityMap& other); - - /** - * \brief Constructs a connectivity map from an influence map. - */ - explicit ConnectivityMap(const InfluenceMap& imap); - - ConnectivityMap& operator=(const ConnectivityMap& other); - - /** - * \brief Converts an influence map to a connectivity map. - */ - ConnectivityMap& operator=(const InfluenceMap& imap); - - void swap(ConnectivityMap& other); - - /** - * \brief Adds another connected component and assigns it - * a label of maxLabel() + 1. - * - * The maxLabel() will then be incremented afterwards. - * - * It's not mandatory for a component to actually be connected. - * In any case, all of its foreground (black) pixels will get - * the same label. - */ - void addComponent(const BinaryImage& image); - - /** - * Adds another connected components which are built from the input image - * with the given connectivity. - * - * The maxLabel() will become maxLabel() + N afterwards, - * where N - number of found connected components in the input image. - * - * Note: this does't connect components of the current and the input map - * even if they are actually connected. - * Use @class InfluenceMap for that purpose. - * - * @param image Image with the components necessary to be added. - * @param conn Connectivity used to build the map from the image. - */ - void addComponents(const BinaryImage& image, Connectivity conn); - - /** - * Adds another connected components from the another map. - * - * The maxLabel() will become maxLabel() + other.maxLabel() afterwards. - * - * Note: this does't connect components of the current and the input map - * even if they are actually connected. - * Use @class InfluenceMap for that purpose. - * - * @param other Map containing the components necessary to be added. - */ - void addComponents(const ConnectivityMap& other); - - /** - * Removes the connected components with the given labels. - * - * The maxLabel() will become maxLabel() - N afterwards, - * where N - the number of actually removed connected components. - * - * @param labelsSet Set of labels determining the components which need to be removed. - */ - void removeComponents(const std::unordered_set& labelsSet); - - /** - * Returns the mask of the current map. - * The mask returned have white background - * and all the labeled connected components are black. - * - * @return Binary mask of this connected map. - */ - BinaryImage getBinaryMask() const; - - /** - * \brief Returns a pointer to the top-left corner of the map. - * - * The data is stored in row-major order, and is padded, - * so moving to the next line requires adding stride() rather - * than size().width(). - */ - const uint32_t* data() const { - return m_pData; - } - - /** - * \brief Returns a pointer to the top-left corner of the map. - * - * The data is stored in row-major order, and is padded, - * so moving to the next line requires adding stride() rather - * than size().width(). - */ - uint32_t* data() { - return m_pData; - } - - /** - * \brief Returns a pointer to the top-left corner of padding of the map. - * - * The actually has a fake line from each side. Those lines are - * labelled as background (label 0). Sometimes it might be desirable - * to access that data. - */ - const uint32_t* paddedData() const { - return m_pData ? &m_data[0] : nullptr; - } - - /** - * \brief Returns a pointer to the top-left corner of padding of the map. - * - * The actually has a fake line from each side. Those lines are - * labelled as background (label 0). Sometimes it might be desirable - * to access that data. - */ - uint32_t* paddedData() { - return m_pData ? &m_data[0] : nullptr; - } - - /** - * \brief Returns non-padded dimensions of the map. - */ - QSize size() const { - return m_size; - } - - /** - * \brief Returns the number of units on a padded line. - * - * Whether working with padded or non-padded maps, adding - * this number to a data pointer will move it one line down. - */ - int stride() const { - return m_stride; - } - - /** - * \brief Returns the maximum label present on the map. - */ - uint32_t maxLabel() const { - return m_maxLabel; - } - - /** - * Updating the maximum label may be necessary after manually - * altering the map. - */ - void setMaxLabel(uint32_t max_label) { - m_maxLabel = max_label; - } - - /** - * \brief Visualizes each label with a different color. - * - * \param bg_color Background color. Transparency is supported. - */ - QImage visualized(QColor bg_color = Qt::black) const; - -private: - void copyFromInfluenceMap(const InfluenceMap& imap); - - void assignIds(Connectivity conn); - - uint32_t initialTagging(); - - void spreadMin4(); - - void spreadMin8(); - - void processNeighbor(FastQueue& queue, uint32_t this_val, uint32_t* neighbor); - - void processQueue4(FastQueue& queue); - - void processQueue8(FastQueue& queue); - - void markUsedIds(std::vector& used_map) const; - - void remapIds(const std::vector& map); - - static const uint32_t BACKGROUND; - static const uint32_t UNTAGGED_FG; - - std::vector m_data; - uint32_t* m_pData; - QSize m_size; - int m_stride; - uint32_t m_maxLabel; + public: + /** + * \brief Constructs a null connectivity map. + * + * The data() and paddedData() methods return null on such maps. + */ + ConnectivityMap(); + + /** + * \brief Constructs an empty connectivity map. + * + * All cells will have the label of zero. + */ + explicit ConnectivityMap(const QSize& size); + + /** + * \brief Labels components in a binary image. + */ + ConnectivityMap(const BinaryImage& image, Connectivity conn); + + /** + * \brief Same as the version working with BinaryImage + * but allows pixels to be represented by any data type. + */ + template + ConnectivityMap(QSize size, const T* data, int units_per_line, Connectivity conn); + + ConnectivityMap(const ConnectivityMap& other); + + /** + * \brief Constructs a connectivity map from an influence map. + */ + explicit ConnectivityMap(const InfluenceMap& imap); + + ConnectivityMap& operator=(const ConnectivityMap& other); + + /** + * \brief Converts an influence map to a connectivity map. + */ + ConnectivityMap& operator=(const InfluenceMap& imap); + + void swap(ConnectivityMap& other); + + /** + * \brief Adds another connected component and assigns it + * a label of maxLabel() + 1. + * + * The maxLabel() will then be incremented afterwards. + * + * It's not mandatory for a component to actually be connected. + * In any case, all of its foreground (black) pixels will get + * the same label. + */ + void addComponent(const BinaryImage& image); + + /** + * Adds another connected components which are built from the input image + * with the given connectivity. + * + * The maxLabel() will become maxLabel() + N afterwards, + * where N - number of found connected components in the input image. + * + * Note: this does't connect components of the current and the input map + * even if they are actually connected. + * Use @class InfluenceMap for that purpose. + * + * @param image Image with the components necessary to be added. + * @param conn Connectivity used to build the map from the image. + */ + void addComponents(const BinaryImage& image, Connectivity conn); + + /** + * Adds another connected components from the another map. + * + * The maxLabel() will become maxLabel() + other.maxLabel() afterwards. + * + * Note: this does't connect components of the current and the input map + * even if they are actually connected. + * Use @class InfluenceMap for that purpose. + * + * @param other Map containing the components necessary to be added. + */ + void addComponents(const ConnectivityMap& other); + + /** + * Removes the connected components with the given labels. + * + * The maxLabel() will become maxLabel() - N afterwards, + * where N - the number of actually removed connected components. + * + * @param labelsSet Set of labels determining the components which need to be removed. + */ + void removeComponents(const std::unordered_set& labelsSet); + + /** + * Returns the mask of the current map. + * The mask returned have white background + * and all the labeled connected components are black. + * + * @return Binary mask of this connected map. + */ + BinaryImage getBinaryMask() const; + + /** + * \brief Returns a pointer to the top-left corner of the map. + * + * The data is stored in row-major order, and is padded, + * so moving to the next line requires adding stride() rather + * than size().width(). + */ + const uint32_t* data() const { return m_plainData; } + + /** + * \brief Returns a pointer to the top-left corner of the map. + * + * The data is stored in row-major order, and is padded, + * so moving to the next line requires adding stride() rather + * than size().width(). + */ + uint32_t* data() { return m_plainData; } + + /** + * \brief Returns a pointer to the top-left corner of padding of the map. + * + * The actually has a fake line from each side. Those lines are + * labelled as background (label 0). Sometimes it might be desirable + * to access that data. + */ + const uint32_t* paddedData() const { return m_plainData ? &m_data[0] : nullptr; } + + /** + * \brief Returns a pointer to the top-left corner of padding of the map. + * + * The actually has a fake line from each side. Those lines are + * labelled as background (label 0). Sometimes it might be desirable + * to access that data. + */ + uint32_t* paddedData() { return m_plainData ? &m_data[0] : nullptr; } + + /** + * \brief Returns non-padded dimensions of the map. + */ + QSize size() const { return m_size; } + + /** + * \brief Returns the number of units on a padded line. + * + * Whether working with padded or non-padded maps, adding + * this number to a data pointer will move it one line down. + */ + int stride() const { return m_stride; } + + /** + * \brief Returns the maximum label present on the map. + */ + uint32_t maxLabel() const { return m_maxLabel; } + + /** + * Updating the maximum label may be necessary after manually + * altering the map. + */ + void setMaxLabel(uint32_t max_label) { m_maxLabel = max_label; } + + /** + * \brief Visualizes each label with a different color. + * + * \param bg_color Background color. Transparency is supported. + */ + QImage visualized(QColor bg_color = Qt::black) const; + + private: + void copyFromInfluenceMap(const InfluenceMap& imap); + + void assignIds(Connectivity conn); + + uint32_t initialTagging(); + + void spreadMin4(); + + void spreadMin8(); + + void processNeighbor(FastQueue& queue, uint32_t this_val, uint32_t* neighbor); + + void processQueue4(FastQueue& queue); + + void processQueue8(FastQueue& queue); + + void markUsedIds(std::vector& used_map) const; + + void remapIds(const std::vector& map); + + static const uint32_t BACKGROUND; + static const uint32_t UNTAGGED_FG; + + std::vector m_data; + uint32_t* m_plainData; + QSize m_size; + int m_stride; + uint32_t m_maxLabel; }; inline void swap(ConnectivityMap& o1, ConnectivityMap& o2) { - o1.swap(o2); + o1.swap(o2); } -template +template ConnectivityMap::ConnectivityMap(const QSize size, const T* src, const int src_stride, const Connectivity conn) - : m_pData(0), m_size(size), m_stride(0), m_maxLabel(0) { - if (size.isEmpty()) { - return; - } - - const int width = size.width(); - const int height = size.height(); - - m_data.resize((width + 2) * (height + 2), BACKGROUND); - m_stride = width + 2; - m_pData = &m_data[0] + 1 + m_stride; - - uint32_t* dst = m_pData; - const int dst_stride = m_stride; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (src[x] != T()) { - dst[x] = UNTAGGED_FG; - } - } - src += src_stride; - dst += dst_stride; + : m_plainData(0), m_size(size), m_stride(0), m_maxLabel(0) { + if (size.isEmpty()) { + return; + } + + const int width = size.width(); + const int height = size.height(); + + m_data.resize((width + 2) * (height + 2), BACKGROUND); + m_stride = width + 2; + m_plainData = &m_data[0] + 1 + m_stride; + + uint32_t* dst = m_plainData; + const int dst_stride = m_stride; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (src[x] != T()) { + dst[x] = UNTAGGED_FG; + } } + src += src_stride; + dst += dst_stride; + } - assignIds(conn); + assignIds(conn); } } // namespace imageproc #endif // ifndef IMAGEPROC_CONNECTIVITY_MAP_H_ diff --git a/imageproc/DrawOver.cpp b/imageproc/DrawOver.cpp index 67dda3999..50563e786 100644 --- a/imageproc/DrawOver.cpp +++ b/imageproc/DrawOver.cpp @@ -17,56 +17,56 @@ */ #include "DrawOver.h" -#include "BinaryImage.h" -#include "RasterOp.h" #include #include +#include "BinaryImage.h" +#include "RasterOp.h" namespace imageproc { void drawOver(QImage& dst, const QRect& dst_rect, const QImage& src, const QRect& src_rect) { - if (src_rect.size() != dst_rect.size()) { - throw std::invalid_argument("drawOver: source and destination areas have different sizes"); - } - if (dst.format() != src.format()) { - throw std::invalid_argument("drawOver: source and destination have different formats"); - } - if (dst_rect.intersected(dst.rect()) != dst_rect) { - throw std::invalid_argument("drawOver: destination area exceeds the image"); - } - if (src_rect.intersected(src.rect()) != src_rect) { - throw std::invalid_argument("drawOver: source area exceeds the image"); - } + if (src_rect.size() != dst_rect.size()) { + throw std::invalid_argument("drawOver: source and destination areas have different sizes"); + } + if (dst.format() != src.format()) { + throw std::invalid_argument("drawOver: source and destination have different formats"); + } + if (dst_rect.intersected(dst.rect()) != dst_rect) { + throw std::invalid_argument("drawOver: destination area exceeds the image"); + } + if (src_rect.intersected(src.rect()) != src_rect) { + throw std::invalid_argument("drawOver: source area exceeds the image"); + } - uint8_t* dst_line = dst.bits(); - const int dst_bpl = dst.bytesPerLine(); + uint8_t* dst_line = dst.bits(); + const int dst_bpl = dst.bytesPerLine(); - const uint8_t* src_line = src.bits(); - const int src_bpl = src.bytesPerLine(); + const uint8_t* src_line = src.bits(); + const int src_bpl = src.bytesPerLine(); - const int depth = src.depth(); - assert(dst.depth() == depth); + const int depth = src.depth(); + assert(dst.depth() == depth); - if (depth % 8 != 0) { - assert(depth == 1); + if (depth % 8 != 0) { + assert(depth == 1); - // Slow but simple. - BinaryImage dst_bin(dst); - BinaryImage src_bin(src); - rasterOp(dst_bin, dst_rect, src_bin, src_rect.topLeft()); - dst = dst_bin.toQImage().convertToFormat(dst.format()); - // FIXME: we are not preserving the color table. + // Slow but simple. + BinaryImage dst_bin(dst); + BinaryImage src_bin(src); + rasterOp(dst_bin, dst_rect, src_bin, src_rect.topLeft()); + dst = dst_bin.toQImage().convertToFormat(dst.format()); + // FIXME: we are not preserving the color table. - return; - } + return; + } - const int stripe_bytes = src_rect.width() * depth / 8; - dst_line += dst_bpl * dst_rect.top() + dst_rect.left() * depth / 8; - src_line += src_bpl * src_rect.top() + src_rect.left() * depth / 8; + const int stripe_bytes = src_rect.width() * depth / 8; + dst_line += dst_bpl * dst_rect.top() + dst_rect.left() * depth / 8; + src_line += src_bpl * src_rect.top() + src_rect.left() * depth / 8; - for (int i = src_rect.height(); i > 0; --i) { - memcpy(dst_line, src_line, stripe_bytes); - dst_line += dst_bpl; - src_line += src_bpl; - } + for (int i = src_rect.height(); i > 0; --i) { + memcpy(dst_line, src_line, stripe_bytes); + dst_line += dst_bpl; + src_line += src_bpl; + } } // drawOver } // namespace imageproc \ No newline at end of file diff --git a/imageproc/FindPeaksGeneric.h b/imageproc/FindPeaksGeneric.h index e2093d782..a0cbeeb1f 100644 --- a/imageproc/FindPeaksGeneric.h +++ b/imageproc/FindPeaksGeneric.h @@ -19,23 +19,26 @@ #ifndef IMAGEPROC_FIND_PEAKS_H_ #define IMAGEPROC_FIND_PEAKS_H_ -#include "BinaryImage.h" -#include "Connectivity.h" -#include "SeedFillGeneric.h" -#include "LocalMinMaxGeneric.h" -#include +#include #include #include -#include -#include -#include +#include #include #include +#include +#include +#include "BinaryImage.h" +#include "Connectivity.h" +#include "LocalMinMaxGeneric.h" +#include "SeedFillGeneric.h" namespace imageproc { namespace detail { namespace find_peaks { -template +template void raiseAllButPeaks(MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, @@ -46,25 +49,25 @@ void raiseAllButPeaks(MostSignificantSelector most_significant, QSize size, T* to_be_raised, int to_be_raised_stride) { - if (peak_neighborhood.isEmpty()) { - peak_neighborhood.setWidth(1); - peak_neighborhood.setHeight(1); - } + if (peak_neighborhood.isEmpty()) { + peak_neighborhood.setWidth(1); + peak_neighborhood.setHeight(1); + } - // Dilate the peaks and write results to seed. - QRect neighborhood(QPoint(0, 0), peak_neighborhood); - neighborhood.moveCenter(QPoint(0, 0)); - localMinMaxGeneric(most_significant, neighborhood, outside_values, input, input_stride, size, to_be_raised, - to_be_raised_stride); + // Dilate the peaks and write results to seed. + QRect neighborhood(QPoint(0, 0), peak_neighborhood); + neighborhood.moveCenter(QPoint(0, 0)); + localMinMaxGeneric(most_significant, neighborhood, outside_values, input, input_stride, size, to_be_raised, + to_be_raised_stride); - std::vector mask(to_be_raised, to_be_raised + to_be_raised_stride * size.height()); - const int mask_stride = to_be_raised_stride; + std::vector mask(to_be_raised, to_be_raised + to_be_raised_stride * size.height()); + const int mask_stride = to_be_raised_stride; - // Slightly raise the mask relative to to_be_raised. - std::transform(mask.begin(), mask.end(), mask.begin(), increase_significance); + // Slightly raise the mask relative to to_be_raised. + std::transform(mask.begin(), mask.end(), mask.begin(), increase_significance); - seedFillGenericInPlace(most_significant, least_significant, CONN8, &to_be_raised[0], to_be_raised_stride, size, - &mask[0], mask_stride); + seedFillGenericInPlace(most_significant, least_significant, CONN8, &to_be_raised[0], to_be_raised_stride, size, + &mask[0], mask_stride); } } // namespace find_peaks } // namespace detail @@ -99,12 +102,12 @@ void raiseAllButPeaks(MostSignificantSelector most_significant, * \param stride The size of a row in the data buffer, in terms of the number of T objects. * \param size Grid dimensions. */ -template +template void findPeaksInPlaceGeneric(MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, @@ -115,39 +118,42 @@ void findPeaksInPlaceGeneric(MostSignificantSelector most_significant, T* data, int stride, QSize size) { - if (size.isEmpty()) { - return; - } - - std::vector raised(size.width() * size.height()); - const int raised_stride = size.width(); - - detail::find_peaks::raiseAllButPeaks(most_significant, least_significant, increase_significance, peak_neighborhood, - outside_values, data, stride, size, &raised[0], raised_stride); - - T* data_line = data; - T* raised_line = &raised[0]; - const int w = size.width(); - const int h = size.height(); - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - if (data_line[x] == raised_line[x]) { - data_line[x] = peak_mutator(data_line[x]); - } else { - data_line[x] = non_peak_mutator(data_line[x]); - } - } - raised_line += raised_stride; - data_line += stride; + if (size.isEmpty()) { + return; + } + + std::vector raised(size.width() * size.height()); + const int raised_stride = size.width(); + + detail::find_peaks::raiseAllButPeaks(most_significant, least_significant, increase_significance, peak_neighborhood, + outside_values, data, stride, size, &raised[0], raised_stride); + + T* data_line = data; + T* raised_line = &raised[0]; + const int w = size.width(); + const int h = size.height(); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (data_line[x] == raised_line[x]) { + data_line[x] = peak_mutator(data_line[x]); + } else { + data_line[x] = non_peak_mutator(data_line[x]); + } } + raised_line += raised_stride; + data_line += stride; + } } // findPeaksInPlaceGeneric /** * \brief Same as findPeaksInPlaceGeneric(), but returning a binary image * rather than mutating the input data. */ -template +template BinaryImage findPeaksGeneric(MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, @@ -156,37 +162,37 @@ BinaryImage findPeaksGeneric(MostSignificantSelector most_significant, const T* data, int stride, QSize size) { - if (size.isEmpty()) { - return BinaryImage(); - } - - std::vector raised(size.width() * size.height()); - const int raised_stride = size.width(); - - detail::find_peaks::raiseAllButPeaks(most_significant, least_significant, increase_significance, peak_neighborhood, - outside_values, data, stride, size, &raised[0], raised_stride); - - BinaryImage peaks(size, WHITE); - uint32_t* peaks_line = peaks.data(); - const int peaks_stride = peaks.wordsPerLine(); - const T* data_line = data; - const T* raised_line = &raised[0]; - const int w = size.width(); - const int h = size.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - if (data_line[x] == raised_line[x]) { - peaks_line[x >> 5] |= msb >> (x & 31); - } - } - peaks_line += peaks_stride; - raised_line += raised_stride; - data_line += stride; + if (size.isEmpty()) { + return BinaryImage(); + } + + std::vector raised(size.width() * size.height()); + const int raised_stride = size.width(); + + detail::find_peaks::raiseAllButPeaks(most_significant, least_significant, increase_significance, peak_neighborhood, + outside_values, data, stride, size, &raised[0], raised_stride); + + BinaryImage peaks(size, WHITE); + uint32_t* peaks_line = peaks.data(); + const int peaks_stride = peaks.wordsPerLine(); + const T* data_line = data; + const T* raised_line = &raised[0]; + const int w = size.width(); + const int h = size.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (data_line[x] == raised_line[x]) { + peaks_line[x >> 5] |= msb >> (x & 31); + } } + peaks_line += peaks_stride; + raised_line += raised_stride; + data_line += stride; + } - return peaks; + return peaks; } // findPeaksGeneric } // namespace imageproc #endif // ifndef IMAGEPROC_FIND_PEAKS_H_ diff --git a/imageproc/GaussBlur.cpp b/imageproc/GaussBlur.cpp index 3f31534ac..258753b96 100644 --- a/imageproc/GaussBlur.cpp +++ b/imageproc/GaussBlur.cpp @@ -20,87 +20,87 @@ */ #include "GaussBlur.h" -#include "GrayImage.h" -#include "Constants.h" -#include #include +#include #include +#include "Constants.h" +#include "GrayImage.h" namespace imageproc { namespace gauss_blur_impl { void find_iir_constants(float* n_p, float* n_m, float* d_p, float* d_m, float* bd_p, float* bd_m, float std_dev) { - /* The constants used in the implemenation of a casual sequence - * using a 4th order approximation of the gaussian operator - */ - - const auto div = static_cast(std::sqrt(2.0 * constants::PI) * std_dev); - const auto x0 = static_cast(-1.783 / std_dev); - const auto x1 = static_cast(-1.723 / std_dev); - const auto x2 = static_cast(0.6318 / std_dev); - const auto x3 = static_cast(1.997 / std_dev); - const auto x4 = static_cast(1.6803 / div); - const auto x5 = static_cast(3.735 / div); - const auto x6 = static_cast(-0.6803 / div); - const auto x7 = static_cast(-0.2598 / div); - - n_p[0] = x4 + x6; - n_p[1] = std::exp(x1) * (x7 * std::sin(x3) - (x6 + 2 * x4) * std::cos(x3)) - + std::exp(x0) * (x5 * std::sin(x2) - (2 * x6 + x4) * std::cos(x2)); - n_p[2] = 2 * std::exp(x0 + x1) - * ((x4 + x6) * std::cos(x3) * std::cos(x2) - x5 * std::cos(x3) * std::sin(x2) - - x7 * std::cos(x2) * std::sin(x3)) - + x6 * std::exp(2 * x0) + x4 * std::exp(2 * x1); - n_p[3] = std::exp(x1 + 2 * x0) * (x7 * std::sin(x3) - x6 * std::cos(x3)) - + std::exp(x0 + 2 * x1) * (x5 * std::sin(x2) - x4 * std::cos(x2)); - n_p[4] = 0.0; - - d_p[0] = 0.0; - d_p[1] = -2 * std::exp(x1) * std::cos(x3) - 2 * std::exp(x0) * std::cos(x2); - d_p[2] = 4 * std::cos(x3) * std::cos(x2) * std::exp(x0 + x1) + std::exp(2 * x1) + std::exp(2 * x0); - d_p[3] = -2 * std::cos(x2) * std::exp(x0 + 2 * x1) - 2 * std::cos(x3) * std::exp(x1 + 2 * x0); - d_p[4] = std::exp(2 * x0 + 2 * x1); - - for (int i = 0; i <= 4; i++) { - d_m[i] = d_p[i]; - } - - n_m[0] = 0.0; - - for (int i = 1; i <= 4; i++) { - n_m[i] = n_p[i] - d_p[i] * n_p[0]; - } - - float sum_n_p = 0.0; - float sum_n_m = 0.0; - float sum_d = 0.0; - - for (int i = 0; i <= 4; i++) { - sum_n_p += n_p[i]; - sum_n_m += n_m[i]; - sum_d += d_p[i]; - } - - const auto a = static_cast(sum_n_p / (1.0 + sum_d)); - const auto b = static_cast(sum_n_m / (1.0 + sum_d)); - - for (int i = 0; i <= 4; i++) { - bd_p[i] = d_p[i] * a; - bd_m[i] = d_m[i] * b; - } + /* The constants used in the implemenation of a casual sequence + * using a 4th order approximation of the gaussian operator + */ + + const auto div = static_cast(std::sqrt(2.0 * constants::PI) * std_dev); + const auto x0 = static_cast(-1.783 / std_dev); + const auto x1 = static_cast(-1.723 / std_dev); + const auto x2 = static_cast(0.6318 / std_dev); + const auto x3 = static_cast(1.997 / std_dev); + const auto x4 = static_cast(1.6803 / div); + const auto x5 = static_cast(3.735 / div); + const auto x6 = static_cast(-0.6803 / div); + const auto x7 = static_cast(-0.2598 / div); + + n_p[0] = x4 + x6; + n_p[1] = std::exp(x1) * (x7 * std::sin(x3) - (x6 + 2 * x4) * std::cos(x3)) + + std::exp(x0) * (x5 * std::sin(x2) - (2 * x6 + x4) * std::cos(x2)); + n_p[2] = 2 * std::exp(x0 + x1) + * ((x4 + x6) * std::cos(x3) * std::cos(x2) - x5 * std::cos(x3) * std::sin(x2) + - x7 * std::cos(x2) * std::sin(x3)) + + x6 * std::exp(2 * x0) + x4 * std::exp(2 * x1); + n_p[3] = std::exp(x1 + 2 * x0) * (x7 * std::sin(x3) - x6 * std::cos(x3)) + + std::exp(x0 + 2 * x1) * (x5 * std::sin(x2) - x4 * std::cos(x2)); + n_p[4] = 0.0; + + d_p[0] = 0.0; + d_p[1] = -2 * std::exp(x1) * std::cos(x3) - 2 * std::exp(x0) * std::cos(x2); + d_p[2] = 4 * std::cos(x3) * std::cos(x2) * std::exp(x0 + x1) + std::exp(2 * x1) + std::exp(2 * x0); + d_p[3] = -2 * std::cos(x2) * std::exp(x0 + 2 * x1) - 2 * std::cos(x3) * std::exp(x1 + 2 * x0); + d_p[4] = std::exp(2 * x0 + 2 * x1); + + for (int i = 0; i <= 4; i++) { + d_m[i] = d_p[i]; + } + + n_m[0] = 0.0; + + for (int i = 1; i <= 4; i++) { + n_m[i] = n_p[i] - d_p[i] * n_p[0]; + } + + float sum_n_p = 0.0; + float sum_n_m = 0.0; + float sum_d = 0.0; + + for (int i = 0; i <= 4; i++) { + sum_n_p += n_p[i]; + sum_n_m += n_m[i]; + sum_d += d_p[i]; + } + + const auto a = static_cast(sum_n_p / (1.0 + sum_d)); + const auto b = static_cast(sum_n_m / (1.0 + sum_d)); + + for (int i = 0; i <= 4; i++) { + bd_p[i] = d_p[i] * a; + bd_m[i] = d_m[i] * b; + } } // find_iir_constants } // namespace gauss_blur_impl GrayImage gaussBlur(const GrayImage& src, float h_sigma, float v_sigma) { - using namespace boost::lambda; + using namespace boost::lambda; - if (src.isNull()) { - return src; - } + if (src.isNull()) { + return src; + } - GrayImage dst(src.size()); - gaussBlurGeneric(src.size(), h_sigma, v_sigma, src.data(), src.stride(), StaticCastValueConv(), dst.data(), - dst.stride(), _1 = bind(RoundAndClipValueConv(), _2)); + GrayImage dst(src.size()); + gaussBlurGeneric(src.size(), h_sigma, v_sigma, src.data(), src.stride(), StaticCastValueConv(), dst.data(), + dst.stride(), _1 = bind(RoundAndClipValueConv(), _2)); - return dst; + return dst; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/GaussBlur.h b/imageproc/GaussBlur.h index 52b565106..da0b036c4 100644 --- a/imageproc/GaussBlur.h +++ b/imageproc/GaussBlur.h @@ -22,11 +22,11 @@ #ifndef IMAGEPROC_GAUSSBLUR_H_ #define IMAGEPROC_GAUSSBLUR_H_ -#include "ValueConv.h" #include #include -#include #include +#include +#include "ValueConv.h" namespace imageproc { class GrayImage; @@ -83,7 +83,7 @@ GrayImage gaussBlur(const GrayImage& src, float h_sigma, float v_sigma); * gaussBlurGeneric(..., _1 = bind(RoundAndClipValueConv(), _2); * \endcode */ -template +template void gaussBlurGeneric(QSize size, float h_sigma, float v_sigma, @@ -97,25 +97,23 @@ void gaussBlurGeneric(QSize size, namespace gauss_blur_impl { void find_iir_constants(float* n_p, float* n_m, float* d_p, float* d_m, float* bd_p, float* bd_m, float std_dev); -template +template void save(int num_items, Src1It src1, Src2It src2, DstIt dst, int dst_stride, FloatWriter writer) { - while (num_items-- != 0) { - writer(*dst, *src1 + *src2); - ++src1; - ++src2; - dst += dst_stride; - } + while (num_items-- != 0) { + writer(*dst, *src1 + *src2); + ++src1; + ++src2; + dst += dst_stride; + } } class FloatToFloatWriter { -public: - void operator()(float& dst, float src) const { - dst = src; - } + public: + void operator()(float& dst, float src) const { dst = src; } }; } // namespace gauss_blur_impl -template +template void gaussBlurGeneric(const QSize size, const float h_sigma, const float v_sigma, @@ -125,92 +123,92 @@ void gaussBlurGeneric(const QSize size, const DstIt output, const int output_stride, const FloatWriter float_writer) { - if (size.isEmpty()) { - return; - } + if (size.isEmpty()) { + return; + } + + const int width = size.width(); + const int height = size.height(); + const int width_height_max = width > height ? width : height; + + boost::scoped_array val_p(new float[width_height_max]); + boost::scoped_array val_m(new float[width_height_max]); + boost::scoped_array intermediate_image(new float[width * height]); + const int intermediate_stride = width; + + // IIR parameters. + float n_p[5], n_m[5], d_p[5], d_m[5], bd_p[5], bd_m[5]; + // Vertical pass. + gauss_blur_impl::find_iir_constants(n_p, n_m, d_p, d_m, bd_p, bd_m, v_sigma); + for (int x = 0; x < width; ++x) { + memset(&val_p[0], 0, height * sizeof(val_p[0])); + memset(&val_m[0], 0, height * sizeof(val_m[0])); + + SrcIt sp_p(input + x); + SrcIt sp_m(sp_p + (height - 1) * input_stride); + float* vp = &val_p[0]; + float* vm = &val_m[0] + height - 1; + const float initial_p = float_reader(sp_p[0]); + const float initial_m = float_reader(sp_m[0]); - const int width = size.width(); - const int height = size.height(); - const int width_height_max = width > height ? width : height; + for (int y = 0; y < height; ++y) { + const int terms = y < 4 ? y : 4; + int i = 0; + int sp_off = 0; + for (; i <= terms; ++i, sp_off += input_stride) { + *vp += n_p[i] * float_reader(sp_p[-sp_off]) - d_p[i] * vp[-i]; + *vm += n_m[i] * float_reader(sp_m[sp_off]) - d_m[i] * vm[i]; + } + for (; i <= 4; ++i) { + *vp += (n_p[i] - bd_p[i]) * initial_p; + *vm += (n_m[i] - bd_m[i]) * initial_m; + } + sp_p += input_stride; + sp_m -= input_stride; + ++vp; + --vm; + } - boost::scoped_array val_p(new float[width_height_max]); - boost::scoped_array val_m(new float[width_height_max]); - boost::scoped_array intermediate_image(new float[width * height]); - const int intermediate_stride = width; + gauss_blur_impl::save(height, &val_p[0], &val_m[0], &intermediate_image[0] + x, intermediate_stride, + gauss_blur_impl::FloatToFloatWriter()); + } + // Horizontal pass. + gauss_blur_impl::find_iir_constants(n_p, n_m, d_p, d_m, bd_p, bd_m, h_sigma); + const float* intermediate_line = &intermediate_image[0]; + DstIt output_line(output); + for (int y = 0; y < height; ++y) { + memset(&val_p[0], 0, width * sizeof(val_p[0])); + memset(&val_m[0], 0, width * sizeof(val_m[0])); + + const float* sp_p = intermediate_line; + const float* sp_m = intermediate_line + width - 1; + float* vp = &val_p[0]; + float* vm = &val_m[0] + width - 1; + const float initial_p = sp_p[0]; + const float initial_m = sp_m[0]; - // IIR parameters. - float n_p[5], n_m[5], d_p[5], d_m[5], bd_p[5], bd_m[5]; - // Vertical pass. - gauss_blur_impl::find_iir_constants(n_p, n_m, d_p, d_m, bd_p, bd_m, v_sigma); for (int x = 0; x < width; ++x) { - memset(&val_p[0], 0, height * sizeof(val_p[0])); - memset(&val_m[0], 0, height * sizeof(val_m[0])); - - SrcIt sp_p(input + x); - SrcIt sp_m(sp_p + (height - 1) * input_stride); - float* vp = &val_p[0]; - float* vm = &val_m[0] + height - 1; - const float initial_p = float_reader(sp_p[0]); - const float initial_m = float_reader(sp_m[0]); - - for (int y = 0; y < height; ++y) { - const int terms = y < 4 ? y : 4; - int i = 0; - int sp_off = 0; - for (; i <= terms; ++i, sp_off += input_stride) { - *vp += n_p[i] * float_reader(sp_p[-sp_off]) - d_p[i] * vp[-i]; - *vm += n_m[i] * float_reader(sp_m[sp_off]) - d_m[i] * vm[i]; - } - for (; i <= 4; ++i) { - *vp += (n_p[i] - bd_p[i]) * initial_p; - *vm += (n_m[i] - bd_m[i]) * initial_m; - } - sp_p += input_stride; - sp_m -= input_stride; - ++vp; - --vm; - } - - gauss_blur_impl::save(height, &val_p[0], &val_m[0], &intermediate_image[0] + x, intermediate_stride, - gauss_blur_impl::FloatToFloatWriter()); - } - // Horizontal pass. - gauss_blur_impl::find_iir_constants(n_p, n_m, d_p, d_m, bd_p, bd_m, h_sigma); - const float* intermediate_line = &intermediate_image[0]; - DstIt output_line(output); - for (int y = 0; y < height; ++y) { - memset(&val_p[0], 0, width * sizeof(val_p[0])); - memset(&val_m[0], 0, width * sizeof(val_m[0])); - - const float* sp_p = intermediate_line; - const float* sp_m = intermediate_line + width - 1; - float* vp = &val_p[0]; - float* vm = &val_m[0] + width - 1; - const float initial_p = sp_p[0]; - const float initial_m = sp_m[0]; - - for (int x = 0; x < width; ++x) { - const int terms = x < 4 ? x : 4; - int i = 0; - for (; i <= terms; ++i) { - *vp += n_p[i] * sp_p[-i] - d_p[i] * vp[-i]; - *vm += n_m[i] * sp_m[i] - d_m[i] * vm[i]; - } - for (; i <= 4; ++i) { - *vp += (n_p[i] - bd_p[i]) * initial_p; - *vm += (n_m[i] - bd_m[i]) * initial_m; - } - ++sp_p; - --sp_m; - ++vp; - --vm; - } - - gauss_blur_impl::save(width, &val_p[0], &val_m[0], output_line, 1, float_writer); - - intermediate_line += intermediate_stride; - output_line += output_stride; + const int terms = x < 4 ? x : 4; + int i = 0; + for (; i <= terms; ++i) { + *vp += n_p[i] * sp_p[-i] - d_p[i] * vp[-i]; + *vm += n_m[i] * sp_m[i] - d_m[i] * vm[i]; + } + for (; i <= 4; ++i) { + *vp += (n_p[i] - bd_p[i]) * initial_p; + *vm += (n_m[i] - bd_m[i]) * initial_m; + } + ++sp_p; + --sp_m; + ++vp; + --vm; } + + gauss_blur_impl::save(width, &val_p[0], &val_m[0], output_line, 1, float_writer); + + intermediate_line += intermediate_stride; + output_line += output_stride; + } } // gaussBlurGeneric } // namespace imageproc #endif // ifndef IMAGEPROC_GAUSSBLUR_H_ diff --git a/imageproc/GrayImage.cpp b/imageproc/GrayImage.cpp index 56485f96f..8cc152609 100644 --- a/imageproc/GrayImage.cpp +++ b/imageproc/GrayImage.cpp @@ -21,44 +21,43 @@ namespace imageproc { GrayImage::GrayImage(QSize size) { - if (size.isEmpty()) { - return; - } + if (size.isEmpty()) { + return; + } - m_image = QImage(size, QImage::Format_Indexed8); - m_image.setColorTable(createGrayscalePalette()); - if (m_image.isNull()) { - throw std::bad_alloc(); - } + m_image = QImage(size, QImage::Format_Indexed8); + m_image.setColorTable(createGrayscalePalette()); + if (m_image.isNull()) { + throw std::bad_alloc(); + } } -GrayImage::GrayImage(const QImage& image) : m_image(toGrayscale(image)) { -} +GrayImage::GrayImage(const QImage& image) : m_image(toGrayscale(image)) {} GrayImage GrayImage::inverted() const { - GrayImage inverted(*this); - inverted.invert(); + GrayImage inverted(*this); + inverted.invert(); - return inverted; + return inverted; } void GrayImage::invert() { - m_image.invertPixels(QImage::InvertRgb); + m_image.invertPixels(QImage::InvertRgb); } int GrayImage::dotsPerMeterX() const { - return m_image.dotsPerMeterX(); + return m_image.dotsPerMeterX(); } int GrayImage::dotsPerMeterY() const { - return m_image.dotsPerMeterY(); + return m_image.dotsPerMeterY(); } void GrayImage::setDotsPerMeterX(int value) { - m_image.setDotsPerMeterX(value); + m_image.setDotsPerMeterX(value); } void GrayImage::setDotsPerMeterY(int value) { - m_image.setDotsPerMeterY(value); + m_image.setDotsPerMeterY(value); } } // namespace imageproc diff --git a/imageproc/GrayImage.h b/imageproc/GrayImage.h index d713c2ea1..07da2b7ba 100644 --- a/imageproc/GrayImage.h +++ b/imageproc/GrayImage.h @@ -20,8 +20,8 @@ #define IMAGEPROC_GRAYIMAGE_H_ #include -#include #include +#include #include namespace imageproc { @@ -29,106 +29,84 @@ namespace imageproc { * \brief A wrapper class around QImage that is always guaranteed to be 8-bit grayscale. */ class GrayImage { -public: - /** - * \brief Creates a 8-bit grayscale image with specified dimensions. - * - * The image contents won't be initialized. You can use fill() to initialize them. - * If size.isEmpty() is true, creates a null image. - * - * \throw std::bad_alloc Unlike the underlying QImage, GrayImage reacts to - * out-of-memory situations by throwing an exception rather than - * constructing a null image. - */ - explicit GrayImage(QSize size = QSize()); - - /** - * \brief Constructs a 8-bit grayscale image by converting an arbitrary QImage. - * - * The QImage may be in any format and may be null. - */ - explicit GrayImage(const QImage& image); - - /** - * \brief Returns a const reference to the underlying QImage. - * - * The underlying QImage is either a null image or a 8-bit indexed - * image with a grayscale palette. - */ - const QImage& toQImage() const { - return m_image; - } - - operator const QImage&() const { - return m_image; - } - - bool isNull() const { - return m_image.isNull(); - } - - void fill(uint8_t color) { - m_image.fill(color); - } - - uint8_t* data() { - return m_image.bits(); - } - - const uint8_t* data() const { - return m_image.bits(); - } - - /** - * \brief Number of bytes per line. - * - * This value may be larger than image width. - * An additional guaranee provided by the underlying QImage - * is that this value is a multiple of 4. - */ - int stride() const { - return m_image.bytesPerLine(); - } - - QSize size() const { - return m_image.size(); - } - - QRect rect() const { - return m_image.rect(); - } - - int width() const { - return m_image.width(); - } - - int height() const { - return m_image.height(); - } - - void invert(); - - GrayImage inverted() const; - - int dotsPerMeterX() const; - - int dotsPerMeterY() const; - - void setDotsPerMeterX(int value); - - void setDotsPerMeterY(int value); - -private: - QImage m_image; + public: + /** + * \brief Creates a 8-bit grayscale image with specified dimensions. + * + * The image contents won't be initialized. You can use fill() to initialize them. + * If size.isEmpty() is true, creates a null image. + * + * \throw std::bad_alloc Unlike the underlying QImage, GrayImage reacts to + * out-of-memory situations by throwing an exception rather than + * constructing a null image. + */ + explicit GrayImage(QSize size = QSize()); + + /** + * \brief Constructs a 8-bit grayscale image by converting an arbitrary QImage. + * + * The QImage may be in any format and may be null. + */ + explicit GrayImage(const QImage& image); + + /** + * \brief Returns a const reference to the underlying QImage. + * + * The underlying QImage is either a null image or a 8-bit indexed + * image with a grayscale palette. + */ + const QImage& toQImage() const { return m_image; } + + operator const QImage&() const { return m_image; } + + bool isNull() const { return m_image.isNull(); } + + void fill(uint8_t color) { m_image.fill(color); } + + uint8_t* data() { return m_image.bits(); } + + const uint8_t* data() const { return m_image.bits(); } + + /** + * \brief Number of bytes per line. + * + * This value may be larger than image width. + * An additional guaranee provided by the underlying QImage + * is that this value is a multiple of 4. + */ + int stride() const { return m_image.bytesPerLine(); } + + QSize size() const { return m_image.size(); } + + QRect rect() const { return m_image.rect(); } + + int width() const { return m_image.width(); } + + int height() const { return m_image.height(); } + + void invert(); + + GrayImage inverted() const; + + int dotsPerMeterX() const; + + int dotsPerMeterY() const; + + void setDotsPerMeterX(int value); + + void setDotsPerMeterY(int value); + + private: + QImage m_image; }; inline bool operator==(const GrayImage& lhs, const GrayImage& rhs) { - return lhs.toQImage() == rhs.toQImage(); + return lhs.toQImage() == rhs.toQImage(); } inline bool operator!=(const GrayImage& lhs, const GrayImage& rhs) { - return lhs.toQImage() != rhs.toQImage(); + return lhs.toQImage() != rhs.toQImage(); } } // namespace imageproc #endif // ifndef IMAGEPROC_GRAYIMAGE_H_ diff --git a/imageproc/GrayRasterOp.h b/imageproc/GrayRasterOp.h index 32e05767b..9b397cc15 100644 --- a/imageproc/GrayRasterOp.h +++ b/imageproc/GrayRasterOp.h @@ -19,14 +19,14 @@ #ifndef IMAGEPROC_GRAYRASTEROP_H_ #define IMAGEPROC_GRAYRASTEROP_H_ -#include "Grayscale.h" -#include "GrayImage.h" #include #include #include -#include -#include #include +#include +#include +#include "GrayImage.h" +#include "Grayscale.h" namespace imageproc { /** @@ -39,7 +39,7 @@ namespace imageproc { * combination of several GRop* class templates, such as * GRopSubtract\. */ -template +template void grayRasterOp(GrayImage& dst, const GrayImage& src); /** @@ -47,10 +47,8 @@ void grayRasterOp(GrayImage& dst, const GrayImage& src); * \see grayRasterOp() */ class GRopSrc { -public: - static uint8_t transform(uint8_t src, uint8_t /*dst*/) { - return src; - } + public: + static uint8_t transform(uint8_t src, uint8_t /*dst*/) { return src; } }; @@ -59,10 +57,8 @@ class GRopSrc { * \see grayRasterOp() */ class GRopDst { -public: - static uint8_t transform(uint8_t /*src*/, uint8_t dst) { - return dst; - } + public: + static uint8_t transform(uint8_t /*src*/, uint8_t dst) { return dst; } }; @@ -70,12 +66,10 @@ class GRopDst { * \brief Raster operation that inverts the gray level. * \see grayRasterOp() */ -template +template class GRopInvert { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - return uint8_t(0xff) - Arg::transform(src, dst); - } + public: + static uint8_t transform(uint8_t src, uint8_t dst) { return uint8_t(0xff) - Arg::transform(src, dst); } }; @@ -87,15 +81,15 @@ class GRopInvert { * * \see grayRasterOp() */ -template +template class GRopClippedSubtract { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const uint8_t lhs = Lhs::transform(src, dst); - const uint8_t rhs = Rhs::transform(src, dst); + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const uint8_t lhs = Lhs::transform(src, dst); + const uint8_t rhs = Rhs::transform(src, dst); - return lhs > rhs ? lhs - rhs : uint8_t(0); - } + return lhs > rhs ? lhs - rhs : uint8_t(0); + } }; @@ -107,15 +101,15 @@ class GRopClippedSubtract { * * \see grayRasterOp() */ -template +template class GRopUnclippedSubtract { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const uint8_t lhs = Lhs::transform(src, dst); - const uint8_t rhs = Rhs::transform(src, dst); + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const uint8_t lhs = Lhs::transform(src, dst); + const uint8_t rhs = Rhs::transform(src, dst); - return lhs - rhs; - } + return lhs - rhs; + } }; @@ -126,16 +120,16 @@ class GRopUnclippedSubtract { * * \see grayRasterOp() */ -template +template class GRopClippedAdd { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const unsigned lhs = Lhs::transform(src, dst); - const unsigned rhs = Rhs::transform(src, dst); - const unsigned sum = lhs + rhs; - - return sum < 256 ? static_cast(sum) : uint8_t(255); - } + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const unsigned lhs = Lhs::transform(src, dst); + const unsigned rhs = Rhs::transform(src, dst); + const unsigned sum = lhs + rhs; + + return sum < 256 ? static_cast(sum) : uint8_t(255); + } }; @@ -147,15 +141,15 @@ class GRopClippedAdd { * * \see grayRasterOp() */ -template +template class GRopUnclippedAdd { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const uint8_t lhs = Lhs::transform(src, dst); - const uint8_t rhs = Rhs::transform(src, dst); + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const uint8_t lhs = Lhs::transform(src, dst); + const uint8_t rhs = Rhs::transform(src, dst); - return lhs + rhs; - } + return lhs + rhs; + } }; @@ -163,15 +157,15 @@ class GRopUnclippedAdd { * \brief Raster operation that takes the darkest of its arguments. * \see grayRasterOp() */ -template +template class GRopDarkest { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const uint8_t lhs = Lhs::transform(src, dst); - const uint8_t rhs = Rhs::transform(src, dst); + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const uint8_t lhs = Lhs::transform(src, dst); + const uint8_t rhs = Rhs::transform(src, dst); - return lhs < rhs ? lhs : rhs; - } + return lhs < rhs ? lhs : rhs; + } }; @@ -179,43 +173,43 @@ class GRopDarkest { * \brief Raster operation that takes the lightest of its arguments. * \see grayRasterOp() */ -template +template class GRopLightest { -public: - static uint8_t transform(uint8_t src, uint8_t dst) { - const uint8_t lhs = Lhs::transform(src, dst); - const uint8_t rhs = Rhs::transform(src, dst); + public: + static uint8_t transform(uint8_t src, uint8_t dst) { + const uint8_t lhs = Lhs::transform(src, dst); + const uint8_t rhs = Rhs::transform(src, dst); - return lhs > rhs ? lhs : rhs; - } + return lhs > rhs ? lhs : rhs; + } }; -template +template void grayRasterOp(GrayImage& dst, const GrayImage& src) { - if (dst.isNull() || src.isNull()) { - throw std::invalid_argument("grayRasterOp: can't operate on null images"); - } + if (dst.isNull() || src.isNull()) { + throw std::invalid_argument("grayRasterOp: can't operate on null images"); + } - if (src.size() != dst.size()) { - throw std::invalid_argument("grayRasterOp: images sizes are not the same"); - } + if (src.size() != dst.size()) { + throw std::invalid_argument("grayRasterOp: images sizes are not the same"); + } - const uint8_t* src_line = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); + const uint8_t* src_line = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); - const int width = src.width(); - const int height = src.height(); + const int width = src.width(); + const int height = src.height(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - dst_line[x] = GRop::transform(src_line[x], dst_line[x]); - } - src_line += src_stride; - dst_line += dst_stride; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + dst_line[x] = GRop::transform(src_line[x], dst_line[x]); } + src_line += src_stride; + dst_line += dst_stride; + } } } // namespace imageproc #endif // ifndef IMAGEPROC_GRAYRASTEROP_H_ diff --git a/imageproc/Grayscale.cpp b/imageproc/Grayscale.cpp index 7187484b7..e68333ea6 100644 --- a/imageproc/Grayscale.cpp +++ b/imageproc/Grayscale.cpp @@ -22,451 +22,451 @@ namespace imageproc { static QImage monoMsbToGrayscale(const QImage& src) { - const int width = src.width(); - const int height = src.height(); + const int width = src.width(); + const int height = src.height(); - QImage dst(width, height, QImage::Format_Indexed8); - dst.setColorTable(createGrayscalePalette()); - if ((width > 0) && (height > 0) && dst.isNull()) { - throw std::bad_alloc(); - } + QImage dst(width, height, QImage::Format_Indexed8); + dst.setColorTable(createGrayscalePalette()); + if ((width > 0) && (height > 0) && dst.isNull()) { + throw std::bad_alloc(); + } - const uint8_t* src_line = src.bits(); - uint8_t* dst_line = dst.bits(); - const int src_bpl = src.bytesPerLine(); - const int dst_bpl = dst.bytesPerLine(); - - uint8_t bin2gray[2] = {0, 0xff}; - if (src.colorCount() >= 2) { - if (qGray(src.color(0)) > qGray(src.color(1))) { - // if color 0 is lighter than color 1 - bin2gray[0] = 0xff; - bin2gray[1] = 0; - } - } + const uint8_t* src_line = src.bits(); + uint8_t* dst_line = dst.bits(); + const int src_bpl = src.bytesPerLine(); + const int dst_bpl = dst.bytesPerLine(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width;) { - const uint8_t b = src_line[x / 8]; - for (int i = 7; i >= 0 && x < width; --i, ++x) { - dst_line[x] = bin2gray[(b >> i) & 1]; - } - } + uint8_t bin2gray[2] = {0, 0xff}; + if (src.colorCount() >= 2) { + if (qGray(src.color(0)) > qGray(src.color(1))) { + // if color 0 is lighter than color 1 + bin2gray[0] = 0xff; + bin2gray[1] = 0; + } + } - src_line += src_bpl; - dst_line += dst_bpl; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width;) { + const uint8_t b = src_line[x / 8]; + for (int i = 7; i >= 0 && x < width; --i, ++x) { + dst_line[x] = bin2gray[(b >> i) & 1]; + } } - dst.setDotsPerMeterX(src.dotsPerMeterX()); - dst.setDotsPerMeterY(src.dotsPerMeterY()); + src_line += src_bpl; + dst_line += dst_bpl; + } + + dst.setDotsPerMeterX(src.dotsPerMeterX()); + dst.setDotsPerMeterY(src.dotsPerMeterY()); - return dst; + return dst; } // monoMsbToGrayscale static QImage monoLsbToGrayscale(const QImage& src) { - const int width = src.width(); - const int height = src.height(); + const int width = src.width(); + const int height = src.height(); - QImage dst(width, height, QImage::Format_Indexed8); - dst.setColorTable(createGrayscalePalette()); - if ((width > 0) && (height > 0) && dst.isNull()) { - throw std::bad_alloc(); - } + QImage dst(width, height, QImage::Format_Indexed8); + dst.setColorTable(createGrayscalePalette()); + if ((width > 0) && (height > 0) && dst.isNull()) { + throw std::bad_alloc(); + } - const uint8_t* src_line = src.bits(); - uint8_t* dst_line = dst.bits(); - const int src_bpl = src.bytesPerLine(); - const int dst_bpl = dst.bytesPerLine(); - - uint8_t bin2gray[2] = {0, 0xff}; - if (src.colorCount() >= 2) { - if (qGray(src.color(0)) > qGray(src.color(1))) { - // if color 0 is lighter than color 1 - bin2gray[0] = 0xff; - bin2gray[1] = 0; - } - } + const uint8_t* src_line = src.bits(); + uint8_t* dst_line = dst.bits(); + const int src_bpl = src.bytesPerLine(); + const int dst_bpl = dst.bytesPerLine(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width;) { - const uint8_t b = src_line[x / 8]; - for (int i = 0; i < 8 && x < width; ++i, ++x) { - dst_line[x] = bin2gray[(b >> i) & 1]; - } - } + uint8_t bin2gray[2] = {0, 0xff}; + if (src.colorCount() >= 2) { + if (qGray(src.color(0)) > qGray(src.color(1))) { + // if color 0 is lighter than color 1 + bin2gray[0] = 0xff; + bin2gray[1] = 0; + } + } - src_line += src_bpl; - dst_line += dst_bpl; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width;) { + const uint8_t b = src_line[x / 8]; + for (int i = 0; i < 8 && x < width; ++i, ++x) { + dst_line[x] = bin2gray[(b >> i) & 1]; + } } - dst.setDotsPerMeterX(src.dotsPerMeterX()); - dst.setDotsPerMeterY(src.dotsPerMeterY()); + src_line += src_bpl; + dst_line += dst_bpl; + } + + dst.setDotsPerMeterX(src.dotsPerMeterX()); + dst.setDotsPerMeterY(src.dotsPerMeterY()); - return dst; + return dst; } // monoLsbToGrayscale static QImage anyToGrayscale(const QImage& src) { - const int width = src.width(); - const int height = src.height(); + const int width = src.width(); + const int height = src.height(); - QImage dst(width, height, QImage::Format_Indexed8); - dst.setColorTable(createGrayscalePalette()); - if ((width > 0) && (height > 0) && dst.isNull()) { - throw std::bad_alloc(); - } + QImage dst(width, height, QImage::Format_Indexed8); + dst.setColorTable(createGrayscalePalette()); + if ((width > 0) && (height > 0) && dst.isNull()) { + throw std::bad_alloc(); + } - uint8_t* dst_line = dst.bits(); - const int dst_bpl = dst.bytesPerLine(); + uint8_t* dst_line = dst.bits(); + const int dst_bpl = dst.bytesPerLine(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - dst_line[x] = static_cast(qGray(src.pixel(x, y))); - } - dst_line += dst_bpl; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + dst_line[x] = static_cast(qGray(src.pixel(x, y))); } + dst_line += dst_bpl; + } - dst.setDotsPerMeterX(src.dotsPerMeterX()); - dst.setDotsPerMeterY(src.dotsPerMeterY()); + dst.setDotsPerMeterX(src.dotsPerMeterX()); + dst.setDotsPerMeterY(src.dotsPerMeterY()); - return dst; + return dst; } QVector createGrayscalePalette() { - QVector palette(256); - for (int i = 0; i < 256; ++i) { - palette[i] = qRgb(i, i, i); - } + QVector palette(256); + for (int i = 0; i < 256; ++i) { + palette[i] = qRgb(i, i, i); + } - return palette; + return palette; } QImage toGrayscale(const QImage& src) { - if (src.isNull()) { - return src; - } - - switch (src.format()) { - case QImage::Format_Mono: - return monoMsbToGrayscale(src); - case QImage::Format_MonoLSB: - return monoLsbToGrayscale(src); - case QImage::Format_Indexed8: - if (src.isGrayscale()) { - if (src.colorCount() == 256) { - return src; - } else { - QImage dst(src); - dst.setColorTable(createGrayscalePalette()); - if (!src.isNull() && dst.isNull()) { - throw std::bad_alloc(); - } - - return dst; - } - } - // fall though - default: - return anyToGrayscale(src); - } + if (src.isNull()) { + return src; + } + + switch (src.format()) { + case QImage::Format_Mono: + return monoMsbToGrayscale(src); + case QImage::Format_MonoLSB: + return monoLsbToGrayscale(src); + case QImage::Format_Indexed8: + if (src.isGrayscale()) { + if (src.colorCount() == 256) { + return src; + } else { + QImage dst(src); + dst.setColorTable(createGrayscalePalette()); + if (!src.isNull() && dst.isNull()) { + throw std::bad_alloc(); + } + + return dst; + } + } + // fall though + default: + return anyToGrayscale(src); + } } GrayImage stretchGrayRange(const GrayImage& src, const double black_clip_fraction, const double white_clip_fraction) { - if (src.isNull()) { - return src; - } + if (src.isNull()) { + return src; + } - GrayImage dst(src); + GrayImage dst(src); - const int width = dst.width(); - const int height = dst.height(); + const int width = dst.width(); + const int height = dst.height(); - const int num_pixels = width * height; - int black_clip_pixels = qRound(black_clip_fraction * num_pixels); - int white_clip_pixels = qRound(white_clip_fraction * num_pixels); + const int num_pixels = width * height; + int black_clip_pixels = qRound(black_clip_fraction * num_pixels); + int white_clip_pixels = qRound(white_clip_fraction * num_pixels); - const GrayscaleHistogram hist(dst); + const GrayscaleHistogram hist(dst); - int min = 0; - if (black_clip_fraction >= 1.0) { - min = qRound(black_clip_fraction); - } else { - for (; min <= 255; ++min) { - if (black_clip_pixels < hist[min]) { - break; - } - black_clip_pixels -= hist[min]; - } + int min = 0; + if (black_clip_fraction >= 1.0) { + min = qRound(black_clip_fraction); + } else { + for (; min <= 255; ++min) { + if (black_clip_pixels < hist[min]) { + break; + } + black_clip_pixels -= hist[min]; } + } - int max = 255; - if (white_clip_fraction >= 1.0) { - max = qRound(white_clip_fraction); - } else { - for (; max >= 0; --max) { - if (white_clip_pixels < hist[max]) { - break; - } - white_clip_pixels -= hist[max]; - } + int max = 255; + if (white_clip_fraction >= 1.0) { + max = qRound(white_clip_fraction); + } else { + for (; max >= 0; --max) { + if (white_clip_pixels < hist[max]) { + break; + } + white_clip_pixels -= hist[max]; } + } - uint8_t gray_mapping[256]; + uint8_t gray_mapping[256]; - if (min >= max) { - const int avg = (min + max) / 2; - for (int i = 0; i <= avg; ++i) { - gray_mapping[i] = 0; - } - for (int i = avg + 1; i < 256; ++i) { - gray_mapping[i] = 255; - } - } else { - for (int i = 0; i < 256; ++i) { - const int src_level = qBound(min, i, max); - const int num = 255 * (src_level - min); - const int denom = max - min; - const int dst_level = (num + denom / 2) / denom; - gray_mapping[i] = static_cast(dst_level); - } + if (min >= max) { + const int avg = (min + max) / 2; + for (int i = 0; i <= avg; ++i) { + gray_mapping[i] = 0; + } + for (int i = avg + 1; i < 256; ++i) { + gray_mapping[i] = 255; + } + } else { + for (int i = 0; i < 256; ++i) { + const int src_level = qBound(min, i, max); + const int num = 255 * (src_level - min); + const int denom = max - min; + const int dst_level = (num + denom / 2) / denom; + gray_mapping[i] = static_cast(dst_level); } + } - uint8_t* line = dst.data(); - const int stride = dst.stride(); + uint8_t* line = dst.data(); + const int stride = dst.stride(); - for (int y = 0; y < height; ++y, line += stride) { - for (int x = 0; x < width; ++x) { - line[x] = gray_mapping[line[x]]; - } + for (int y = 0; y < height; ++y, line += stride) { + for (int x = 0; x < width; ++x) { + line[x] = gray_mapping[line[x]]; } + } - return dst; + return dst; } // stretchGrayRange GrayImage createFramedImage(const QSize& size, const unsigned char inner_color, const unsigned char frame_color) { - GrayImage image(size); - image.fill(inner_color); + GrayImage image(size); + image.fill(inner_color); - const int width = size.width(); - const int height = size.height(); + const int width = size.width(); + const int height = size.height(); - unsigned char* line = image.data(); - const int stride = image.stride(); + unsigned char* line = image.data(); + const int stride = image.stride(); - memset(line, frame_color, width); + memset(line, frame_color, width); - for (int y = 0; y < height; ++y, line += stride) { - line[0] = frame_color; - line[width - 1] = frame_color; - } + for (int y = 0; y < height; ++y, line += stride) { + line[0] = frame_color; + line[width - 1] = frame_color; + } - memset(line - stride, frame_color, width); + memset(line - stride, frame_color, width); - return image; + return image; } unsigned char darkestGrayLevel(const QImage& image) { - const QImage gray(toGrayscale(image)); + const QImage gray(toGrayscale(image)); - const int width = image.width(); - const int height = image.height(); + const int width = image.width(); + const int height = image.height(); - const unsigned char* line = image.bits(); - const int bpl = image.bytesPerLine(); + const unsigned char* line = image.bits(); + const int bpl = image.bytesPerLine(); - unsigned char darkest = 0xff; + unsigned char darkest = 0xff; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - darkest = std::min(darkest, line[x]); - } - line += bpl; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + darkest = std::min(darkest, line[x]); } + line += bpl; + } - return darkest; + return darkest; } GrayscaleHistogram::GrayscaleHistogram(const QImage& img) { - memset(m_pixels, 0, sizeof(m_pixels)); - - if (img.isNull()) { - return; - } - - switch (img.format()) { - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - fromMonoImage(img); - break; - case QImage::Format_Indexed8: - if (img.isGrayscale()) { - fromGrayscaleImage(img); - break; - } - // fall though - default: - fromAnyImage(img); - } + memset(m_pixels, 0, sizeof(m_pixels)); + + if (img.isNull()) { + return; + } + + switch (img.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + fromMonoImage(img); + break; + case QImage::Format_Indexed8: + if (img.isGrayscale()) { + fromGrayscaleImage(img); + break; + } + // fall though + default: + fromAnyImage(img); + } } GrayscaleHistogram::GrayscaleHistogram(const QImage& img, const BinaryImage& mask) { - memset(m_pixels, 0, sizeof(m_pixels)); - - if (img.isNull()) { - return; - } - - if (img.size() != mask.size()) { - throw std::invalid_argument("GrayscaleHistogram: img and mask have different sizes"); - } - - switch (img.format()) { - case QImage::Format_Mono: - fromMonoMSBImage(img, mask); - break; - case QImage::Format_MonoLSB: - fromMonoMSBImage(img.convertToFormat(QImage::Format_Mono), mask); - break; - case QImage::Format_Indexed8: - if (img.isGrayscale()) { - fromGrayscaleImage(img, mask); - break; - } - // fall though - default: - fromAnyImage(img, mask); - } + memset(m_pixels, 0, sizeof(m_pixels)); + + if (img.isNull()) { + return; + } + + if (img.size() != mask.size()) { + throw std::invalid_argument("GrayscaleHistogram: img and mask have different sizes"); + } + + switch (img.format()) { + case QImage::Format_Mono: + fromMonoMSBImage(img, mask); + break; + case QImage::Format_MonoLSB: + fromMonoMSBImage(img.convertToFormat(QImage::Format_Mono), mask); + break; + case QImage::Format_Indexed8: + if (img.isGrayscale()) { + fromGrayscaleImage(img, mask); + break; + } + // fall though + default: + fromAnyImage(img, mask); + } } void GrayscaleHistogram::fromMonoImage(const QImage& img) { - const int w = img.width(); - const int h = img.height(); - const int bpl = img.bytesPerLine(); - const int last_byte_idx = (w - 1) >> 3; - const int last_byte_unused_bits = (((last_byte_idx + 1) << 3) - w); - uint8_t last_byte_mask = ~uint8_t(0); - if (img.format() == QImage::Format_MonoLSB) { - last_byte_mask >>= last_byte_unused_bits; - } else { - last_byte_mask <<= last_byte_unused_bits; - } - const uint8_t* line = img.bits(); - - int num_bits_1 = 0; - for (int y = 0; y < h; ++y, line += bpl) { - int i = 0; - for (; i < last_byte_idx; ++i) { - num_bits_1 += countNonZeroBits(line[i]); - } - - // The last (possibly incomplete) byte. - num_bits_1 += countNonZeroBits(line[i] & last_byte_mask); - } - const int num_bits_0 = w * h - num_bits_1; - - QRgb color0 = 0xffffffff; - QRgb color1 = 0xff000000; - if (img.colorCount() >= 2) { - color0 = img.color(0); - color1 = img.color(1); - } - - m_pixels[qGray(color0)] = num_bits_0; - m_pixels[qGray(color1)] = num_bits_1; + const int w = img.width(); + const int h = img.height(); + const int bpl = img.bytesPerLine(); + const int last_byte_idx = (w - 1) >> 3; + const int last_byte_unused_bits = (((last_byte_idx + 1) << 3) - w); + uint8_t last_byte_mask = ~uint8_t(0); + if (img.format() == QImage::Format_MonoLSB) { + last_byte_mask >>= last_byte_unused_bits; + } else { + last_byte_mask <<= last_byte_unused_bits; + } + const uint8_t* line = img.bits(); + + int num_bits_1 = 0; + for (int y = 0; y < h; ++y, line += bpl) { + int i = 0; + for (; i < last_byte_idx; ++i) { + num_bits_1 += countNonZeroBits(line[i]); + } + + // The last (possibly incomplete) byte. + num_bits_1 += countNonZeroBits(line[i] & last_byte_mask); + } + const int num_bits_0 = w * h - num_bits_1; + + QRgb color0 = 0xffffffff; + QRgb color1 = 0xff000000; + if (img.colorCount() >= 2) { + color0 = img.color(0); + color1 = img.color(1); + } + + m_pixels[qGray(color0)] = num_bits_0; + m_pixels[qGray(color1)] = num_bits_1; } // GrayscaleHistogram::fromMonoImage void GrayscaleHistogram::fromMonoMSBImage(const QImage& img, const BinaryImage& mask) { - const int w = img.width(); - const int h = img.height(); - const int wpl = img.bytesPerLine() >> 2; - const int last_word_idx = (w - 1) >> 5; - const int last_word_unused_bits = (((last_word_idx + 1) << 5) - w); - uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; - const auto* line = (const uint32_t*) img.bits(); - const uint32_t* mask_line = mask.data(); - const int mask_wpl = mask.wordsPerLine(); - - int num_bits_0 = 0; - int num_bits_1 = 0; - for (int y = 0; y < h; ++y, line += wpl, mask_line += mask_wpl) { - int i = 0; - for (; i < last_word_idx; ++i) { - const uint32_t mask = mask_line[i]; - num_bits_1 += countNonZeroBits(line[i] & mask); - num_bits_0 += countNonZeroBits(~line[i] & mask); - } - - // The last (possibly incomplete) word. - const uint32_t mask = mask_line[i] & last_word_mask; - num_bits_1 += countNonZeroBits(line[i] & mask); - num_bits_0 += countNonZeroBits(~line[i] & mask); - } - - QRgb color0 = 0xffffffff; - QRgb color1 = 0xff000000; - if (img.colorCount() >= 2) { - color0 = img.color(0); - color1 = img.color(1); - } - - m_pixels[qGray(color0)] = num_bits_0; - m_pixels[qGray(color1)] = num_bits_1; + const int w = img.width(); + const int h = img.height(); + const int wpl = img.bytesPerLine() >> 2; + const int last_word_idx = (w - 1) >> 5; + const int last_word_unused_bits = (((last_word_idx + 1) << 5) - w); + uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; + const auto* line = (const uint32_t*) img.bits(); + const uint32_t* mask_line = mask.data(); + const int mask_wpl = mask.wordsPerLine(); + + int num_bits_0 = 0; + int num_bits_1 = 0; + for (int y = 0; y < h; ++y, line += wpl, mask_line += mask_wpl) { + int i = 0; + for (; i < last_word_idx; ++i) { + const uint32_t mask = mask_line[i]; + num_bits_1 += countNonZeroBits(line[i] & mask); + num_bits_0 += countNonZeroBits(~line[i] & mask); + } + + // The last (possibly incomplete) word. + const uint32_t mask = mask_line[i] & last_word_mask; + num_bits_1 += countNonZeroBits(line[i] & mask); + num_bits_0 += countNonZeroBits(~line[i] & mask); + } + + QRgb color0 = 0xffffffff; + QRgb color1 = 0xff000000; + if (img.colorCount() >= 2) { + color0 = img.color(0); + color1 = img.color(1); + } + + m_pixels[qGray(color0)] = num_bits_0; + m_pixels[qGray(color1)] = num_bits_1; } // GrayscaleHistogram::fromMonoMSBImage void GrayscaleHistogram::fromGrayscaleImage(const QImage& img) { - const int w = img.width(); - const int h = img.height(); - const int bpl = img.bytesPerLine(); - const uint8_t* line = img.bits(); - - for (int y = 0; y < h; ++y, line += bpl) { - for (int x = 0; x < w; ++x) { - ++m_pixels[line[x]]; - } + const int w = img.width(); + const int h = img.height(); + const int bpl = img.bytesPerLine(); + const uint8_t* line = img.bits(); + + for (int y = 0; y < h; ++y, line += bpl) { + for (int x = 0; x < w; ++x) { + ++m_pixels[line[x]]; } + } } void GrayscaleHistogram::fromGrayscaleImage(const QImage& img, const BinaryImage& mask) { - const int w = img.width(); - const int h = img.height(); - const int bpl = img.bytesPerLine(); - const uint8_t* line = img.bits(); - const uint32_t* mask_line = mask.data(); - const int mask_wpl = mask.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < h; ++y, line += bpl, mask_line += mask_wpl) { - for (int x = 0; x < w; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - ++m_pixels[line[x]]; - } - } - } + const int w = img.width(); + const int h = img.height(); + const int bpl = img.bytesPerLine(); + const uint8_t* line = img.bits(); + const uint32_t* mask_line = mask.data(); + const int mask_wpl = mask.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < h; ++y, line += bpl, mask_line += mask_wpl) { + for (int x = 0; x < w; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + ++m_pixels[line[x]]; + } + } + } } void GrayscaleHistogram::fromAnyImage(const QImage& img) { - const int w = img.width(); - const int h = img.height(); + const int w = img.width(); + const int h = img.height(); - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - ++m_pixels[qGray(img.pixel(x, y))]; - } + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + ++m_pixels[qGray(img.pixel(x, y))]; } + } } void GrayscaleHistogram::fromAnyImage(const QImage& img, const BinaryImage& mask) { - const int w = img.width(); - const int h = img.height(); - const uint32_t* mask_line = mask.data(); - const int mask_wpl = mask.wordsPerLine(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < h; ++y, mask_line += mask_wpl) { - for (int x = 0; x < w; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - ++m_pixels[qGray(img.pixel(x, y))]; - } - } - } + const int w = img.width(); + const int h = img.height(); + const uint32_t* mask_line = mask.data(); + const int mask_wpl = mask.wordsPerLine(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < h; ++y, mask_line += mask_wpl) { + for (int x = 0; x < w; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + ++m_pixels[qGray(img.pixel(x, y))]; + } + } + } } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/Grayscale.h b/imageproc/Grayscale.h index 8b8165b54..f0bea2f8b 100644 --- a/imageproc/Grayscale.h +++ b/imageproc/Grayscale.h @@ -19,9 +19,9 @@ #ifndef IMAGEPROC_GRAYSCALE_H_ #define IMAGEPROC_GRAYSCALE_H_ -#include "GrayImage.h" -#include #include +#include +#include "GrayImage.h" class QImage; class QSize; @@ -30,33 +30,29 @@ namespace imageproc { class BinaryImage; class GrayscaleHistogram { -public: - explicit GrayscaleHistogram(const QImage& img); + public: + explicit GrayscaleHistogram(const QImage& img); - GrayscaleHistogram(const QImage& img, const BinaryImage& mask); + GrayscaleHistogram(const QImage& img, const BinaryImage& mask); - inline int& operator[](int idx) { - return m_pixels[idx]; - } + inline int& operator[](int idx) { return m_pixels[idx]; } - inline int operator[](int idx) const { - return m_pixels[idx]; - } + inline int operator[](int idx) const { return m_pixels[idx]; } -private: - void fromMonoImage(const QImage& img); + private: + void fromMonoImage(const QImage& img); - void fromMonoMSBImage(const QImage& img, const BinaryImage& mask); + void fromMonoMSBImage(const QImage& img, const BinaryImage& mask); - void fromGrayscaleImage(const QImage& img); + void fromGrayscaleImage(const QImage& img); - void fromGrayscaleImage(const QImage& img, const BinaryImage& mask); + void fromGrayscaleImage(const QImage& img, const BinaryImage& mask); - void fromAnyImage(const QImage& img); + void fromAnyImage(const QImage& img); - void fromAnyImage(const QImage& img, const BinaryImage& mask); + void fromAnyImage(const QImage& img, const BinaryImage& mask); - int m_pixels[256]; + int m_pixels[256]; }; diff --git a/imageproc/HoughLineDetector.cpp b/imageproc/HoughLineDetector.cpp index 465a67450..a56f829df 100644 --- a/imageproc/HoughLineDetector.cpp +++ b/imageproc/HoughLineDetector.cpp @@ -17,24 +17,22 @@ */ #include "HoughLineDetector.h" +#include +#include +#include +#include #include "BinaryImage.h" #include "ConnCompEraser.h" #include "Constants.h" +#include "Grayscale.h" #include "Morphology.h" #include "RasterOp.h" #include "SeedFill.h" -#include "Grayscale.h" -#include -#include -#include -#include namespace imageproc { class HoughLineDetector::GreaterQualityFirst { -public: - bool operator()(const HoughLine& lhs, const HoughLine& rhs) const { - return lhs.quality() > rhs.quality(); - } + public: + bool operator()(const HoughLine& lhs, const HoughLine& rhs) const { return lhs.quality() > rhs.quality(); } }; @@ -43,175 +41,175 @@ HoughLineDetector::HoughLineDetector(const QSize& input_dimensions, const double start_angle, const double angle_delta, const int num_angles) - : m_distanceResolution(distance_resolution), m_recipDistanceResolution(1.0 / distance_resolution) { - const int max_x = input_dimensions.width() - 1; - const int max_y = input_dimensions.height() - 1; - - QPoint checkpoints[3]; - checkpoints[0] = QPoint(max_x, max_y); - checkpoints[1] = QPoint(max_x, 0); - checkpoints[2] = QPoint(0, max_y); - - double max_distance = 0.0; - double min_distance = 0.0; - - m_angleUnitVectors.reserve(num_angles); - for (int i = 0; i < num_angles; ++i) { - double angle = start_angle + angle_delta * i; - angle *= constants::DEG2RAD; - - const QPointF uv(std::cos(angle), std::sin(angle)); - for (const QPoint& p : checkpoints) { - const double distance = uv.x() * p.x() + uv.y() * p.y(); - max_distance = std::max(max_distance, distance); - min_distance = std::min(min_distance, distance); - } - - m_angleUnitVectors.push_back(uv); + : m_distanceResolution(distance_resolution), m_recipDistanceResolution(1.0 / distance_resolution) { + const int max_x = input_dimensions.width() - 1; + const int max_y = input_dimensions.height() - 1; + + QPoint checkpoints[3]; + checkpoints[0] = QPoint(max_x, max_y); + checkpoints[1] = QPoint(max_x, 0); + checkpoints[2] = QPoint(0, max_y); + + double max_distance = 0.0; + double min_distance = 0.0; + + m_angleUnitVectors.reserve(num_angles); + for (int i = 0; i < num_angles; ++i) { + double angle = start_angle + angle_delta * i; + angle *= constants::DEG2RAD; + + const QPointF uv(std::cos(angle), std::sin(angle)); + for (const QPoint& p : checkpoints) { + const double distance = uv.x() * p.x() + uv.y() * p.y(); + max_distance = std::max(max_distance, distance); + min_distance = std::min(min_distance, distance); } - // We bias distances to make them non-negative. - m_distanceBias = -min_distance; - const double max_biased_distance = max_distance + m_distanceBias; - const auto max_bin = int(max_biased_distance * m_recipDistanceResolution + 0.5); + m_angleUnitVectors.push_back(uv); + } + // We bias distances to make them non-negative. + m_distanceBias = -min_distance; + + const double max_biased_distance = max_distance + m_distanceBias; + const auto max_bin = int(max_biased_distance * m_recipDistanceResolution + 0.5); - m_histWidth = max_bin + 1; - m_histHeight = num_angles; - m_histogram.resize(m_histWidth * m_histHeight, 0); + m_histWidth = max_bin + 1; + m_histHeight = num_angles; + m_histogram.resize(m_histWidth * m_histHeight, 0); } void HoughLineDetector::process(int x, int y, unsigned weight) { - unsigned* hist_line = &m_histogram[0]; + unsigned* hist_line = &m_histogram[0]; - for (const QPointF& uv : m_angleUnitVectors) { - const double distance = uv.x() * x + uv.y() * y; - const double biased_distance = distance + m_distanceBias; + for (const QPointF& uv : m_angleUnitVectors) { + const double distance = uv.x() * x + uv.y() * y; + const double biased_distance = distance + m_distanceBias; - const auto bin = (int) (biased_distance * m_recipDistanceResolution + 0.5); - assert(bin >= 0 && bin < m_histWidth); - hist_line[bin] += weight; + const auto bin = (int) (biased_distance * m_recipDistanceResolution + 0.5); + assert(bin >= 0 && bin < m_histWidth); + hist_line[bin] += weight; - hist_line += m_histWidth; - } + hist_line += m_histWidth; + } } QImage HoughLineDetector::visualizeHoughSpace(const unsigned lower_bound) const { - QImage intensity(m_histWidth, m_histHeight, QImage::Format_Indexed8); - intensity.setColorTable(createGrayscalePalette()); - if ((m_histWidth > 0) && (m_histHeight > 0) && intensity.isNull()) { - throw std::bad_alloc(); - } - - unsigned max_value = 0; - const unsigned* hist_line = &m_histogram[0]; - for (int y = 0; y < m_histHeight; ++y) { - for (int x = 0; x < m_histWidth; ++x) { - max_value = std::max(max_value, hist_line[x]); - } - hist_line += m_histWidth; + QImage intensity(m_histWidth, m_histHeight, QImage::Format_Indexed8); + intensity.setColorTable(createGrayscalePalette()); + if ((m_histWidth > 0) && (m_histHeight > 0) && intensity.isNull()) { + throw std::bad_alloc(); + } + + unsigned max_value = 0; + const unsigned* hist_line = &m_histogram[0]; + for (int y = 0; y < m_histHeight; ++y) { + for (int x = 0; x < m_histWidth; ++x) { + max_value = std::max(max_value, hist_line[x]); } - - if (max_value == 0) { - intensity.fill(0); - - return intensity; + hist_line += m_histWidth; + } + + if (max_value == 0) { + intensity.fill(0); + + return intensity; + } + + unsigned char* intensity_line = intensity.bits(); + const int intensity_bpl = intensity.bytesPerLine(); + hist_line = &m_histogram[0]; + for (int y = 0; y < m_histHeight; ++y) { + for (int x = 0; x < m_histWidth; ++x) { + const auto intensity = (unsigned) std::floor(hist_line[x] * 255.0 / max_value + 0.5); + intensity_line[x] = (unsigned char) intensity; } - - unsigned char* intensity_line = intensity.bits(); - const int intensity_bpl = intensity.bytesPerLine(); - hist_line = &m_histogram[0]; - for (int y = 0; y < m_histHeight; ++y) { - for (int x = 0; x < m_histWidth; ++x) { - const auto intensity = (unsigned) std::floor(hist_line[x] * 255.0 / max_value + 0.5); - intensity_line[x] = (unsigned char) intensity; - } - intensity_line += intensity_bpl; - hist_line += m_histWidth; - } - - const BinaryImage peaks(findHistogramPeaks(m_histogram, m_histWidth, m_histHeight, lower_bound)); - - QImage peaks_visual(intensity.size(), QImage::Format_ARGB32_Premultiplied); - peaks_visual.fill(qRgb(0xff, 0x00, 0x00)); - QImage alpha_channel(peaks.toQImage()); - if (qGray(alpha_channel.color(0)) < qGray(alpha_channel.color(1))) { - alpha_channel.setColor(0, qRgb(0x80, 0x80, 0x80)); - alpha_channel.setColor(1, 0); - } else { - alpha_channel.setColor(0, 0); - alpha_channel.setColor(1, qRgb(0x80, 0x80, 0x80)); - } - peaks_visual.setAlphaChannel(alpha_channel); - - QImage visual(intensity.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - - { - QPainter painter(&visual); - painter.drawImage(QPoint(0, 0), peaks_visual); - } - - return visual; + intensity_line += intensity_bpl; + hist_line += m_histWidth; + } + + const BinaryImage peaks(findHistogramPeaks(m_histogram, m_histWidth, m_histHeight, lower_bound)); + + QImage peaks_visual(intensity.size(), QImage::Format_ARGB32_Premultiplied); + peaks_visual.fill(qRgb(0xff, 0x00, 0x00)); + QImage alpha_channel(peaks.toQImage()); + if (qGray(alpha_channel.color(0)) < qGray(alpha_channel.color(1))) { + alpha_channel.setColor(0, qRgb(0x80, 0x80, 0x80)); + alpha_channel.setColor(1, 0); + } else { + alpha_channel.setColor(0, 0); + alpha_channel.setColor(1, qRgb(0x80, 0x80, 0x80)); + } + peaks_visual.setAlphaChannel(alpha_channel); + + QImage visual(intensity.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + + { + QPainter painter(&visual); + painter.drawImage(QPoint(0, 0), peaks_visual); + } + + return visual; } // HoughLineDetector::visualizeHoughSpace std::vector HoughLineDetector::findLines(const unsigned quality_lower_bound) const { - BinaryImage peaks(findHistogramPeaks(m_histogram, m_histWidth, m_histHeight, quality_lower_bound)); + BinaryImage peaks(findHistogramPeaks(m_histogram, m_histWidth, m_histHeight, quality_lower_bound)); - std::vector lines; + std::vector lines; - const QRect peaks_rect(peaks.rect()); - ConnCompEraser eraser(peaks.release(), CONN8); - ConnComp cc; - while (!(cc = eraser.nextConnComp()).isNull()) { - const unsigned level = m_histogram[cc.seed().y() * m_histWidth + cc.seed().x()]; + const QRect peaks_rect(peaks.rect()); + ConnCompEraser eraser(peaks.release(), CONN8); + ConnComp cc; + while (!(cc = eraser.nextConnComp()).isNull()) { + const unsigned level = m_histogram[cc.seed().y() * m_histWidth + cc.seed().x()]; - const QPoint center(cc.rect().center()); - const QPointF norm_uv(m_angleUnitVectors[center.y()]); - const double distance = (center.x() + 0.5) * m_distanceResolution - m_distanceBias; - lines.emplace_back(norm_uv, distance, level); - } + const QPoint center(cc.rect().center()); + const QPointF norm_uv(m_angleUnitVectors[center.y()]); + const double distance = (center.x() + 0.5) * m_distanceResolution - m_distanceBias; + lines.emplace_back(norm_uv, distance, level); + } - std::sort(lines.begin(), lines.end(), GreaterQualityFirst()); + std::sort(lines.begin(), lines.end(), GreaterQualityFirst()); - return lines; + return lines; } BinaryImage HoughLineDetector::findHistogramPeaks(const std::vector& hist, const int width, const int height, const unsigned lower_bound) { - // Peak candidates are connected components of bins having the same - // value. Such a connected component may or may not be a peak. - BinaryImage peak_candidates(findPeakCandidates(hist, width, height, lower_bound)); - - // To check if a peak candidate is really a peak, we have to check - // that every bin in its neighborhood has a lower value than that - // candidate. The are working with 5x5 neighborhoods. - BinaryImage neighborhood_mask(dilateBrick(peak_candidates, QSize(5, 5))); - rasterOp>(neighborhood_mask, peak_candidates); - - // Bins in the neighborhood of a peak candidate fall into two categories: - // 1. The bin has a lower value than the peak candidate. - // 2. The bin has the same value as the peak candidate, - // but it has a greater bin in its neighborhood. - // The second case indicates that our candidate is not relly a peak. - // To test for the second case we are going to increment the values - // of the bins in the neighborhood of peak candidates, find the peak - // candidates again and analize the differences. - std::vector new_hist(hist); - incrementBinsMasked(new_hist, width, height, neighborhood_mask); - neighborhood_mask.release(); - - BinaryImage diff(findPeakCandidates(new_hist, width, height, lower_bound)); - rasterOp>(diff, peak_candidates); - - // If a bin that has changed its state was a part of a peak candidate, - // it means a neighboring bin went from equal to a greater value, - // which indicates that such candidate is not a peak. - const BinaryImage not_peaks(seedFill(diff, peak_candidates, CONN8)); - - rasterOp>(peak_candidates, not_peaks); - - return peak_candidates; + // Peak candidates are connected components of bins having the same + // value. Such a connected component may or may not be a peak. + BinaryImage peak_candidates(findPeakCandidates(hist, width, height, lower_bound)); + + // To check if a peak candidate is really a peak, we have to check + // that every bin in its neighborhood has a lower value than that + // candidate. The are working with 5x5 neighborhoods. + BinaryImage neighborhood_mask(dilateBrick(peak_candidates, QSize(5, 5))); + rasterOp>(neighborhood_mask, peak_candidates); + + // Bins in the neighborhood of a peak candidate fall into two categories: + // 1. The bin has a lower value than the peak candidate. + // 2. The bin has the same value as the peak candidate, + // but it has a greater bin in its neighborhood. + // The second case indicates that our candidate is not relly a peak. + // To test for the second case we are going to increment the values + // of the bins in the neighborhood of peak candidates, find the peak + // candidates again and analize the differences. + std::vector new_hist(hist); + incrementBinsMasked(new_hist, width, height, neighborhood_mask); + neighborhood_mask.release(); + + BinaryImage diff(findPeakCandidates(new_hist, width, height, lower_bound)); + rasterOp>(diff, peak_candidates); + + // If a bin that has changed its state was a part of a peak candidate, + // it means a neighboring bin went from equal to a greater value, + // which indicates that such candidate is not a peak. + const BinaryImage not_peaks(seedFill(diff, peak_candidates, CONN8)); + + rasterOp>(peak_candidates, not_peaks); + + return peak_candidates; } // HoughLineDetector::findHistogramPeaks /** @@ -224,14 +222,14 @@ BinaryImage HoughLineDetector::findPeakCandidates(const std::vector& h const int width, const int height, const unsigned lower_bound) { - std::vector maxed(hist.size(), 0); + std::vector maxed(hist.size(), 0); - // Every bin becomes the maximum of itself and its neighbors. - max5x5(hist, maxed, width, height); - // Those that haven't changed didn't have a greater neighbor. - BinaryImage equal_map(buildEqualMap(hist, maxed, width, height, lower_bound)); + // Every bin becomes the maximum of itself and its neighbors. + max5x5(hist, maxed, width, height); + // Those that haven't changed didn't have a greater neighbor. + BinaryImage equal_map(buildEqualMap(hist, maxed, width, height, lower_bound)); - return equal_map; + return equal_map; } /** @@ -241,20 +239,20 @@ void HoughLineDetector::incrementBinsMasked(std::vector& hist, const int width, const int height, const BinaryImage& mask) { - const uint32_t* mask_line = mask.data(); - const int mask_wpl = mask.wordsPerLine(); - unsigned* hist_line = &hist[0]; - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - ++hist_line[x]; - } - } - mask_line += mask_wpl; - hist_line += width; + const uint32_t* mask_line = mask.data(); + const int mask_wpl = mask.wordsPerLine(); + unsigned* hist_line = &hist[0]; + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + ++hist_line[x]; + } } + mask_line += mask_wpl; + hist_line += width; + } } /** @@ -265,11 +263,11 @@ void HoughLineDetector::max5x5(const std::vector& src, std::vector& dst, const int width, const int height) { - std::vector tmp(src.size(), 0); - max3x1(src, tmp, width, height); - max3x1(tmp, dst, width, height); - max1x3(dst, tmp, width, height); - max1x3(tmp, dst, width, height); + std::vector tmp(src.size(), 0); + max3x1(src, tmp, width, height); + max3x1(tmp, dst, width, height); + max1x3(dst, tmp, width, height); + max1x3(tmp, dst, width, height); } /** @@ -280,32 +278,32 @@ void HoughLineDetector::max3x1(const std::vector& src, std::vector& dst, const int width, const int height) { - if (width == 1) { - dst = src; + if (width == 1) { + dst = src; - return; - } + return; + } + + const unsigned* src_line = &src[0]; + unsigned* dst_line = &dst[0]; - const unsigned* src_line = &src[0]; - unsigned* dst_line = &dst[0]; - - for (int y = 0; y < height; ++y) { - // First column (no left neighbors). - int x = 0; - dst_line[x] = std::max(src_line[x], src_line[x + 1]); - - for (++x; x < width - 1; ++x) { - const unsigned prev = src_line[x - 1]; - const unsigned cur = src_line[x]; - const unsigned next = src_line[x + 1]; - dst_line[x] = std::max(prev, std::max(cur, next)); - } - // Last column (no right neighbors). - dst_line[x] = std::max(src_line[x], src_line[x - 1]); - - src_line += width; - dst_line += width; + for (int y = 0; y < height; ++y) { + // First column (no left neighbors). + int x = 0; + dst_line[x] = std::max(src_line[x], src_line[x + 1]); + + for (++x; x < width - 1; ++x) { + const unsigned prev = src_line[x - 1]; + const unsigned cur = src_line[x]; + const unsigned next = src_line[x + 1]; + dst_line[x] = std::max(prev, std::max(cur, next)); } + // Last column (no right neighbors). + dst_line[x] = std::max(src_line[x], src_line[x - 1]); + + src_line += width; + dst_line += width; + } } /** @@ -316,37 +314,37 @@ void HoughLineDetector::max1x3(const std::vector& src, std::vector& dst, const int width, const int height) { - if (height == 1) { - dst = src; - - return; - } - // First row (no top neighbors). - const unsigned* p_src = &src[0]; - unsigned* p_dst = &dst[0]; + if (height == 1) { + dst = src; + + return; + } + // First row (no top neighbors). + const unsigned* p_src = &src[0]; + unsigned* p_dst = &dst[0]; + for (int x = 0; x < width; ++x) { + *p_dst = std::max(p_src[0], p_src[width]); + ++p_src; + ++p_dst; + } + + for (int y = 1; y < height - 1; ++y) { for (int x = 0; x < width; ++x) { - *p_dst = std::max(p_src[0], p_src[width]); - ++p_src; - ++p_dst; + const unsigned prev = p_src[x - width]; + const unsigned cur = p_src[x]; + const unsigned next = p_src[x + width]; + p_dst[x] = std::max(prev, std::max(cur, next)); } - for (int y = 1; y < height - 1; ++y) { - for (int x = 0; x < width; ++x) { - const unsigned prev = p_src[x - width]; - const unsigned cur = p_src[x]; - const unsigned next = p_src[x + width]; - p_dst[x] = std::max(prev, std::max(cur, next)); - } - - p_src += width; - p_dst += width; - } - // Last row (no bottom neighbors). - for (int x = 0; x < width; ++x) { - *p_dst = std::max(p_src[0], p_src[-width]); - ++p_src; - ++p_dst; - } + p_src += width; + p_dst += width; + } + // Last row (no bottom neighbors). + for (int x = 0; x < width; ++x) { + *p_dst = std::max(p_src[0], p_src[-width]); + ++p_src; + ++p_dst; + } } // HoughLineDetector::max1x3 /** @@ -359,45 +357,45 @@ BinaryImage HoughLineDetector::buildEqualMap(const std::vector& src1, const int width, const int height, const unsigned lower_bound) { - BinaryImage dst(width, height, WHITE); - uint32_t* dst_line = dst.data(); - const int dst_wpl = dst.wordsPerLine(); - const unsigned* src1_line = &src1[0]; - const unsigned* src2_line = &src2[0]; - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if ((src1_line[x] >= lower_bound) && (src1_line[x] == src2_line[x])) { - dst_line[x >> 5] |= msb >> (x & 31); - } - } - dst_line += dst_wpl; - src1_line += width; - src2_line += width; + BinaryImage dst(width, height, WHITE); + uint32_t* dst_line = dst.data(); + const int dst_wpl = dst.wordsPerLine(); + const unsigned* src1_line = &src1[0]; + const unsigned* src2_line = &src2[0]; + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if ((src1_line[x] >= lower_bound) && (src1_line[x] == src2_line[x])) { + dst_line[x >> 5] |= msb >> (x & 31); + } } + dst_line += dst_wpl; + src1_line += width; + src2_line += width; + } - return dst; + return dst; } /*=============================== HoughLine ================================*/ QPointF HoughLine::pointAtY(const double y) const { - double x = (m_distance - y * m_normUnitVector.y()) / m_normUnitVector.x(); + double x = (m_distance - y * m_normUnitVector.y()) / m_normUnitVector.x(); - return QPointF(x, y); + return QPointF(x, y); } QPointF HoughLine::pointAtX(const double x) const { - double y = (m_distance - x * m_normUnitVector.x()) / m_normUnitVector.y(); + double y = (m_distance - x * m_normUnitVector.x()) / m_normUnitVector.y(); - return QPointF(x, y); + return QPointF(x, y); } QLineF HoughLine::unitSegment() const { - const QPointF line_point(m_normUnitVector * m_distance); - const QPointF line_vector(m_normUnitVector.y(), -m_normUnitVector.x()); + const QPointF line_point(m_normUnitVector * m_distance); + const QPointF line_vector(m_normUnitVector.y(), -m_normUnitVector.x()); - return QLineF(line_point, line_point + line_vector); + return QLineF(line_point, line_point + line_vector); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/HoughLineDetector.h b/imageproc/HoughLineDetector.h index b78dc4836..77d336725 100644 --- a/imageproc/HoughLineDetector.h +++ b/imageproc/HoughLineDetector.h @@ -38,154 +38,140 @@ class BinaryImage; * unit_vector is the normal vector for that line. */ class HoughLine { -public: - HoughLine() : m_normUnitVector(), m_distance(), m_quality() { - } - - HoughLine(const QPointF& norm_uv, double distance, unsigned quality) - : m_normUnitVector(norm_uv), m_distance(distance), m_quality(quality) { - } - - const QPointF& normUnitVector() const { - return m_normUnitVector; - } - - double distance() const { - return m_distance; - } - - /** - * \brief The sum of weights of points on the line. - * - * The weight of a point is an argument to HoughLineDetector::put(). - */ - unsigned quality() const { - return m_quality; - } - - QPointF pointAtY(double y) const; - - QPointF pointAtX(double x) const; - - /** - * \brief Returns an arbitrary line segment of length 1. - */ - QLineF unitSegment() const; - -private: - QPointF m_normUnitVector; - double m_distance; - unsigned m_quality; + public: + HoughLine() : m_normUnitVector(), m_distance(), m_quality() {} + + HoughLine(const QPointF& norm_uv, double distance, unsigned quality) + : m_normUnitVector(norm_uv), m_distance(distance), m_quality(quality) {} + + const QPointF& normUnitVector() const { return m_normUnitVector; } + + double distance() const { return m_distance; } + + /** + * \brief The sum of weights of points on the line. + * + * The weight of a point is an argument to HoughLineDetector::put(). + */ + unsigned quality() const { return m_quality; } + + QPointF pointAtY(double y) const; + + QPointF pointAtX(double x) const; + + /** + * \brief Returns an arbitrary line segment of length 1. + */ + QLineF unitSegment() const; + + private: + QPointF m_normUnitVector; + double m_distance; + unsigned m_quality; }; class HoughLineDetector { -public: - /** - * \brief A line finder based on Hough transform. - * - * \param input_dimensions The range of valid input coordinates, - * which are [0, width - 1] for x and [0, height - 1] for y. - * \param distance_resolution The distance in input units that - * represents the width of the lines we are searching for. - * The more this parameter is, the more pixels on the sides - * of a line will be considered a part of it. - * Normally this parameter greater than 1, but theoretically - * it maybe any positive value. - * \param start_angle The first angle to check for. This angle - * is between the normal vector of a line we are looking for - * and the X axis. The angle is in degrees. - * \param angle_delta The difference (in degrees) between an - * angle and the next one. - * \param num_angles The number of angles to check. - */ - HoughLineDetector(const QSize& input_dimensions, - double distance_resolution, - double start_angle, - double angle_delta, - int num_angles); - - /** - * \brief Processes a point with a specified weight. - */ - void process(int x, int y, unsigned weight = 1); - - QImage visualizeHoughSpace(unsigned lower_bound) const; - - /** - * \brief Returns the lines found among the input points. - * - * The lines will be ordered by the descending quality. - * \see HoughLineDetector::Line::quality() - * - * \param quality_lower_bound The minimum acceptable line quality. - */ - std::vector findLines(unsigned quality_lower_bound) const; - -private: - class GreaterQualityFirst; - - static BinaryImage findHistogramPeaks(const std::vector& hist, - int width, - int height, - unsigned lower_bound); - - static BinaryImage findPeakCandidates(const std::vector& hist, - int width, - int height, - unsigned lower_bound); - - static void incrementBinsMasked(std::vector& hist, int width, int height, const BinaryImage& mask); - - static void max5x5(const std::vector& src, std::vector& dst, int width, int height); - - static void max3x1(const std::vector& src, std::vector& dst, int width, int height); - - static void max1x3(const std::vector& src, std::vector& dst, int width, int height); - - static BinaryImage buildEqualMap(const std::vector& src1, - const std::vector& src2, - int width, - int height, - unsigned lower_bound); - - /** - * \brief A 2D histogram laid out in raster order. - * - * Rows correspond to line angles while columns correspond to - * line distances from the origin. - */ - std::vector m_histogram; - - /** - * \brief An array of sines (y) and cosines(x) of angles we working with. - */ - std::vector m_angleUnitVectors; - - /** - * \see HoughLineDetector:HoughLineDetector() - */ - double m_distanceResolution; - - /** - * 1.0 / m_distanceResolution - */ - double m_recipDistanceResolution; - - /** - * The value to be added to distance to make sure it's positive. - */ - double m_distanceBias; - - /** - * The width of m_histogram. - */ - int m_histWidth; - - /** - * The height of m_histogram. - */ - int m_histHeight; + public: + /** + * \brief A line finder based on Hough transform. + * + * \param input_dimensions The range of valid input coordinates, + * which are [0, width - 1] for x and [0, height - 1] for y. + * \param distance_resolution The distance in input units that + * represents the width of the lines we are searching for. + * The more this parameter is, the more pixels on the sides + * of a line will be considered a part of it. + * Normally this parameter greater than 1, but theoretically + * it maybe any positive value. + * \param start_angle The first angle to check for. This angle + * is between the normal vector of a line we are looking for + * and the X axis. The angle is in degrees. + * \param angle_delta The difference (in degrees) between an + * angle and the next one. + * \param num_angles The number of angles to check. + */ + HoughLineDetector(const QSize& input_dimensions, + double distance_resolution, + double start_angle, + double angle_delta, + int num_angles); + + /** + * \brief Processes a point with a specified weight. + */ + void process(int x, int y, unsigned weight = 1); + + QImage visualizeHoughSpace(unsigned lower_bound) const; + + /** + * \brief Returns the lines found among the input points. + * + * The lines will be ordered by the descending quality. + * \see HoughLineDetector::Line::quality() + * + * \param quality_lower_bound The minimum acceptable line quality. + */ + std::vector findLines(unsigned quality_lower_bound) const; + + private: + class GreaterQualityFirst; + + static BinaryImage findHistogramPeaks(const std::vector& hist, int width, int height, unsigned lower_bound); + + static BinaryImage findPeakCandidates(const std::vector& hist, int width, int height, unsigned lower_bound); + + static void incrementBinsMasked(std::vector& hist, int width, int height, const BinaryImage& mask); + + static void max5x5(const std::vector& src, std::vector& dst, int width, int height); + + static void max3x1(const std::vector& src, std::vector& dst, int width, int height); + + static void max1x3(const std::vector& src, std::vector& dst, int width, int height); + + static BinaryImage buildEqualMap(const std::vector& src1, + const std::vector& src2, + int width, + int height, + unsigned lower_bound); + + /** + * \brief A 2D histogram laid out in raster order. + * + * Rows correspond to line angles while columns correspond to + * line distances from the origin. + */ + std::vector m_histogram; + + /** + * \brief An array of sines (y) and cosines(x) of angles we working with. + */ + std::vector m_angleUnitVectors; + + /** + * \see HoughLineDetector:HoughLineDetector() + */ + double m_distanceResolution; + + /** + * 1.0 / m_distanceResolution + */ + double m_recipDistanceResolution; + + /** + * The value to be added to distance to make sure it's positive. + */ + double m_distanceBias; + + /** + * The width of m_histogram. + */ + int m_histWidth; + + /** + * The height of m_histogram. + */ + int m_histHeight; }; } // namespace imageproc #endif // ifndef IMAGEPROC_HOUGHLINEDETECTOR_H_ diff --git a/imageproc/ImageCombination.cpp b/imageproc/ImageCombination.cpp index 8f88532c2..5963f3bd6 100644 --- a/imageproc/ImageCombination.cpp +++ b/imageproc/ImageCombination.cpp @@ -1,411 +1,402 @@ #include "ImageCombination.h" -#include "BinaryImage.h" #include #include +#include +#include "BinaryImage.h" namespace imageproc { -namespace { -template -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); - const uint32_t* foreground_line = foreground.data(); - const int foreground_stride = foreground.wordsPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (foreground_line[x >> 5] & (msb >> (x & 31))) { - uint32_t tmp = foreground_line[x >> 5]; - tmp >>= (31 - (x & 31)); - tmp &= uint32_t(1); - - --tmp; - tmp |= 0xff000000; - mixed_line[x] = static_cast(tmp); - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - } +namespace impl { +template +void combineImagesMono(QImage& mixedImage, const BinaryImage& foreground) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); + const uint32_t* foreground_line = foreground.data(); + const int foreground_stride = foreground.wordsPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (foreground_line[x >> 5] & (msb >> (x & 31))) { + uint32_t tmp = foreground_line[x >> 5]; + tmp >>= (31 - (x & 31)); + tmp &= uint32_t(1); + + --tmp; + tmp |= 0xff000000; + mixed_line[x] = static_cast(tmp); + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + } } -template -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); - const uint32_t* foreground_line = foreground.data(); - const int foreground_stride = foreground.wordsPerLine(); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - uint32_t tmp = foreground_line[x >> 5]; - tmp >>= (31 - (x & 31)); - tmp &= uint32_t(1); - - --tmp; - tmp |= 0xff000000; - mixed_line[x] = static_cast(tmp); - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - mask_line += mask_stride; - } +template +void combineImagesMono(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); + const uint32_t* foreground_line = foreground.data(); + const int foreground_stride = foreground.wordsPerLine(); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + uint32_t tmp = foreground_line[x >> 5]; + tmp >>= (31 - (x & 31)); + tmp &= uint32_t(1); + + --tmp; + tmp |= 0xff000000; + mixed_line[x] = static_cast(tmp); + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + mask_line += mask_stride; + } } -template -void combineImageColor(QImage& mixedImage, const QImage& foreground) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); - const auto* foreground_line = reinterpret_cast(foreground.bits()); - const int foreground_stride = foreground.bytesPerLine() / sizeof(MixedPixel); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const auto msb = uint32_t(0x00ffffff); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if ((foreground_line[x] & msb) != msb) { - mixed_line[x] = foreground_line[x]; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - } +template +void combineImagesColor(QImage& mixedImage, const QImage& foreground) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); + const auto* foreground_line = reinterpret_cast(foreground.bits()); + const int foreground_stride = foreground.bytesPerLine() / sizeof(MixedPixel); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const auto msb = uint32_t(0x00ffffff); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if ((foreground_line[x] & msb) != msb) { + mixed_line[x] = foreground_line[x]; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + } } -template -void combineImageColor(QImage& mixedImage, const QImage& foreground); - -template<> -void combineImageColor(QImage& mixedImage, const QImage& foreground) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(uint32_t); - const auto* foreground_line = foreground.bits(); - const int foreground_stride = foreground.bytesPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const auto msb = uint32_t(0x00ffffff); - - QVector foreground_palette = foreground.colorTable(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t color = foreground_palette[foreground_line[x]]; - if ((color & msb) != msb) { - mixed_line[x] = color; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - } +template +void combineImagesColor(QImage& mixedImage, const QImage& foreground); + +template <> +void combineImagesColor(QImage& mixedImage, const QImage& foreground) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(uint32_t); + const auto* foreground_line = foreground.bits(); + const int foreground_stride = foreground.bytesPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const auto msb = uint32_t(0x00ffffff); + + const QVector foreground_palette = foreground.colorTable(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t color = foreground_palette[foreground_line[x]]; + if ((color & msb) != msb) { + mixed_line[x] = color; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + } } -template<> -void combineImageColor(QImage& mixedImage, const QImage& foreground) { - auto* mixed_line = mixedImage.bits(); - const int mixed_stride = mixedImage.bytesPerLine(); - const auto* foreground_line = foreground.bits(); - const int foreground_stride = foreground.bytesPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const auto msb = uint32_t(0x00ffffff); - - QVector mixed_palette = mixedImage.colorTable(); - QVector foreground_palette = foreground.colorTable(); - if (mixed_palette.size() < 256) { - for (uint32_t color : foreground_palette) { - if (!mixed_palette.contains(color)) { - mixed_palette.push_back(color); - } - } - if (mixed_palette.size() > 256) { - mixed_palette.resize(256); - } - mixedImage.setColorTable(mixed_palette); +void mergePalettes(QVector& mixed_palette, const QVector& palette) { + std::unordered_set mixed_colors(mixed_palette.begin(), mixed_palette.end()); + for (uint32_t color : palette) { + if (mixed_colors.find(color) == mixed_colors.end()) { + mixed_palette.push_back(color); + mixed_colors.insert(color); } + } +} - std::unordered_map colorToIndex; - for (int i = 0; i < mixed_palette.size(); ++i) { - colorToIndex[mixed_palette[i]] = static_cast(i); - } +template <> +void combineImagesColor(QImage& mixedImage, const QImage& foreground) { + auto* mixed_line = mixedImage.bits(); + const int mixed_stride = mixedImage.bytesPerLine(); + const auto* foreground_line = foreground.bits(); + const int foreground_stride = foreground.bytesPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const auto msb = uint32_t(0x00ffffff); + + QVector mixed_palette = mixedImage.colorTable(); + const QVector foreground_palette = foreground.colorTable(); + if (mixed_palette.size() < 256) { + mergePalettes(mixed_palette, foreground_palette); + if (mixed_palette.size() > 256) { + mixed_palette.resize(256); + } + mixedImage.setColorTable(mixed_palette); + } + + std::unordered_map colorToIndex; + for (int i = 0; i < mixed_palette.size(); ++i) { + colorToIndex[mixed_palette[i]] = static_cast(i); + } + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t color = foreground_palette[foreground_line[x]]; + if ((color & msb) != msb) { + mixed_line[x] = colorToIndex[color]; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + } +} - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t color = foreground_palette[foreground_line[x]]; - if ((color & msb) != msb) { - mixed_line[x] = colorToIndex[color]; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - } +template +void combineImagesColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); + const auto* foreground_line = reinterpret_cast(foreground.bits()); + const int foreground_stride = foreground.bytesPerLine() / sizeof(MixedPixel); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + mixed_line[x] = foreground_line[x]; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + mask_line += mask_stride; + } } -template -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(MixedPixel); - const auto* foreground_line = reinterpret_cast(foreground.bits()); - const int foreground_stride = foreground.bytesPerLine() / sizeof(MixedPixel); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - mixed_line[x] = foreground_line[x]; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - mask_line += mask_stride; - } +template +void combineImagesColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask); + +template <> +void combineImagesColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { + auto* mixed_line = reinterpret_cast(mixedImage.bits()); + const int mixed_stride = mixedImage.bytesPerLine() / sizeof(uint32_t); + const auto* foreground_line = foreground.bits(); + const int foreground_stride = foreground.bytesPerLine(); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const uint32_t msb = uint32_t(1) << 31; + + const QVector foreground_palette = foreground.colorTable(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + uint32_t color = foreground_palette[foreground_line[x]]; + mixed_line[x] = color; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + mask_line += mask_stride; + } } -template -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask); - -template<> -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { - auto* mixed_line = reinterpret_cast(mixedImage.bits()); - const int mixed_stride = mixedImage.bytesPerLine() / sizeof(uint32_t); - const auto* foreground_line = foreground.bits(); - const int foreground_stride = foreground.bytesPerLine(); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const uint32_t msb = uint32_t(1) << 31; - - QVector foreground_palette = foreground.colorTable(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - uint32_t color = foreground_palette[foreground_line[x]]; - mixed_line[x] = color; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - mask_line += mask_stride; - } +template <> +void combineImagesColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { + auto* mixed_line = mixedImage.bits(); + const int mixed_stride = mixedImage.bytesPerLine(); + const auto* foreground_line = foreground.bits(); + const int foreground_stride = foreground.bytesPerLine(); + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + const int width = mixedImage.width(); + const int height = mixedImage.height(); + const uint32_t msb = uint32_t(1) << 31; + + QVector mixed_palette = mixedImage.colorTable(); + const QVector foreground_palette = foreground.colorTable(); + if (mixed_palette.size() < 256) { + mergePalettes(mixed_palette, foreground_palette); + if (mixed_palette.size() > 256) { + mixed_palette.resize(256); + } + mixedImage.setColorTable(mixed_palette); + } + + std::unordered_map colorToIndex; + for (int i = 0; i < mixed_palette.size(); ++i) { + colorToIndex[mixed_palette[i]] = static_cast(i); + } + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + uint32_t color = foreground_palette[foreground_line[x]]; + mixed_line[x] = colorToIndex[color]; + } + } + mixed_line += mixed_stride; + foreground_line += foreground_stride; + mask_line += mask_stride; + } } -template<> -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { - auto* mixed_line = mixedImage.bits(); - const int mixed_stride = mixedImage.bytesPerLine(); - const auto* foreground_line = foreground.bits(); - const int foreground_stride = foreground.bytesPerLine(); - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - const int width = mixedImage.width(); - const int height = mixedImage.height(); - const uint32_t msb = uint32_t(1) << 31; - - QVector mixed_palette = mixedImage.colorTable(); - QVector foreground_palette = foreground.colorTable(); - if (mixed_palette.size() < 256) { - for (uint32_t color : foreground_palette) { - if (!mixed_palette.contains(color)) { - mixed_palette.push_back(color); - } - } - if (mixed_palette.size() > 256) { - mixed_palette.resize(256); - } - mixedImage.setColorTable(mixed_palette); - } +template +void applyMask(QImage& image, const BinaryImage& bw_mask, const BWColor filling_color = WHITE) { + auto* image_line = reinterpret_cast(image.bits()); + const int image_stride = image.bytesPerLine() / sizeof(MixedPixel); + const uint32_t* bw_mask_line = bw_mask.data(); + const int bw_mask_stride = bw_mask.wordsPerLine(); + const int width = image.width(); + const int height = image.height(); + const uint32_t msb = uint32_t(1) << 31; + const auto fillingPixel = static_cast((filling_color == WHITE) ? 0xffffffff : 0x00000000); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { + image_line[x] = fillingPixel; + } + } + image_line += image_stride; + bw_mask_line += bw_mask_stride; + } +} +} // namespace impl - std::unordered_map colorToIndex; - for (int i = 0; i < mixed_palette.size(); ++i) { - colorToIndex[mixed_palette[i]] = static_cast(i); - } +namespace { +inline void checkImageFormatSupported(const QImage& image) { + if ((image.format() != QImage::Format_Indexed8) && (image.format() != QImage::Format_RGB32) + && (image.format() != QImage::Format_ARGB32)) { + throw std::invalid_argument("ImageCombination: wrong image format."); + } +} - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - uint32_t color = foreground_palette[foreground_line[x]]; - mixed_line[x] = colorToIndex[color]; - } - } - mixed_line += mixed_stride; - foreground_line += foreground_stride; - mask_line += mask_stride; - } +template +inline void checkImagesHaveEqualSize(const T1& image1, const T2& image2) { + if (image1.size() != image2.size()) { + throw std::invalid_argument("ImageCombination: images size don't match."); + } } -template -void applyMask(QImage& image, const BinaryImage& bw_mask, const BWColor filling_color = WHITE) { - auto* image_line = reinterpret_cast(image.bits()); - const int image_stride = image.bytesPerLine() / sizeof(MixedPixel); - const uint32_t* bw_mask_line = bw_mask.data(); - const int bw_mask_stride = bw_mask.wordsPerLine(); - const int width = image.width(); - const int height = image.height(); - const uint32_t msb = uint32_t(1) << 31; - const auto fillingPixel = static_cast((filling_color == WHITE) ? 0xffffffff : 0x00000000); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (!(bw_mask_line[x >> 5] & (msb >> (x & 31)))) { - image_line[x] = fillingPixel; - } - } - image_line += image_stride; - bw_mask_line += bw_mask_stride; - } +template +inline void checkImagesHaveEqualSize(const T1& image1, const T2& image2, const T3& image3) { + if ((image1.size() != image2.size()) || (image1.size() != image3.size())) { + throw std::invalid_argument("ImageCombination: images size don't match."); + } } } // namespace +namespace impl { +void combineImagesMono(QImage& mixedImage, const BinaryImage& foreground) { + if (mixedImage.format() == QImage::Format_Indexed8) { + combineImagesMono(mixedImage, foreground); + } else { + combineImagesMono(mixedImage, foreground); + } +} -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } +void combineImagesMono(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask) { + if (mixedImage.format() == QImage::Format_Indexed8) { + combineImagesMono(mixedImage, foreground, mask); + } else { + combineImagesMono(mixedImage, foreground, mask); + } +} - if (mixedImage.size() != foreground.size()) { - throw std::invalid_argument("ImageCombination: images size don't match."); +void combineImagesColor(QImage& mixedImage, const QImage& foreground) { + if (mixedImage.format() == QImage::Format_Indexed8) { + if (mixedImage.isGrayscale() && foreground.isGrayscale()) { + combineImagesColor(mixedImage, foreground); + } else { + combineImagesColor(mixedImage, foreground); } - - if (mixedImage.format() == QImage::Format_Indexed8) { - combineImageMono(mixedImage, foreground); + } else { + if ((foreground.format() == QImage::Format_RGB32) || (foreground.format() == QImage::Format_ARGB32)) { + combineImagesColor(mixedImage, foreground); } else { - combineImageMono(mixedImage, foreground); + combineImagesColor(mixedImage, foreground); } + } } -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } - - if ((mixedImage.size() != foreground.size()) || (mixedImage.size() != mask.size())) { - throw std::invalid_argument("ImageCombination: images size don't match."); +void combineImagesColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { + if (mixedImage.format() == QImage::Format_Indexed8) { + if (mixedImage.isGrayscale() && foreground.isGrayscale()) { + combineImagesColor(mixedImage, foreground, mask); + } else { + combineImagesColor(mixedImage, foreground, mask); } - - if (mixedImage.format() == QImage::Format_Indexed8) { - combineImageMono(mixedImage, foreground, mask); + } else { + if ((foreground.format() == QImage::Format_RGB32) || (foreground.format() == QImage::Format_ARGB32)) { + combineImagesColor(mixedImage, foreground, mask); } else { - combineImageMono(mixedImage, foreground, mask); + combineImagesColor(mixedImage, foreground, mask); } + } } -void combineImageColor(QImage& mixedImage, const QImage& foreground) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } +void applyMask(QImage& image, const BinaryImage& bw_mask, const BWColor filling_color) { + if (image.format() == QImage::Format_Indexed8) { + applyMask(image, bw_mask, filling_color); + } else { + applyMask(image, bw_mask, filling_color); + } +} +} // namespace impl - if (mixedImage.size() != foreground.size()) { - throw std::invalid_argument("ImageCombination: images size don't match."); - } +void combineImages(QImage& mixedImage, const BinaryImage& foreground) { + checkImageFormatSupported(mixedImage); + checkImagesHaveEqualSize(mixedImage, foreground); - if (mixedImage.format() == QImage::Format_Indexed8) { - if (mixedImage.isGrayscale() && foreground.isGrayscale()) { - combineImageColor(mixedImage, foreground); - } else { - combineImageColor(mixedImage, foreground); - } - } else { - if ((foreground.format() == QImage::Format_RGB32) || (foreground.format() == QImage::Format_ARGB32)) { - combineImageColor(mixedImage, foreground); - } else { - combineImageColor(mixedImage, foreground); - } - } + impl::combineImagesMono(mixedImage, foreground); } -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } - - if ((mixedImage.size() != foreground.size()) || (mixedImage.size() != mask.size())) { - throw std::invalid_argument("ImageCombination: images size don't match."); - } +void combineImages(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask) { + checkImageFormatSupported(mixedImage); + checkImagesHaveEqualSize(mixedImage, foreground); - if (mixedImage.format() == QImage::Format_Indexed8) { - if (mixedImage.isGrayscale() && foreground.isGrayscale()) { - combineImageColor(mixedImage, foreground, mask); - } else { - combineImageColor(mixedImage, foreground, mask); - } - } else { - if ((foreground.format() == QImage::Format_RGB32) || (foreground.format() == QImage::Format_ARGB32)) { - combineImageColor(mixedImage, foreground, mask); - } else { - combineImageColor(mixedImage, foreground, mask); - } - } + impl::combineImagesMono(mixedImage, foreground, mask); } -void combineImage(QImage& mixedImage, const QImage& foreground) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } - - if (mixedImage.size() != foreground.size()) { - throw std::invalid_argument("ImageCombination: images size don't match."); - } +void combineImages(QImage& mixedImage, const QImage& foreground) { + checkImageFormatSupported(mixedImage); + checkImagesHaveEqualSize(mixedImage, foreground); - if ((foreground.format() == QImage::Format_Mono) || (foreground.format() == QImage::Format_MonoLSB)) { - combineImageMono(mixedImage, BinaryImage(foreground)); - } else { - combineImageColor(mixedImage, foreground); - } + if ((foreground.format() == QImage::Format_Mono) || (foreground.format() == QImage::Format_MonoLSB)) { + impl::combineImagesMono(mixedImage, BinaryImage(foreground)); + } else { + impl::combineImagesColor(mixedImage, foreground); + } } -void combineImage(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { - if ((mixedImage.format() != QImage::Format_Indexed8) && (mixedImage.format() != QImage::Format_RGB32) - && (mixedImage.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } - - if ((mixedImage.size() != foreground.size()) || (mixedImage.size() != mask.size())) { - throw std::invalid_argument("ImageCombination: images size don't match."); - } +void combineImages(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask) { + checkImageFormatSupported(mixedImage); + checkImagesHaveEqualSize(mixedImage, foreground, mask); - if ((foreground.format() == QImage::Format_Mono) || (foreground.format() == QImage::Format_MonoLSB)) { - combineImageMono(mixedImage, BinaryImage(foreground), mask); - } else { - combineImageColor(mixedImage, foreground, mask); - } + if ((foreground.format() == QImage::Format_Mono) || (foreground.format() == QImage::Format_MonoLSB)) { + impl::combineImagesMono(mixedImage, BinaryImage(foreground), mask); + } else { + impl::combineImagesColor(mixedImage, foreground, mask); + } } void applyMask(QImage& image, const BinaryImage& bw_mask, const BWColor filling_color) { - if ((image.format() != QImage::Format_Indexed8) && (image.format() != QImage::Format_RGB32) - && (image.format() != QImage::Format_ARGB32)) { - throw std::invalid_argument("ImageCombination: wrong image format."); - } + checkImageFormatSupported(image); + checkImagesHaveEqualSize(image, bw_mask); - if (image.size() != bw_mask.size()) { - throw std::invalid_argument("ImageCombination: images size don't match."); - } - - if (image.format() == QImage::Format_Indexed8) { - applyMask(image, bw_mask, filling_color); - } else { - applyMask(image, bw_mask, filling_color); - } + impl::applyMask(image, bw_mask, filling_color); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ImageCombination.h b/imageproc/ImageCombination.h index 3e11a5c05..8614a335f 100644 --- a/imageproc/ImageCombination.h +++ b/imageproc/ImageCombination.h @@ -9,17 +9,13 @@ class QImage; namespace imageproc { class BinaryImage; -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground); +void combineImages(QImage& mixedImage, const BinaryImage& foreground); -void combineImageMono(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask); +void combineImages(QImage& mixedImage, const BinaryImage& foreground, const BinaryImage& mask); -void combineImageColor(QImage& mixedImage, const QImage& foreground); +void combineImages(QImage& mixedImage, const QImage& foreground); -void combineImageColor(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask); - -void combineImage(QImage& mixedImage, const QImage& foreground); - -void combineImage(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask); +void combineImages(QImage& mixedImage, const QImage& foreground, const BinaryImage& mask); void applyMask(QImage& image, const BinaryImage& bw_mask, BWColor filling_color = WHITE); } // namespace imageproc diff --git a/imageproc/InfluenceMap.cpp b/imageproc/InfluenceMap.cpp index 4a6a13821..dbf50a3d2 100644 --- a/imageproc/InfluenceMap.cpp +++ b/imageproc/InfluenceMap.cpp @@ -17,257 +17,256 @@ */ #include "InfluenceMap.h" +#include #include "BinaryImage.h" -#include "ConnectivityMap.h" #include "BitOps.h" -#include +#include "ConnectivityMap.h" class QImage; namespace imageproc { -InfluenceMap::InfluenceMap() : m_pData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { -} +InfluenceMap::InfluenceMap() : m_plainData(nullptr), m_size(), m_stride(0), m_maxLabel(0) {} -InfluenceMap::InfluenceMap(const ConnectivityMap& cmap) : m_pData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { - if (cmap.size().isEmpty()) { - return; - } +InfluenceMap::InfluenceMap(const ConnectivityMap& cmap) : m_plainData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { + if (cmap.size().isEmpty()) { + return; + } - init(cmap); + init(cmap); } InfluenceMap::InfluenceMap(const ConnectivityMap& cmap, const BinaryImage& mask) - : m_pData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { - if (cmap.size().isEmpty()) { - return; - } - if (cmap.size() != mask.size()) { - throw std::invalid_argument("InfluenceMap: cmap and mask have different sizes"); - } - - init(cmap, &mask); + : m_plainData(nullptr), m_size(), m_stride(0), m_maxLabel(0) { + if (cmap.size().isEmpty()) { + return; + } + if (cmap.size() != mask.size()) { + throw std::invalid_argument("InfluenceMap: cmap and mask have different sizes"); + } + + init(cmap, &mask); } InfluenceMap::InfluenceMap(const InfluenceMap& other) - : m_data(other.m_data), - m_pData(nullptr), - m_size(other.size()), - m_stride(other.stride()), - m_maxLabel(other.m_maxLabel) { - if (!m_size.isEmpty()) { - m_pData = &m_data[0] + m_stride + 1; - } + : m_data(other.m_data), + m_plainData(nullptr), + m_size(other.size()), + m_stride(other.stride()), + m_maxLabel(other.m_maxLabel) { + if (!m_size.isEmpty()) { + m_plainData = &m_data[0] + m_stride + 1; + } } InfluenceMap& InfluenceMap::operator=(const InfluenceMap& other) { - InfluenceMap(other).swap(*this); + InfluenceMap(other).swap(*this); - return *this; + return *this; } void InfluenceMap::swap(InfluenceMap& other) { - m_data.swap(other.m_data); - std::swap(m_pData, other.m_pData); - std::swap(m_size, other.m_size); - std::swap(m_stride, other.m_stride); - std::swap(m_maxLabel, other.m_maxLabel); + m_data.swap(other.m_data); + std::swap(m_plainData, other.m_plainData); + std::swap(m_size, other.m_size); + std::swap(m_stride, other.m_stride); + std::swap(m_maxLabel, other.m_maxLabel); } void InfluenceMap::init(const ConnectivityMap& cmap, const BinaryImage* mask) { - const int width = cmap.size().width() + 2; - const int height = cmap.size().height() + 2; - - m_size = cmap.size(); - m_stride = width; - m_data.resize(width * height); - m_pData = &m_data[0] + width + 1; - m_maxLabel = cmap.maxLabel(); - - FastQueue queue; - - Cell* cell = &m_data[0]; - const uint32_t* label = cmap.paddedData(); - for (int i = width * height; i > 0; --i) { - assert(*label <= cmap.maxLabel()); - cell->label = *label; - cell->distSq = 0; - cell->vec.x = 0; - cell->vec.y = 0; - if (*label != 0) { - queue.push(cell); - } - ++cell; - ++label; + const int width = cmap.size().width() + 2; + const int height = cmap.size().height() + 2; + + m_size = cmap.size(); + m_stride = width; + m_data.resize(width * height); + m_plainData = &m_data[0] + width + 1; + m_maxLabel = cmap.maxLabel(); + + FastQueue queue; + + Cell* cell = &m_data[0]; + const uint32_t* label = cmap.paddedData(); + for (int i = width * height; i > 0; --i) { + assert(*label <= cmap.maxLabel()); + cell->label = *label; + cell->distSq = 0; + cell->vec.x = 0; + cell->vec.y = 0; + if (*label != 0) { + queue.push(cell); } - - if (mask) { - const uint32_t* mask_line = mask->data(); - const int mask_stride = mask->wordsPerLine(); - cell = m_pData; - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height - 2; ++y) { - for (int x = 0; x < width - 2; ++x, ++cell) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - if (cell->label == 0) { - cell->distSq = ~uint32_t(0); - } - } - } - mask_line += mask_stride; - cell += 2; - } - } else { - cell = m_pData; - for (int y = 0; y < height - 2; ++y) { - for (int x = 0; x < width - 2; ++x, ++cell) { - if (cell->label == 0) { - cell->distSq = ~uint32_t(0); - } - } - cell += 2; + ++cell; + ++label; + } + + if (mask) { + const uint32_t* mask_line = mask->data(); + const int mask_stride = mask->wordsPerLine(); + cell = m_plainData; + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height - 2; ++y) { + for (int x = 0; x < width - 2; ++x, ++cell) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + if (cell->label == 0) { + cell->distSq = ~uint32_t(0); + } } + } + mask_line += mask_stride; + cell += 2; } - - while (!queue.empty()) { - cell = queue.front(); - queue.pop(); - - assert((cell - &m_data[0]) / width > 0); - assert((cell - &m_data[0]) / width < height - 1); - assert((cell - &m_data[0]) % width > 0); - assert((cell - &m_data[0]) % width < width - 1); - assert(cell->distSq != ~uint32_t(0)); - assert(cell->label != 0); - assert(cell->label <= m_maxLabel); - - const int32_t dx2 = cell->vec.x << 1; - const int32_t dy2 = cell->vec.y << 1; - - // North-western neighbor. - Cell* nbh = cell - width - 1; - uint32_t new_dist_sq = cell->distSq + dx2 + dy2 + 2; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x + 1); - nbh->vec.y = static_cast(cell->vec.y + 1); - queue.push(nbh); - } - // Northern neighbor. - ++nbh; - new_dist_sq = cell->distSq + dy2 + 1; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = cell->vec.x; - nbh->vec.y = static_cast(cell->vec.y + 1); - queue.push(nbh); + } else { + cell = m_plainData; + for (int y = 0; y < height - 2; ++y) { + for (int x = 0; x < width - 2; ++x, ++cell) { + if (cell->label == 0) { + cell->distSq = ~uint32_t(0); } + } + cell += 2; + } + } + + while (!queue.empty()) { + cell = queue.front(); + queue.pop(); + + assert((cell - &m_data[0]) / width > 0); + assert((cell - &m_data[0]) / width < height - 1); + assert((cell - &m_data[0]) % width > 0); + assert((cell - &m_data[0]) % width < width - 1); + assert(cell->distSq != ~uint32_t(0)); + assert(cell->label != 0); + assert(cell->label <= m_maxLabel); + + const int32_t dx2 = cell->vec.x << 1; + const int32_t dy2 = cell->vec.y << 1; + + // North-western neighbor. + Cell* nbh = cell - width - 1; + uint32_t new_dist_sq = cell->distSq + dx2 + dy2 + 2; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x + 1); + nbh->vec.y = static_cast(cell->vec.y + 1); + queue.push(nbh); + } + // Northern neighbor. + ++nbh; + new_dist_sq = cell->distSq + dy2 + 1; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = cell->vec.x; + nbh->vec.y = static_cast(cell->vec.y + 1); + queue.push(nbh); + } - // North-eastern neighbor. - ++nbh; - new_dist_sq = cell->distSq - dx2 + dy2 + 2; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x - 1); - nbh->vec.y = static_cast(cell->vec.y + 1); - queue.push(nbh); - } - // Eastern neighbor. - nbh += width; - new_dist_sq = cell->distSq - dx2 + 1; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x - 1); - nbh->vec.y = cell->vec.y; - queue.push(nbh); - } + // North-eastern neighbor. + ++nbh; + new_dist_sq = cell->distSq - dx2 + dy2 + 2; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x - 1); + nbh->vec.y = static_cast(cell->vec.y + 1); + queue.push(nbh); + } + // Eastern neighbor. + nbh += width; + new_dist_sq = cell->distSq - dx2 + 1; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x - 1); + nbh->vec.y = cell->vec.y; + queue.push(nbh); + } - // South-eastern neighbor. - nbh += width; - new_dist_sq = cell->distSq - dx2 - dy2 + 2; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x - 1); - nbh->vec.y = static_cast(cell->vec.y - 1); - queue.push(nbh); - } - // Southern neighbor. - --nbh; - new_dist_sq = cell->distSq - dy2 + 1; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = cell->vec.x; - nbh->vec.y = static_cast(cell->vec.y - 1); - queue.push(nbh); - } + // South-eastern neighbor. + nbh += width; + new_dist_sq = cell->distSq - dx2 - dy2 + 2; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x - 1); + nbh->vec.y = static_cast(cell->vec.y - 1); + queue.push(nbh); + } + // Southern neighbor. + --nbh; + new_dist_sq = cell->distSq - dy2 + 1; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = cell->vec.x; + nbh->vec.y = static_cast(cell->vec.y - 1); + queue.push(nbh); + } - // South-western neighbor. - --nbh; - new_dist_sq = cell->distSq + dx2 - dy2 + 2; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x + 1); - nbh->vec.y = static_cast(cell->vec.y - 1); - queue.push(nbh); - } - // Western neighbor. - nbh -= width; - new_dist_sq = cell->distSq + dx2 + 1; - if (new_dist_sq < nbh->distSq) { - nbh->label = cell->label; - nbh->distSq = new_dist_sq; - nbh->vec.x = static_cast(cell->vec.x + 1); - nbh->vec.y = cell->vec.y; - queue.push(nbh); - } + // South-western neighbor. + --nbh; + new_dist_sq = cell->distSq + dx2 - dy2 + 2; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x + 1); + nbh->vec.y = static_cast(cell->vec.y - 1); + queue.push(nbh); + } + // Western neighbor. + nbh -= width; + new_dist_sq = cell->distSq + dx2 + 1; + if (new_dist_sq < nbh->distSq) { + nbh->label = cell->label; + nbh->distSq = new_dist_sq; + nbh->vec.x = static_cast(cell->vec.x + 1); + nbh->vec.y = cell->vec.y; + queue.push(nbh); } + } } // InfluenceMap::init QImage InfluenceMap::visualized() const { - if (m_size.isEmpty()) { - return QImage(); - } - - const int width = m_size.width(); - const int height = m_size.height(); - - QImage dst(m_size, QImage::Format_ARGB32); - dst.fill(0x00FFFFFF); // transparent white - const Cell* src_line = m_pData; - const int src_stride = m_stride; - - auto* dst_line = reinterpret_cast(dst.bits()); - const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t val = src_line[x].label; - if (val == 0) { - continue; - } - - const int bits_unused = countMostSignificantZeroes(val); - const uint32_t reversed = reverseBits(val) >> bits_unused; - const uint32_t mask = ~uint32_t(0) >> bits_unused; - - const double H = 0.99 * (double(reversed) / mask); - const double S = 1.0; - const double V = 1.0; - QColor color; - color.setHsvF(H, S, V, 1.0); - - dst_line[x] = color.rgba(); - } - src_line += src_stride; - dst_line += dst_stride; + if (m_size.isEmpty()) { + return QImage(); + } + + const int width = m_size.width(); + const int height = m_size.height(); + + QImage dst(m_size, QImage::Format_ARGB32); + dst.fill(0x00FFFFFF); // transparent white + const Cell* src_line = m_plainData; + const int src_stride = m_stride; + + auto* dst_line = reinterpret_cast(dst.bits()); + const int dst_stride = dst.bytesPerLine() / sizeof(uint32_t); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const uint32_t val = src_line[x].label; + if (val == 0) { + continue; + } + + const int bits_unused = countMostSignificantZeroes(val); + const uint32_t reversed = reverseBits(val) >> bits_unused; + const uint32_t mask = ~uint32_t(0) >> bits_unused; + + const double H = 0.99 * (double(reversed) / mask); + const double S = 1.0; + const double V = 1.0; + QColor color; + color.setHsvF(H, S, V, 1.0); + + dst_line[x] = color.rgba(); } + src_line += src_stride; + dst_line += dst_stride; + } - return dst; + return dst; } // InfluenceMap::visualized } // namespace imageproc \ No newline at end of file diff --git a/imageproc/InfluenceMap.h b/imageproc/InfluenceMap.h index b2bab3e27..a51d1aacf 100644 --- a/imageproc/InfluenceMap.h +++ b/imageproc/InfluenceMap.h @@ -20,8 +20,8 @@ #define IMAGEPROC_INFLUENCE_MAP_H_ #include -#include #include +#include class QImage; @@ -37,147 +37,133 @@ class ConnectivityMap; * a location equidistant from the initial areas of those two labels. */ class InfluenceMap { -public: - struct Vector { - int16_t x; - int16_t y; - }; - - struct Cell { - /** - * The label from the connectivity map. - */ - uint32_t label; - - /** - * The squared euclidean distance to the nearest pixel - * initially (that is before extension) labelled with - * the same label. - */ - uint32_t distSq; - - /** - * The vector pointing to the nearest pixel initially - * (that is before extension) labelled with the same - * label. - */ - Vector vec; - }; - - /** - * \brief Construct a null influence map. - * - * The data() and paddedData() methods return null on such maps. - */ - InfluenceMap(); - - /** - * \brief Take labelled regions from a connectivity map - * and extend them to cover the whole available area. - */ - explicit InfluenceMap(const ConnectivityMap& cmap); + public: + struct Vector { + int16_t x; + int16_t y; + }; + struct Cell { /** - * \brief Take labelled regions from a connectivity map - * and extend them to cover the area that's black in mask. + * The label from the connectivity map. */ - InfluenceMap(const ConnectivityMap& cmap, const BinaryImage& mask); - - InfluenceMap(const InfluenceMap& other); - - InfluenceMap& operator=(const InfluenceMap& other); - - void swap(InfluenceMap& other); - - /** - * \brief Returns a pointer to the top-left corner of the map. - * - * The data is stored in row-major order, and is padded, - * so moving to the next line requires adding stride() rather - * than size().width(). - */ - const Cell* data() const { - return m_pData; - } + uint32_t label; /** - * \brief Returns a pointer to the top-left corner of the map. - * - * The data is stored in row-major order, and is padded, - * so moving to the next line requires adding stride() rather - * than size().width(). + * The squared euclidean distance to the nearest pixel + * initially (that is before extension) labelled with + * the same label. */ - Cell* data() { - return m_pData; - } + uint32_t distSq; /** - * \brief Returns a pointer to the top-left corner of padding of the map. - * - * The actually has a fake line from each side. Those lines are - * labelled as background (label 0). Sometimes it might be desirable - * to access that data. + * The vector pointing to the nearest pixel initially + * (that is before extension) labelled with the same + * label. */ - const Cell* paddedData() const { - return m_pData ? &m_data[0] : nullptr; - } - - /** - * \brief Returns a pointer to the top-left corner of padding of the map. - * - * The actually has a fake line from each side. Those lines are - * labelled as background (label 0). Sometimes it might be desirable - * to access that data. - */ - Cell* paddedData() { - return m_pData ? &m_data[0] : nullptr; - } - - /** - * \brief Returns non-padded dimensions of the map. - */ - QSize size() const { - return m_size; - } - - /** - * \brief Returns the number of units on a padded line. - * - * Whether working with padded or non-padded maps, adding - * this number to a data pointer will move it one line down. - */ - int stride() const { - return m_stride; - } - - /** - * \brief Returns the maximum label present on the map. - */ - uint32_t maxLabel() const { - return m_maxLabel; - } - - /** - * \brief Visualizes each label with a different color. - * - * Label 0 (which is assigned to background) is represented - * by transparent pixels. - */ - QImage visualized() const; - -private: - void init(const ConnectivityMap& cmap, const BinaryImage* mask = nullptr); - - std::vector m_data; - Cell* m_pData; - QSize m_size; - int m_stride; - uint32_t m_maxLabel; + Vector vec; + }; + + /** + * \brief Construct a null influence map. + * + * The data() and paddedData() methods return null on such maps. + */ + InfluenceMap(); + + /** + * \brief Take labelled regions from a connectivity map + * and extend them to cover the whole available area. + */ + explicit InfluenceMap(const ConnectivityMap& cmap); + + /** + * \brief Take labelled regions from a connectivity map + * and extend them to cover the area that's black in mask. + */ + InfluenceMap(const ConnectivityMap& cmap, const BinaryImage& mask); + + InfluenceMap(const InfluenceMap& other); + + InfluenceMap& operator=(const InfluenceMap& other); + + void swap(InfluenceMap& other); + + /** + * \brief Returns a pointer to the top-left corner of the map. + * + * The data is stored in row-major order, and is padded, + * so moving to the next line requires adding stride() rather + * than size().width(). + */ + const Cell* data() const { return m_plainData; } + + /** + * \brief Returns a pointer to the top-left corner of the map. + * + * The data is stored in row-major order, and is padded, + * so moving to the next line requires adding stride() rather + * than size().width(). + */ + Cell* data() { return m_plainData; } + + /** + * \brief Returns a pointer to the top-left corner of padding of the map. + * + * The actually has a fake line from each side. Those lines are + * labelled as background (label 0). Sometimes it might be desirable + * to access that data. + */ + const Cell* paddedData() const { return m_plainData ? &m_data[0] : nullptr; } + + /** + * \brief Returns a pointer to the top-left corner of padding of the map. + * + * The actually has a fake line from each side. Those lines are + * labelled as background (label 0). Sometimes it might be desirable + * to access that data. + */ + Cell* paddedData() { return m_plainData ? &m_data[0] : nullptr; } + + /** + * \brief Returns non-padded dimensions of the map. + */ + QSize size() const { return m_size; } + + /** + * \brief Returns the number of units on a padded line. + * + * Whether working with padded or non-padded maps, adding + * this number to a data pointer will move it one line down. + */ + int stride() const { return m_stride; } + + /** + * \brief Returns the maximum label present on the map. + */ + uint32_t maxLabel() const { return m_maxLabel; } + + /** + * \brief Visualizes each label with a different color. + * + * Label 0 (which is assigned to background) is represented + * by transparent pixels. + */ + QImage visualized() const; + + private: + void init(const ConnectivityMap& cmap, const BinaryImage* mask = nullptr); + + std::vector m_data; + Cell* m_plainData; + QSize m_size; + int m_stride; + uint32_t m_maxLabel; }; inline void swap(InfluenceMap& o1, InfluenceMap& o2) { - o1.swap(o2); + o1.swap(o2); } } // namespace imageproc #endif // ifndef IMAGEPROC_INFLUENCE_MAP_H_ diff --git a/imageproc/IntegralImage.h b/imageproc/IntegralImage.h index 6e21366ff..0204e0e4b 100644 --- a/imageproc/IntegralImage.h +++ b/imageproc/IntegralImage.h @@ -19,10 +19,10 @@ #ifndef IMAGEPROC_INTEGRALIMAGE_H_ #define IMAGEPROC_INTEGRALIMAGE_H_ -#include "NonCopyable.h" -#include #include +#include #include +#include "NonCopyable.h" namespace imageproc { /** @@ -37,121 +37,121 @@ namespace imageproc { * \note Template parameter T must be large enough to hold the sum of all * values in the array. */ -template +template class IntegralImage { - DECLARE_NON_COPYABLE(IntegralImage) - -public: - IntegralImage(int width, int height); - - explicit IntegralImage(const QSize& size); - - ~IntegralImage(); - - /** - * \brief To be called before pushing new row data. - */ - void beginRow(); - - /** - * \brief Push a single value to the integral image. - * - * Values must be pushed row by row, starting from row 0, and from - * column to column within each row, starting from column 0. - * At the beginning of a row, a call to beginRow() must be made. - * - * \note Pushing more than width * height values results in undefined - * behavior. - */ - void push(T val); - - /** - * \brief Calculate the sum of values in the given rectangle. - * - * \note If the rectangle exceeds the image area, the behaviour is - * undefined. - */ - T sum(const QRect& rect) const; - -private: - void init(int width, int height); - - T* m_pData; - T* m_pCur; - T* m_pAbove; - T m_lineSum; - int m_width; - int m_height; + DECLARE_NON_COPYABLE(IntegralImage) + + public: + IntegralImage(int width, int height); + + explicit IntegralImage(const QSize& size); + + ~IntegralImage(); + + /** + * \brief To be called before pushing new row data. + */ + void beginRow(); + + /** + * \brief Push a single value to the integral image. + * + * Values must be pushed row by row, starting from row 0, and from + * column to column within each row, starting from column 0. + * At the beginning of a row, a call to beginRow() must be made. + * + * \note Pushing more than width * height values results in undefined + * behavior. + */ + void push(T val); + + /** + * \brief Calculate the sum of values in the given rectangle. + * + * \note If the rectangle exceeds the image area, the behaviour is + * undefined. + */ + T sum(const QRect& rect) const; + + private: + void init(int width, int height); + + T* m_data; + T* m_cur; + T* m_above; + T m_lineSum; + int m_width; + int m_height; }; -template +template IntegralImage::IntegralImage(const int width, const int height) - : m_lineSum() { // init with 0 or with default constructor. - // The first row and column are fake. - init(width + 1, height + 1); + : m_lineSum() { // init with 0 or with default constructor. + // The first row and column are fake. + init(width + 1, height + 1); } -template +template IntegralImage::IntegralImage(const QSize& size) : m_lineSum() { // init with 0 or with default constructor. - // The first row and column are fake. - init(size.width() + 1, size.height() + 1); + // The first row and column are fake. + init(size.width() + 1, size.height() + 1); } -template +template IntegralImage::~IntegralImage() { - delete[] m_pData; + delete[] m_data; } -template +template void IntegralImage::init(const int width, const int height) { - m_width = width; - m_height = height; + m_width = width; + m_height = height; - m_pData = new T[width * height]; + m_data = new T[width * height]; - // Initialize the first (fake) row. - // As for the fake column, we initialize its elements in beginRow(). - T* p = m_pData; - for (int i = 0; i < width; ++i, ++p) { - *p = T(); - } + // Initialize the first (fake) row. + // As for the fake column, we initialize its elements in beginRow(). + T* p = m_data; + for (int i = 0; i < width; ++i, ++p) { + *p = T(); + } - m_pAbove = m_pData; - m_pCur = m_pAbove + width; // Skip the first row. + m_above = m_data; + m_cur = m_above + width; // Skip the first row. } -template +template void IntegralImage::push(const T val) { - m_lineSum += val; - *m_pCur = *m_pAbove + m_lineSum; - ++m_pCur; - ++m_pAbove; + m_lineSum += val; + *m_cur = *m_above + m_lineSum; + ++m_cur; + ++m_above; } -template +template void IntegralImage::beginRow() { - m_lineSum = T(); + m_lineSum = T(); - // Initialize and skip the fake column. - *m_pCur = T(); - ++m_pCur; - ++m_pAbove; + // Initialize and skip the fake column. + *m_cur = T(); + ++m_cur; + ++m_above; } -template +template inline T IntegralImage::sum(const QRect& rect) const { - // Keep in mind that row 0 and column 0 are fake. - const int pre_left = rect.left(); - const int pre_right = rect.right() + 1; // QRect::right() is inclusive. - const int pre_top = rect.top(); - const int pre_bottom = rect.bottom() + 1; // QRect::bottom() is inclusive. - T sum(m_pData[pre_bottom * m_width + pre_right]); - sum -= m_pData[pre_top * m_width + pre_right]; - sum += m_pData[pre_top * m_width + pre_left]; - sum -= m_pData[pre_bottom * m_width + pre_left]; - - return sum; + // Keep in mind that row 0 and column 0 are fake. + const int pre_left = rect.left(); + const int pre_right = rect.right() + 1; // QRect::right() is inclusive. + const int pre_top = rect.top(); + const int pre_bottom = rect.bottom() + 1; // QRect::bottom() is inclusive. + T sum(m_data[pre_bottom * m_width + pre_right]); + sum -= m_data[pre_top * m_width + pre_right]; + sum += m_data[pre_top * m_width + pre_left]; + sum -= m_data[pre_bottom * m_width + pre_left]; + + return sum; } } // namespace imageproc #endif // ifndef IMAGEPROC_INTEGRALIMAGE_H_ diff --git a/imageproc/LocalMinMaxGeneric.h b/imageproc/LocalMinMaxGeneric.h index c85e04a72..89c1b44a1 100644 --- a/imageproc/LocalMinMaxGeneric.h +++ b/imageproc/LocalMinMaxGeneric.h @@ -19,16 +19,16 @@ #ifndef IMAGEPROC_LOCAL_MIN_MAX_GENERIC_H_ #define IMAGEPROC_LOCAL_MIN_MAX_GENERIC_H_ -#include #include -#include +#include #include #include +#include namespace imageproc { namespace detail { namespace local_min_max { -template +template void fillAccumulator(MinMaxSelector selector, int todo_before, int todo_within, @@ -38,42 +38,42 @@ void fillAccumulator(MinMaxSelector selector, const int src_delta, T* dst, const int dst_delta) { - T extremum(outside_values); - - if ((todo_before <= 0) && (todo_within > 0)) { - extremum = *src; - } - - while (todo_before-- > 0) { - *dst = extremum; - dst += dst_delta; - src += src_delta; - } - - while (todo_within-- > 0) { - extremum = selector(extremum, *src); - *dst = extremum; - src += src_delta; - dst += dst_delta; - } - - if (todo_after > 0) { - extremum = selector(extremum, outside_values); - do { - *dst = extremum; - dst += dst_delta; - } while (--todo_after > 0); - } + T extremum(outside_values); + + if ((todo_before <= 0) && (todo_within > 0)) { + extremum = *src; + } + + while (todo_before-- > 0) { + *dst = extremum; + dst += dst_delta; + src += src_delta; + } + + while (todo_within-- > 0) { + extremum = selector(extremum, *src); + *dst = extremum; + src += src_delta; + dst += dst_delta; + } + + if (todo_after > 0) { + extremum = selector(extremum, outside_values); + do { + *dst = extremum; + dst += dst_delta; + } while (--todo_after > 0); + } } -template +template void fillWithConstant(T* from, T* to, T constant) { - for (++to; from != to; ++from) { - *from = constant; - } + for (++to; from != to; ++from) { + *from = constant; + } } -template +template void horizontalPass(MinMaxSelector selector, const QRect neighborhood, const T outside_values, @@ -82,74 +82,74 @@ void horizontalPass(MinMaxSelector selector, const QSize input_size, T* output, const int output_stride) { - const int se_len = neighborhood.width(); - const int width = input_size.width(); - const int width_m1 = width - 1; - const int height = input_size.height(); - const int dx1 = neighborhood.left(); - const int dx2 = neighborhood.right(); - - std::vector accum(se_len * 2 - 1); - T* const accum_middle = &accum[se_len - 1]; - - for (int y = 0; y < height; ++y) { - for (int dst_segment_first = 0; dst_segment_first < width; dst_segment_first += se_len) { - const int dst_segment_last = std::min(dst_segment_first + se_len, width) - 1; // inclusive - const int src_segment_first = dst_segment_first + dx1; - const int src_segment_last = dst_segment_last + dx2; - const int src_segment_middle = (src_segment_first + src_segment_last) >> 1; - // Fill the first half of the accumulator buffer. - if ((src_segment_first > width_m1) || (src_segment_middle < 0)) { - // This half is completely outside the image. - // Note that the branch below can't deal with such a case. - fillWithConstant(&accum.front(), accum_middle, outside_values); - } else { - // after <- [to <- within <- from] <- before - const int from = std::min(width_m1, src_segment_middle); - const int to = std::max(0, src_segment_first); - - const int todo_before = src_segment_middle - from; - const int todo_within = from - to + 1; - const int todo_after = to - src_segment_first; - const int src_delta = -1; - const int dst_delta = -1; - - fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, - input + src_segment_middle, src_delta, accum_middle, dst_delta); - } - // Fill the second half of the accumulator buffer. - if ((src_segment_last < 0) || (src_segment_middle > width_m1)) { - // This half is completely outside the image. - // Note that the branch below can't deal with such a case. - fillWithConstant(accum_middle, &accum.back(), outside_values); - } else { - // before -> [from -> within -> to] -> after - const int from = std::max(0, src_segment_middle); - const int to = std::min(width_m1, src_segment_last); - - const int todo_before = from - src_segment_middle; - const int todo_within = to - from + 1; - const int todo_after = src_segment_last - to; - const int src_delta = 1; - const int dst_delta = 1; - - fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, - input + src_segment_middle, src_delta, accum_middle, dst_delta); - } - - const int offset1 = dx1 - src_segment_middle; - const int offset2 = dx2 - src_segment_middle; - for (int x = dst_segment_first; x <= dst_segment_last; ++x) { - output[x] = selector(accum_middle[x + offset1], accum_middle[x + offset2]); - } - } - - input += input_stride; - output += output_stride; + const int se_len = neighborhood.width(); + const int width = input_size.width(); + const int width_m1 = width - 1; + const int height = input_size.height(); + const int dx1 = neighborhood.left(); + const int dx2 = neighborhood.right(); + + std::vector accum(se_len * 2 - 1); + T* const accum_middle = &accum[se_len - 1]; + + for (int y = 0; y < height; ++y) { + for (int dst_segment_first = 0; dst_segment_first < width; dst_segment_first += se_len) { + const int dst_segment_last = std::min(dst_segment_first + se_len, width) - 1; // inclusive + const int src_segment_first = dst_segment_first + dx1; + const int src_segment_last = dst_segment_last + dx2; + const int src_segment_middle = (src_segment_first + src_segment_last) >> 1; + // Fill the first half of the accumulator buffer. + if ((src_segment_first > width_m1) || (src_segment_middle < 0)) { + // This half is completely outside the image. + // Note that the branch below can't deal with such a case. + fillWithConstant(&accum.front(), accum_middle, outside_values); + } else { + // after <- [to <- within <- from] <- before + const int from = std::min(width_m1, src_segment_middle); + const int to = std::max(0, src_segment_first); + + const int todo_before = src_segment_middle - from; + const int todo_within = from - to + 1; + const int todo_after = to - src_segment_first; + const int src_delta = -1; + const int dst_delta = -1; + + fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, input + src_segment_middle, + src_delta, accum_middle, dst_delta); + } + // Fill the second half of the accumulator buffer. + if ((src_segment_last < 0) || (src_segment_middle > width_m1)) { + // This half is completely outside the image. + // Note that the branch below can't deal with such a case. + fillWithConstant(accum_middle, &accum.back(), outside_values); + } else { + // before -> [from -> within -> to] -> after + const int from = std::max(0, src_segment_middle); + const int to = std::min(width_m1, src_segment_last); + + const int todo_before = from - src_segment_middle; + const int todo_within = to - from + 1; + const int todo_after = src_segment_last - to; + const int src_delta = 1; + const int dst_delta = 1; + + fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, input + src_segment_middle, + src_delta, accum_middle, dst_delta); + } + + const int offset1 = dx1 - src_segment_middle; + const int offset2 = dx2 - src_segment_middle; + for (int x = dst_segment_first; x <= dst_segment_last; ++x) { + output[x] = selector(accum_middle[x + offset1], accum_middle[x + offset2]); + } } + + input += input_stride; + output += output_stride; + } } // horizontalPass -template +template void verticalPass(MinMaxSelector selector, const QRect neighborhood, const T outside_values, @@ -158,73 +158,73 @@ void verticalPass(MinMaxSelector selector, const QSize input_size, T* output, const int output_stride) { - const int se_len = neighborhood.height(); - const int width = input_size.width(); - const int height = input_size.height(); - const int height_m1 = height - 1; - const int dy1 = neighborhood.top(); - const int dy2 = neighborhood.bottom(); - - std::vector accum(se_len * 2 - 1); - T* const accum_middle = &accum[se_len - 1]; - - for (int x = 0; x < width; ++x) { - for (int dst_segment_first = 0; dst_segment_first < height; dst_segment_first += se_len) { - const int dst_segment_last = std::min(dst_segment_first + se_len, height) - 1; // inclusive - const int src_segment_first = dst_segment_first + dy1; - const int src_segment_last = dst_segment_last + dy2; - const int src_segment_middle = (src_segment_first + src_segment_last) >> 1; - // Fill the first half of accumulator buffer. - if ((src_segment_first > height_m1) || (src_segment_middle < 0)) { - // This half is completely outside the image. - // Note that the branch below can't deal with such a case. - fillWithConstant(&accum.front(), accum_middle, outside_values); - } else { - // after <- [to <- within <- from] <- before - const int from = std::min(height_m1, src_segment_middle); - const int to = std::max(0, src_segment_first); - - const int todo_before = src_segment_middle - from; - const int todo_within = from - to + 1; - const int todo_after = to - src_segment_first; - const int src_delta = -input_stride; - const int dst_delta = -1; - - fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, - input + src_segment_middle * input_stride, src_delta, accum_middle, dst_delta); - } - // Fill the second half of accumulator buffer. - if ((src_segment_last < 0) || (src_segment_middle > height_m1)) { - // This half is completely outside the image. - // Note that the branch below can't deal with such a case. - fillWithConstant(accum_middle, &accum.back(), outside_values); - } else { - // before -> [from -> within -> to] -> after - const int from = std::max(0, src_segment_middle); - const int to = std::min(height_m1, src_segment_last); - - const int todo_before = from - src_segment_middle; - const int todo_within = to - from + 1; - const int todo_after = src_segment_last - to; - const int src_delta = input_stride; - const int dst_delta = 1; - - fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, - input + src_segment_middle * input_stride, src_delta, accum_middle, dst_delta); - } - - const int offset1 = dy1 - src_segment_middle; - const int offset2 = dy2 - src_segment_middle; - T* p_out = output + dst_segment_first * output_stride; - for (int y = dst_segment_first; y <= dst_segment_last; ++y) { - *p_out = selector(accum_middle[y + offset1], accum_middle[y + offset2]); - p_out += output_stride; - } - } - - ++input; - ++output; + const int se_len = neighborhood.height(); + const int width = input_size.width(); + const int height = input_size.height(); + const int height_m1 = height - 1; + const int dy1 = neighborhood.top(); + const int dy2 = neighborhood.bottom(); + + std::vector accum(se_len * 2 - 1); + T* const accum_middle = &accum[se_len - 1]; + + for (int x = 0; x < width; ++x) { + for (int dst_segment_first = 0; dst_segment_first < height; dst_segment_first += se_len) { + const int dst_segment_last = std::min(dst_segment_first + se_len, height) - 1; // inclusive + const int src_segment_first = dst_segment_first + dy1; + const int src_segment_last = dst_segment_last + dy2; + const int src_segment_middle = (src_segment_first + src_segment_last) >> 1; + // Fill the first half of accumulator buffer. + if ((src_segment_first > height_m1) || (src_segment_middle < 0)) { + // This half is completely outside the image. + // Note that the branch below can't deal with such a case. + fillWithConstant(&accum.front(), accum_middle, outside_values); + } else { + // after <- [to <- within <- from] <- before + const int from = std::min(height_m1, src_segment_middle); + const int to = std::max(0, src_segment_first); + + const int todo_before = src_segment_middle - from; + const int todo_within = from - to + 1; + const int todo_after = to - src_segment_first; + const int src_delta = -input_stride; + const int dst_delta = -1; + + fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, + input + src_segment_middle * input_stride, src_delta, accum_middle, dst_delta); + } + // Fill the second half of accumulator buffer. + if ((src_segment_last < 0) || (src_segment_middle > height_m1)) { + // This half is completely outside the image. + // Note that the branch below can't deal with such a case. + fillWithConstant(accum_middle, &accum.back(), outside_values); + } else { + // before -> [from -> within -> to] -> after + const int from = std::max(0, src_segment_middle); + const int to = std::min(height_m1, src_segment_last); + + const int todo_before = from - src_segment_middle; + const int todo_within = to - from + 1; + const int todo_after = src_segment_last - to; + const int src_delta = input_stride; + const int dst_delta = 1; + + fillAccumulator(selector, todo_before, todo_within, todo_after, outside_values, + input + src_segment_middle * input_stride, src_delta, accum_middle, dst_delta); + } + + const int offset1 = dy1 - src_segment_middle; + const int offset2 = dy2 - src_segment_middle; + T* p_out = output + dst_segment_first * output_stride; + for (int y = dst_segment_first; y <= dst_segment_last; ++y) { + *p_out = selector(accum_middle[y + offset1], accum_middle[y + offset2]); + p_out += output_stride; + } } + + ++input; + ++output; + } } // verticalPass } // namespace local_min_max } // namespace detail @@ -257,7 +257,7 @@ void verticalPass(MinMaxSelector selector, * A good description of this algorithm is available online at: * http://leptonica.com/grayscale-morphology.html#FAST-IMPLEMENTATION */ -template +template void localMinMaxGeneric(MinMaxSelector selector, const QRect neighborhood, const T outside_values, @@ -266,20 +266,20 @@ void localMinMaxGeneric(MinMaxSelector selector, const QSize input_size, T* output, const int output_stride) { - assert(!neighborhood.isEmpty()); + assert(!neighborhood.isEmpty()); - if (input_size.isEmpty()) { - return; - } + if (input_size.isEmpty()) { + return; + } - std::vector temp(input_size.width() * input_size.height()); - const int temp_stride = input_size.width(); + std::vector temp(input_size.width() * input_size.height()); + const int temp_stride = input_size.width(); - detail::local_min_max::horizontalPass(selector, neighborhood, outside_values, input, input_stride, input_size, - &temp[0], temp_stride); + detail::local_min_max::horizontalPass(selector, neighborhood, outside_values, input, input_stride, input_size, + &temp[0], temp_stride); - detail::local_min_max::verticalPass(selector, neighborhood, outside_values, &temp[0], temp_stride, input_size, - output, output_stride); + detail::local_min_max::verticalPass(selector, neighborhood, outside_values, &temp[0], temp_stride, input_size, output, + output_stride); } } // namespace imageproc #endif // ifndef IMAGEPROC_LOCAL_MIN_MAX_GENERIC_H_ diff --git a/imageproc/MaxWhitespaceFinder.cpp b/imageproc/MaxWhitespaceFinder.cpp index 983928467..0c5de66a0 100644 --- a/imageproc/MaxWhitespaceFinder.cpp +++ b/imageproc/MaxWhitespaceFinder.cpp @@ -25,350 +25,349 @@ using namespace max_whitespace_finder; namespace { class AreaCompare { -public: - bool operator()(const QRect lhs, const QRect rhs) const { - const int lhs_area = lhs.width() * lhs.height(); - const int rhs_area = rhs.width() * rhs.height(); + public: + bool operator()(const QRect lhs, const QRect rhs) const { + const int lhs_area = lhs.width() * lhs.height(); + const int rhs_area = rhs.width() * rhs.height(); - return lhs_area < rhs_area; - } + return lhs_area < rhs_area; + } }; } // anonymous namespace MaxWhitespaceFinder::MaxWhitespaceFinder(const BinaryImage& img, QSize min_size) - : m_integralImg(img.size()), - m_ptrQueuedRegions(new PriorityStorageImpl(AreaCompare())), - m_minSize(min_size) { - init(img); + : m_integralImg(img.size()), + m_queuedRegions(new PriorityStorageImpl(AreaCompare())), + m_minSize(min_size) { + init(img); } void MaxWhitespaceFinder::init(const BinaryImage& img) { - const int width = img.width(); - const int height = img.height(); - const uint32_t* line = img.data(); - const int wpl = img.wordsPerLine(); - - for (int y = 0; y < height; ++y, line += wpl) { - m_integralImg.beginRow(); - for (int x = 0; x < width; ++x) { - const int shift = 31 - (x & 31); - m_integralImg.push((line[x >> 5] >> shift) & 1); - } + const int width = img.width(); + const int height = img.height(); + const uint32_t* line = img.data(); + const int wpl = img.wordsPerLine(); + + for (int y = 0; y < height; ++y, line += wpl) { + m_integralImg.beginRow(); + for (int x = 0; x < width; ++x) { + const int shift = 31 - (x & 31); + m_integralImg.push((line[x >> 5] >> shift) & 1); } + } - Region region(0, img.rect()); - m_ptrQueuedRegions->push(region); + Region region(0, img.rect()); + m_queuedRegions->push(region); } void MaxWhitespaceFinder::addObstacle(const QRect& obstacle) { - if (m_ptrQueuedRegions->size() == 1) { - m_ptrQueuedRegions->top().addObstacle(obstacle); - } else { - m_newObstacles.push_back(obstacle); - } + if (m_queuedRegions->size() == 1) { + m_queuedRegions->top().addObstacle(obstacle); + } else { + m_newObstacles.push_back(obstacle); + } } QRect MaxWhitespaceFinder::next(const ObstacleMode obstacle_mode, int max_iterations) { - while (max_iterations-- > 0 && !m_ptrQueuedRegions->empty()) { - Region& top_region = m_ptrQueuedRegions->top(); - Region region(top_region); - region.swapObstacles(top_region); - m_ptrQueuedRegions->pop(); - - region.addNewObstacles(m_newObstacles); + while (max_iterations-- > 0 && !m_queuedRegions->empty()) { + Region& top_region = m_queuedRegions->top(); + Region region(top_region); + region.swapObstacles(top_region); + m_queuedRegions->pop(); - if (!region.obstacles().empty()) { - subdivideUsingObstacles(region); - continue; - } + region.addNewObstacles(m_newObstacles); - if (m_integralImg.sum(region.bounds()) != 0) { - subdivideUsingRaster(region); - continue; - } + if (!region.obstacles().empty()) { + subdivideUsingObstacles(region); + continue; + } - if (obstacle_mode == AUTO_OBSTACLES) { - m_newObstacles.push_back(region.bounds()); - } + if (m_integralImg.sum(region.bounds()) != 0) { + subdivideUsingRaster(region); + continue; + } - return region.bounds(); + if (obstacle_mode == AUTO_OBSTACLES) { + m_newObstacles.push_back(region.bounds()); } - return QRect(); + return region.bounds(); + } + + return QRect(); } void MaxWhitespaceFinder::subdivideUsingObstacles(const Region& region) { - const QRect bounds(region.bounds()); - const QRect pivot_rect(findPivotObstacle(region)); + const QRect bounds(region.bounds()); + const QRect pivot_rect(findPivotObstacle(region)); - subdivide(region, bounds, pivot_rect); + subdivide(region, bounds, pivot_rect); } void MaxWhitespaceFinder::subdivideUsingRaster(const Region& region) { - const QRect bounds(region.bounds()); - const QPoint pivot_pixel(findBlackPixelCloseToCenter(bounds)); - const QRect pivot_rect(extendBlackPixelToBlackBox(pivot_pixel, bounds)); + const QRect bounds(region.bounds()); + const QPoint pivot_pixel(findBlackPixelCloseToCenter(bounds)); + const QRect pivot_rect(extendBlackPixelToBlackBox(pivot_pixel, bounds)); - subdivide(region, bounds, pivot_rect); + subdivide(region, bounds, pivot_rect); } void MaxWhitespaceFinder::subdivide(const Region& region, const QRect bounds, const QRect pivot) { - // Area above the pivot obstacle. - if (pivot.top() - bounds.top() >= m_minSize.height()) { - QRect new_bounds(bounds); - new_bounds.setBottom(pivot.top() - 1); // Bottom is inclusive. - Region new_region(static_cast(m_newObstacles.size()), new_bounds); - new_region.addObstacles(region); - m_ptrQueuedRegions->push(new_region); - } - - // Area below the pivot obstacle. - if (bounds.bottom() - pivot.bottom() >= m_minSize.height()) { - QRect new_bounds(bounds); - new_bounds.setTop(pivot.bottom() + 1); - Region new_region(static_cast(m_newObstacles.size()), new_bounds); - new_region.addObstacles(region); - m_ptrQueuedRegions->push(new_region); - } - - // Area to the left of the pivot obstacle. - if (pivot.left() - bounds.left() >= m_minSize.width()) { - QRect new_bounds(bounds); - new_bounds.setRight(pivot.left() - 1); // Right is inclusive. - Region new_region(static_cast(m_newObstacles.size()), new_bounds); - new_region.addObstacles(region); - m_ptrQueuedRegions->push(new_region); - } - // Area to the right of the pivot obstacle. - if (bounds.right() - pivot.right() >= m_minSize.width()) { - QRect new_bounds(bounds); - new_bounds.setLeft(pivot.right() + 1); - Region new_region(static_cast(m_newObstacles.size()), new_bounds); - new_region.addObstacles(region); - m_ptrQueuedRegions->push(new_region); - } + // Area above the pivot obstacle. + if (pivot.top() - bounds.top() >= m_minSize.height()) { + QRect new_bounds(bounds); + new_bounds.setBottom(pivot.top() - 1); // Bottom is inclusive. + Region new_region(static_cast(m_newObstacles.size()), new_bounds); + new_region.addObstacles(region); + m_queuedRegions->push(new_region); + } + + // Area below the pivot obstacle. + if (bounds.bottom() - pivot.bottom() >= m_minSize.height()) { + QRect new_bounds(bounds); + new_bounds.setTop(pivot.bottom() + 1); + Region new_region(static_cast(m_newObstacles.size()), new_bounds); + new_region.addObstacles(region); + m_queuedRegions->push(new_region); + } + + // Area to the left of the pivot obstacle. + if (pivot.left() - bounds.left() >= m_minSize.width()) { + QRect new_bounds(bounds); + new_bounds.setRight(pivot.left() - 1); // Right is inclusive. + Region new_region(static_cast(m_newObstacles.size()), new_bounds); + new_region.addObstacles(region); + m_queuedRegions->push(new_region); + } + // Area to the right of the pivot obstacle. + if (bounds.right() - pivot.right() >= m_minSize.width()) { + QRect new_bounds(bounds); + new_bounds.setLeft(pivot.right() + 1); + Region new_region(static_cast(m_newObstacles.size()), new_bounds); + new_region.addObstacles(region); + m_queuedRegions->push(new_region); + } } // MaxWhitespaceFinder::subdivide QRect MaxWhitespaceFinder::findPivotObstacle(const Region& region) const { - assert(!region.obstacles().empty()); - - const QPoint center(region.bounds().center()); - - QRect best_obstacle; - int best_distance = std::numeric_limits::max(); - for (const QRect& obstacle : region.obstacles()) { - const QPoint vec(center - obstacle.center()); - const int distance = vec.x() * vec.x() + vec.y() * vec.y(); - if (distance <= best_distance) { - best_obstacle = obstacle; - best_distance = distance; - } + assert(!region.obstacles().empty()); + + const QPoint center(region.bounds().center()); + + QRect best_obstacle; + int best_distance = std::numeric_limits::max(); + for (const QRect& obstacle : region.obstacles()) { + const QPoint vec(center - obstacle.center()); + const int distance = vec.x() * vec.x() + vec.y() * vec.y(); + if (distance <= best_distance) { + best_obstacle = obstacle; + best_distance = distance; } + } - return best_obstacle; + return best_obstacle; } QPoint MaxWhitespaceFinder::findBlackPixelCloseToCenter(const QRect non_white_rect) const { - assert(m_integralImg.sum(non_white_rect) != 0); + assert(m_integralImg.sum(non_white_rect) != 0); + + const QPoint center(non_white_rect.center()); + QRect outer_rect(non_white_rect); + QRect inner_rect(center.x(), center.y(), 1, 1); + + if (m_integralImg.sum(inner_rect) != 0) { + return center; + } + + // We have two rectangles: the outer one, that always contains at least + // one black pixel, and the inner one (contained within the outer one), + // that doesn't contain any black pixels. - const QPoint center(non_white_rect.center()); - QRect outer_rect(non_white_rect); - QRect inner_rect(center.x(), center.y(), 1, 1); + // The first thing we do is bringing those two rectangles as close + // as possible to each other, so that no more than 1 pixel separates + // their corresponding edges. - if (m_integralImg.sum(inner_rect) != 0) { - return center; + while (true) { + const int outer_inner_dw = outer_rect.width() - inner_rect.width(); + const int outer_inner_dh = outer_rect.height() - inner_rect.height(); + + if ((outer_inner_dw <= 1) && (outer_inner_dh <= 1)) { + break; } - // We have two rectangles: the outer one, that always contains at least - // one black pixel, and the inner one (contained within the outer one), - // that doesn't contain any black pixels. - - // The first thing we do is bringing those two rectangles as close - // as possible to each other, so that no more than 1 pixel separates - // their corresponding edges. - - for (;;) { - const int outer_inner_dw = outer_rect.width() - inner_rect.width(); - const int outer_inner_dh = outer_rect.height() - inner_rect.height(); - - if ((outer_inner_dw <= 1) && (outer_inner_dh <= 1)) { - break; - } - - const int delta_left = inner_rect.left() - outer_rect.left(); - const int delta_right = outer_rect.right() - inner_rect.right(); - const int delta_top = inner_rect.top() - outer_rect.top(); - const int delta_bottom = outer_rect.bottom() - inner_rect.bottom(); - - QRect middle_rect(outer_rect.left() + ((delta_left + 1) >> 1), outer_rect.top() + ((delta_top + 1) >> 1), 0, 0); - middle_rect.setRight(outer_rect.right() - (delta_right >> 1)); - middle_rect.setBottom(outer_rect.bottom() - (delta_bottom >> 1)); - assert(outer_rect.contains(middle_rect)); - assert(middle_rect.contains(inner_rect)); - - if (m_integralImg.sum(middle_rect) == 0) { - inner_rect = middle_rect; - } else { - outer_rect = middle_rect; - } + const int delta_left = inner_rect.left() - outer_rect.left(); + const int delta_right = outer_rect.right() - inner_rect.right(); + const int delta_top = inner_rect.top() - outer_rect.top(); + const int delta_bottom = outer_rect.bottom() - inner_rect.bottom(); + + QRect middle_rect(outer_rect.left() + ((delta_left + 1) >> 1), outer_rect.top() + ((delta_top + 1) >> 1), 0, 0); + middle_rect.setRight(outer_rect.right() - (delta_right >> 1)); + middle_rect.setBottom(outer_rect.bottom() - (delta_bottom >> 1)); + assert(outer_rect.contains(middle_rect)); + assert(middle_rect.contains(inner_rect)); + + if (m_integralImg.sum(middle_rect) == 0) { + inner_rect = middle_rect; + } else { + outer_rect = middle_rect; } + } - // Process the left edge. - if (outer_rect.left() != inner_rect.left()) { - QRect rect(outer_rect); - rect.setRight(rect.left()); // Right is inclusive. - const unsigned sum = m_integralImg.sum(rect); - if (outer_rect.height() == 1) { - // This means we are dealing with a horizontal line - // and that we only have to check at most two pixels - // (the endpoints) and that at least one of them - // is definately black and that rect is a 1x1 pixels - // block pointing to the left endpoint. - if (sum != 0) { - return outer_rect.topLeft(); - } else { - return outer_rect.topRight(); - } - } else if (sum != 0) { - return findBlackPixelCloseToCenter(rect); - } + // Process the left edge. + if (outer_rect.left() != inner_rect.left()) { + QRect rect(outer_rect); + rect.setRight(rect.left()); // Right is inclusive. + const unsigned sum = m_integralImg.sum(rect); + if (outer_rect.height() == 1) { + // This means we are dealing with a horizontal line + // and that we only have to check at most two pixels + // (the endpoints) and that at least one of them + // is definately black and that rect is a 1x1 pixels + // block pointing to the left endpoint. + if (sum != 0) { + return outer_rect.topLeft(); + } else { + return outer_rect.topRight(); + } + } else if (sum != 0) { + return findBlackPixelCloseToCenter(rect); } - // Process the right edge. - if (outer_rect.right() != inner_rect.right()) { - QRect rect(outer_rect); - rect.setLeft(rect.right()); // Right is inclusive. - const unsigned sum = m_integralImg.sum(rect); - if (outer_rect.height() == 1) { - // Same as above, except rect now points to the - // right endpoint. - if (sum != 0) { - return outer_rect.topRight(); - } else { - return outer_rect.topLeft(); - } - } else if (sum != 0) { - return findBlackPixelCloseToCenter(rect); - } + } + // Process the right edge. + if (outer_rect.right() != inner_rect.right()) { + QRect rect(outer_rect); + rect.setLeft(rect.right()); // Right is inclusive. + const unsigned sum = m_integralImg.sum(rect); + if (outer_rect.height() == 1) { + // Same as above, except rect now points to the + // right endpoint. + if (sum != 0) { + return outer_rect.topRight(); + } else { + return outer_rect.topLeft(); + } + } else if (sum != 0) { + return findBlackPixelCloseToCenter(rect); } + } - // Process the top edge. - if (outer_rect.top() != inner_rect.top()) { - QRect rect(outer_rect); - rect.setBottom(rect.top()); // Bottom is inclusive. - const unsigned sum = m_integralImg.sum(rect); - if (outer_rect.width() == 1) { - // Same as above, except rect now points to the - // top endpoint. - if (sum != 0) { - return outer_rect.topLeft(); - } else { - return outer_rect.bottomLeft(); - } - } else if (sum != 0) { - return findBlackPixelCloseToCenter(rect); - } - } - // Process the bottom edge. - assert(outer_rect.bottom() != inner_rect.bottom()); + // Process the top edge. + if (outer_rect.top() != inner_rect.top()) { QRect rect(outer_rect); - rect.setTop(rect.bottom()); // Bottom is inclusive. - assert(m_integralImg.sum(rect) != 0); + rect.setBottom(rect.top()); // Bottom is inclusive. + const unsigned sum = m_integralImg.sum(rect); if (outer_rect.width() == 1) { + // Same as above, except rect now points to the + // top endpoint. + if (sum != 0) { + return outer_rect.topLeft(); + } else { return outer_rect.bottomLeft(); - } else { - return findBlackPixelCloseToCenter(rect); + } + } else if (sum != 0) { + return findBlackPixelCloseToCenter(rect); } + } + // Process the bottom edge. + assert(outer_rect.bottom() != inner_rect.bottom()); + QRect rect(outer_rect); + rect.setTop(rect.bottom()); // Bottom is inclusive. + assert(m_integralImg.sum(rect) != 0); + if (outer_rect.width() == 1) { + return outer_rect.bottomLeft(); + } else { + return findBlackPixelCloseToCenter(rect); + } } // MaxWhitespaceFinder::findBlackPixelCloseToCenter QRect MaxWhitespaceFinder::extendBlackPixelToBlackBox(const QPoint pixel, const QRect bounds) const { - assert(bounds.contains(pixel)); + assert(bounds.contains(pixel)); - QRect outer_rect(bounds); - QRect inner_rect(pixel.x(), pixel.y(), 1, 1); + QRect outer_rect(bounds); + QRect inner_rect(pixel.x(), pixel.y(), 1, 1); - if (m_integralImg.sum(outer_rect) == unsigned(outer_rect.width() * outer_rect.height())) { - return outer_rect; + if (m_integralImg.sum(outer_rect) == unsigned(outer_rect.width() * outer_rect.height())) { + return outer_rect; + } + + // We have two rectangles: the outer one, that always contains at least + // one white pixel, and the inner one (contained within the outer one), + // that doesn't. + + // We will be bringing those two rectangles as close as possible to + // each other, so that no more than 1 pixel separates their + // corresponding edges. + + while (true) { + const int outer_inner_dw = outer_rect.width() - inner_rect.width(); + const int outer_inner_dh = outer_rect.height() - inner_rect.height(); + + if ((outer_inner_dw <= 1) && (outer_inner_dh <= 1)) { + break; } - // We have two rectangles: the outer one, that always contains at least - // one white pixel, and the inner one (contained within the outer one), - // that doesn't. - - // We will be bringing those two rectangles as close as possible to - // each other, so that no more than 1 pixel separates their - // corresponding edges. - - for (;;) { - const int outer_inner_dw = outer_rect.width() - inner_rect.width(); - const int outer_inner_dh = outer_rect.height() - inner_rect.height(); - - if ((outer_inner_dw <= 1) && (outer_inner_dh <= 1)) { - break; - } - - const int delta_left = inner_rect.left() - outer_rect.left(); - const int delta_right = outer_rect.right() - inner_rect.right(); - const int delta_top = inner_rect.top() - outer_rect.top(); - const int delta_bottom = outer_rect.bottom() - inner_rect.bottom(); - - QRect middle_rect(outer_rect.left() + ((delta_left + 1) >> 1), outer_rect.top() + ((delta_top + 1) >> 1), 0, 0); - middle_rect.setRight(outer_rect.right() - (delta_right >> 1)); - middle_rect.setBottom(outer_rect.bottom() - (delta_bottom >> 1)); - assert(outer_rect.contains(middle_rect)); - assert(middle_rect.contains(inner_rect)); - - const unsigned area = middle_rect.width() * middle_rect.height(); - if (m_integralImg.sum(middle_rect) == area) { - inner_rect = middle_rect; - } else { - outer_rect = middle_rect; - } + const int delta_left = inner_rect.left() - outer_rect.left(); + const int delta_right = outer_rect.right() - inner_rect.right(); + const int delta_top = inner_rect.top() - outer_rect.top(); + const int delta_bottom = outer_rect.bottom() - inner_rect.bottom(); + + QRect middle_rect(outer_rect.left() + ((delta_left + 1) >> 1), outer_rect.top() + ((delta_top + 1) >> 1), 0, 0); + middle_rect.setRight(outer_rect.right() - (delta_right >> 1)); + middle_rect.setBottom(outer_rect.bottom() - (delta_bottom >> 1)); + assert(outer_rect.contains(middle_rect)); + assert(middle_rect.contains(inner_rect)); + + const unsigned area = middle_rect.width() * middle_rect.height(); + if (m_integralImg.sum(middle_rect) == area) { + inner_rect = middle_rect; + } else { + outer_rect = middle_rect; } + } - return inner_rect; + return inner_rect; } // MaxWhitespaceFinder::extendBlackPixelToBlackBox /*======================= MaxWhitespaceFinder::Region =====================*/ MaxWhitespaceFinder::Region::Region(unsigned known_new_obstacles, const QRect& bounds) - : m_knownNewObstacles(known_new_obstacles), m_bounds(bounds) { -} + : m_knownNewObstacles(known_new_obstacles), m_bounds(bounds) {} MaxWhitespaceFinder::Region::Region(const Region& other) - : m_knownNewObstacles(other.m_knownNewObstacles), m_bounds(other.m_bounds) { - // Note that we don't copy m_obstacles. This is a shallow copy. + : m_knownNewObstacles(other.m_knownNewObstacles), m_bounds(other.m_bounds) { + // Note that we don't copy m_obstacles. This is a shallow copy. } /** * Adds obstacles from another region that intersect this region's area. */ void MaxWhitespaceFinder::Region::addObstacles(const Region& other_region) { - for (const QRect& obstacle : other_region.obstacles()) { - const QRect intersected(obstacle.intersected(m_bounds)); - if (!intersected.isEmpty()) { - m_obstacles.push_back(intersected); - } + for (const QRect& obstacle : other_region.obstacles()) { + const QRect intersected(obstacle.intersected(m_bounds)); + if (!intersected.isEmpty()) { + m_obstacles.push_back(intersected); } + } } /** * Adds global obstacles that were not there when this region was constructed. */ void MaxWhitespaceFinder::Region::addNewObstacles(const std::vector& new_obstacles) { - for (size_t i = m_knownNewObstacles; i < new_obstacles.size(); ++i) { - const QRect intersected(new_obstacles[i].intersected(m_bounds)); - if (!intersected.isEmpty()) { - m_obstacles.push_back(intersected); - } + for (size_t i = m_knownNewObstacles; i < new_obstacles.size(); ++i) { + const QRect intersected(new_obstacles[i].intersected(m_bounds)); + if (!intersected.isEmpty()) { + m_obstacles.push_back(intersected); } + } } /** * A fast and non-throwing swap operation. */ void MaxWhitespaceFinder::Region::swap(Region& other) { - std::swap(m_bounds, other.m_bounds); - std::swap(m_knownNewObstacles, other.m_knownNewObstacles); - m_obstacles.swap(other.m_obstacles); + std::swap(m_bounds, other.m_bounds); + std::swap(m_knownNewObstacles, other.m_knownNewObstacles); + m_obstacles.swap(other.m_obstacles); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/MaxWhitespaceFinder.h b/imageproc/MaxWhitespaceFinder.h index 214e7016e..a295bffe9 100644 --- a/imageproc/MaxWhitespaceFinder.h +++ b/imageproc/MaxWhitespaceFinder.h @@ -19,15 +19,15 @@ #ifndef IMAGEPROC_MAX_WHITESPACE_FINDER_H_ #define IMAGEPROC_MAX_WHITESPACE_FINDER_H_ -#include "NonCopyable.h" -#include "BinaryImage.h" -#include "IntegralImage.h" #include #include -#include +#include #include #include -#include +#include +#include "BinaryImage.h" +#include "IntegralImage.h" +#include "NonCopyable.h" namespace imageproc { class BinaryImage; @@ -40,295 +40,277 @@ class PriorityStorage; * \brief Finds white rectangles in a binary image starting from the largest ones. */ class MaxWhitespaceFinder { - DECLARE_NON_COPYABLE(MaxWhitespaceFinder) - - friend class max_whitespace_finder::PriorityStorage; - -public: - /** \see next() */ - enum ObstacleMode { AUTO_OBSTACLES, MANUAL_OBSTACLES }; - - /** - * \brief Constructor. - * - * \param img The image to find white regions in. - * \param min_size The minimum dimensions of regions to find. - */ - explicit MaxWhitespaceFinder(const BinaryImage& img, QSize min_size = QSize(1, 1)); - - /** - * \brief Constructor with customized rectangle ordering. - * - * \param comp A functor used to compare the "quality" of - * rectangles. It will be called like this:\n - * \code - * QRect lhs, rhs; - * if (comp(lhs, rhs)) { - * // lhs is of less quality than rhs. - * } - * \endcode - * The comparison functor must comply with the following - * restriction: - * \code - * QRect rect, subrect; - * if (rect.contains(subrect)) { - * assert(comp(rect, subrect) == false); - * } - * \endcode - * That is, if one rectangle contains or is equal to another, - * it can't have lesser quality. - * - * \param img The image to find white rectangles in. - * \param min_size The minimum dimensions of regions to find. - */ - template - MaxWhitespaceFinder(QualityCompare comp, const BinaryImage& img, QSize min_size = QSize(1, 1)); + DECLARE_NON_COPYABLE(MaxWhitespaceFinder) + + friend class max_whitespace_finder::PriorityStorage; + + public: + /** \see next() */ + enum ObstacleMode { AUTO_OBSTACLES, MANUAL_OBSTACLES }; + + /** + * \brief Constructor. + * + * \param img The image to find white regions in. + * \param min_size The minimum dimensions of regions to find. + */ + explicit MaxWhitespaceFinder(const BinaryImage& img, QSize min_size = QSize(1, 1)); + + /** + * \brief Constructor with customized rectangle ordering. + * + * \param comp A functor used to compare the "quality" of + * rectangles. It will be called like this:\n + * \code + * QRect lhs, rhs; + * if (comp(lhs, rhs)) { + * // lhs is of less quality than rhs. + * } + * \endcode + * The comparison functor must comply with the following + * restriction: + * \code + * QRect rect, subrect; + * if (rect.contains(subrect)) { + * assert(comp(rect, subrect) == false); + * } + * \endcode + * That is, if one rectangle contains or is equal to another, + * it can't have lesser quality. + * + * \param img The image to find white rectangles in. + * \param min_size The minimum dimensions of regions to find. + */ + template + MaxWhitespaceFinder(QualityCompare comp, const BinaryImage& img, QSize min_size = QSize(1, 1)); + + /** + * \brief Mark a region as black. + * + * This will prevent further rectangles from covering this region. + * + * \param rect The rectangle to mark as black. It may exceed + * the image area. + */ + void addObstacle(const QRect& obstacle); + + /** + * \brief Find the next white rectangle. + * + * \param obstacle_mode If set to AUTO_OBSTACLES, addObstacle() + * will be called automatically to prevent further rectangles + * from covering this region. If set to MANUAL_OBSTACLES, + * the caller is expected to call addObstacle() himself, + * not necessarily with the same rectangle returned by next(). + * This mode allows finding partially overlapping rectangles + * (by adding reduced obstacles). There is no strict + * requirement to manually add an obstacle after calling + * this function with MANUAL_OBSTACLES. + * \param max_iterations The maximum number of iterations to spend + * searching for the next maximal white rectangle. + * Reaching this limit without finding one will cause + * a null rectangle to be returned. You generally don't + * want to set this limit MAX_INT or similar, because + * some patterns (a pixel by pixel checkboard pattern for example) + * will take prohibitively long time to process. + * \return A white rectangle, or a null rectangle, if no white + * rectangles confirming to the minimum size were found. + */ + QRect next(ObstacleMode obstacle_mode = AUTO_OBSTACLES, int max_iterations = 1000); + + private: + class Region { + public: + Region(unsigned known_new_obstacles, const QRect& bounds); /** - * \brief Mark a region as black. - * - * This will prevent further rectangles from covering this region. - * - * \param rect The rectangle to mark as black. It may exceed - * the image area. + * A shallow copy. Copies everything except the obstacle list. */ - void addObstacle(const QRect& obstacle); - - /** - * \brief Find the next white rectangle. - * - * \param obstacle_mode If set to AUTO_OBSTACLES, addObstacle() - * will be called automatically to prevent further rectangles - * from covering this region. If set to MANUAL_OBSTACLES, - * the caller is expected to call addObstacle() himself, - * not necessarily with the same rectangle returned by next(). - * This mode allows finding partially overlapping rectangles - * (by adding reduced obstacles). There is no strict - * requirement to manually add an obstacle after calling - * this function with MANUAL_OBSTACLES. - * \param max_iterations The maximum number of iterations to spend - * searching for the next maximal white rectangle. - * Reaching this limit without finding one will cause - * a null rectangle to be returned. You generally don't - * want to set this limit MAX_INT or similar, because - * some patterns (a pixel by pixel checkboard pattern for example) - * will take prohibitively long time to process. - * \return A white rectangle, or a null rectangle, if no white - * rectangles confirming to the minimum size were found. - */ - QRect next(ObstacleMode obstacle_mode = AUTO_OBSTACLES, int max_iterations = 1000); - -private: - class Region { - public: - Region(unsigned known_new_obstacles, const QRect& bounds); + Region(const Region& other); - /** - * A shallow copy. Copies everything except the obstacle list. - */ - Region(const Region& other); + const QRect& bounds() const { return m_bounds; } - const QRect& bounds() const { - return m_bounds; - } + const std::vector& obstacles() const { return m_obstacles; } - const std::vector& obstacles() const { - return m_obstacles; - } + void addObstacle(const QRect& obstacle) { m_obstacles.push_back(obstacle); } - void addObstacle(const QRect& obstacle) { - m_obstacles.push_back(obstacle); - } + void addObstacles(const Region& other_region); - void addObstacles(const Region& other_region); + void addNewObstacles(const std::vector& new_obstacles); - void addNewObstacles(const std::vector& new_obstacles); + void swap(Region& other); - void swap(Region& other); + void swapObstacles(Region& other) { m_obstacles.swap(other.m_obstacles); } - void swapObstacles(Region& other) { - m_obstacles.swap(other.m_obstacles); - } + private: + Region& operator=(const Region&); - private: - Region& operator=(const Region&); + unsigned m_knownNewObstacles; + QRect m_bounds; + std::vector m_obstacles; + }; - unsigned m_knownNewObstacles; - QRect m_bounds; - std::vector m_obstacles; - }; + void init(const BinaryImage& img); - void init(const BinaryImage& img); + void subdivideUsingObstacles(const Region& region); - void subdivideUsingObstacles(const Region& region); + void subdivideUsingRaster(const Region& region); - void subdivideUsingRaster(const Region& region); + void subdivide(const Region& region, QRect bounds, QRect pivot); - void subdivide(const Region& region, QRect bounds, QRect pivot); + QRect findPivotObstacle(const Region& region) const; - QRect findPivotObstacle(const Region& region) const; + QPoint findBlackPixelCloseToCenter(QRect non_white_rect) const; - QPoint findBlackPixelCloseToCenter(QRect non_white_rect) const; + QRect extendBlackPixelToBlackBox(QPoint pixel, QRect bounds) const; - QRect extendBlackPixelToBlackBox(QPoint pixel, QRect bounds) const; - - IntegralImage m_integralImg; - std::unique_ptr m_ptrQueuedRegions; - std::vector m_newObstacles; - QSize m_minSize; + IntegralImage m_integralImg; + std::unique_ptr m_queuedRegions; + std::vector m_newObstacles; + QSize m_minSize; }; namespace max_whitespace_finder { class PriorityStorage { -protected: - typedef MaxWhitespaceFinder::Region Region; + protected: + typedef MaxWhitespaceFinder::Region Region; -public: - virtual ~PriorityStorage() = default; + public: + virtual ~PriorityStorage() = default; - virtual bool empty() const = 0; + virtual bool empty() const = 0; - virtual size_t size() const = 0; + virtual size_t size() const = 0; - virtual Region& top() = 0; + virtual Region& top() = 0; - virtual void push(Region& region) = 0; + virtual void push(Region& region) = 0; - virtual void pop() = 0; + virtual void pop() = 0; }; -template +template class PriorityStorageImpl : public PriorityStorage { -public: - explicit PriorityStorageImpl(QualityCompare comp) : m_qualityLess(comp) { - } + public: + explicit PriorityStorageImpl(QualityCompare comp) : m_qualityLess(comp) {} - bool empty() const override { - return m_priorityQueue.empty(); - } + bool empty() const override { return m_priorityQueue.empty(); } - size_t size() const override { - return m_priorityQueue.size(); - } + size_t size() const override { return m_priorityQueue.size(); } - Region& top() override { - return m_priorityQueue.front(); - } + Region& top() override { return m_priorityQueue.front(); } - void push(Region& region) override; + void push(Region& region) override; - void pop() override; + void pop() override; -private: - class ProxyComparator { - public: - explicit ProxyComparator(QualityCompare delegate) : m_delegate(delegate) { - } + private: + class ProxyComparator { + public: + explicit ProxyComparator(QualityCompare delegate) : m_delegate(delegate) {} - bool operator()(const Region& lhs, const Region& rhs) const { - return m_delegate(lhs.bounds(), rhs.bounds()); - } + bool operator()(const Region& lhs, const Region& rhs) const { return m_delegate(lhs.bounds(), rhs.bounds()); } - private: - QualityCompare m_delegate; - }; + private: + QualityCompare m_delegate; + }; - void pushHeap(std::deque::iterator begin, std::deque::iterator end); + void pushHeap(std::deque::iterator begin, std::deque::iterator end); - void popHeap(std::deque::iterator begin, std::deque::iterator end); + void popHeap(std::deque::iterator begin, std::deque::iterator end); - std::deque m_priorityQueue; - ProxyComparator m_qualityLess; + std::deque m_priorityQueue; + ProxyComparator m_qualityLess; }; -template +template void PriorityStorageImpl::push(Region& region) { - m_priorityQueue.push_back(region); - m_priorityQueue.back().swapObstacles(region); - pushHeap(m_priorityQueue.begin(), m_priorityQueue.end()); + m_priorityQueue.push_back(region); + m_priorityQueue.back().swapObstacles(region); + pushHeap(m_priorityQueue.begin(), m_priorityQueue.end()); } -template +template void PriorityStorageImpl::pop() { - popHeap(m_priorityQueue.begin(), m_priorityQueue.end()); - m_priorityQueue.pop_back(); + popHeap(m_priorityQueue.begin(), m_priorityQueue.end()); + m_priorityQueue.pop_back(); } /** * Same as std::push_heap(), except this one never copies objects, but swap()'s * them instead. We need this to avoid copying the obstacle list over and over. */ -template +template void PriorityStorageImpl::pushHeap(const std::deque::iterator begin, const std::deque::iterator end) { - typedef std::vector::iterator::difference_type Distance; + typedef std::vector::iterator::difference_type Distance; - Distance valueIdx = end - begin - 1; - Distance parentIdx = (valueIdx - 1) / 2; + Distance valueIdx = end - begin - 1; + Distance parentIdx = (valueIdx - 1) / 2; - // While the node is bigger than its parent, swap them. - while (valueIdx > 0 && m_qualityLess(*(begin + parentIdx), *(begin + valueIdx))) { - (begin + valueIdx)->swap(*(begin + parentIdx)); - valueIdx = parentIdx; - parentIdx = (valueIdx - 1) / 2; - } + // While the node is bigger than its parent, swap them. + while (valueIdx > 0 && m_qualityLess(*(begin + parentIdx), *(begin + valueIdx))) { + (begin + valueIdx)->swap(*(begin + parentIdx)); + valueIdx = parentIdx; + parentIdx = (valueIdx - 1) / 2; + } } /** * Same as std::pop_heap(), except this one never copies objects, but swap()'s * them instead. We need this to avoid copying the obstacle list over and over. */ -template +template void PriorityStorageImpl::popHeap(const std::deque::iterator begin, const std::deque::iterator end) { - // Swap the first (top) and the last elements. - begin->swap(*(end - 1)); - - typedef std::vector::iterator::difference_type Distance; - const Distance new_length = end - begin - 1; - Distance nodeIdx = 0; - Distance secondChildIdx = 2 * (nodeIdx + 1); - - // Lower the new top node all the way down the tree - // by continuously swapping it with the biggest of its children. - while (secondChildIdx < new_length) { - const Distance firstChildIdx = secondChildIdx - 1; - Distance biggestChildIdx = firstChildIdx; - - if (m_qualityLess(*(begin + firstChildIdx), *(begin + secondChildIdx))) { - biggestChildIdx = secondChildIdx; - } - - (begin + nodeIdx)->swap(*(begin + biggestChildIdx)); - - nodeIdx = biggestChildIdx; - secondChildIdx = 2 * (nodeIdx + 1); - } - if (secondChildIdx == new_length) { - // Swap it with its only child. - const Distance firstChildIdx = secondChildIdx - 1; - (begin + nodeIdx)->swap(*(begin + firstChildIdx)); - nodeIdx = firstChildIdx; + // Swap the first (top) and the last elements. + begin->swap(*(end - 1)); + + typedef std::vector::iterator::difference_type Distance; + const Distance new_length = end - begin - 1; + Distance nodeIdx = 0; + Distance secondChildIdx = 2 * (nodeIdx + 1); + + // Lower the new top node all the way down the tree + // by continuously swapping it with the biggest of its children. + while (secondChildIdx < new_length) { + const Distance firstChildIdx = secondChildIdx - 1; + Distance biggestChildIdx = firstChildIdx; + + if (m_qualityLess(*(begin + firstChildIdx), *(begin + secondChildIdx))) { + biggestChildIdx = secondChildIdx; } - // Now raise the node until it's at correct position. Very little - // raising should be necessary, that's why it's faster than adding - // an additional comparision to the loop where we lower the node. - pushHeap(begin, begin + nodeIdx + 1); + (begin + nodeIdx)->swap(*(begin + biggestChildIdx)); + + nodeIdx = biggestChildIdx; + secondChildIdx = 2 * (nodeIdx + 1); + } + if (secondChildIdx == new_length) { + // Swap it with its only child. + const Distance firstChildIdx = secondChildIdx - 1; + (begin + nodeIdx)->swap(*(begin + firstChildIdx)); + nodeIdx = firstChildIdx; + } + + // Now raise the node until it's at correct position. Very little + // raising should be necessary, that's why it's faster than adding + // an additional comparision to the loop where we lower the node. + pushHeap(begin, begin + nodeIdx + 1); } // >::popHeap } // namespace max_whitespace_finder -template +template MaxWhitespaceFinder::MaxWhitespaceFinder(const QualityCompare comp, const BinaryImage& img, const QSize min_size) - : m_integralImg(img.size()), - m_ptrQueuedRegions(new max_whitespace_finder::PriorityStorageImpl(comp)), - m_minSize(min_size) { - init(img); + : m_integralImg(img.size()), + m_queuedRegions(new max_whitespace_finder::PriorityStorageImpl(comp)), + m_minSize(min_size) { + init(img); } } // namespace imageproc #endif // ifndef IMAGEPROC_MAX_WHITESPACE_FINDER_H_ diff --git a/imageproc/MorphGradientDetect.cpp b/imageproc/MorphGradientDetect.cpp index ea1b55763..233bbbd1b 100644 --- a/imageproc/MorphGradientDetect.cpp +++ b/imageproc/MorphGradientDetect.cpp @@ -17,22 +17,22 @@ */ #include "MorphGradientDetect.h" -#include "Morphology.h" -#include "Grayscale.h" #include "GrayRasterOp.h" +#include "Grayscale.h" +#include "Morphology.h" namespace imageproc { GrayImage morphGradientDetectDarkSide(const GrayImage& image, const QSize& area) { - GrayImage lighter(erodeGray(image, area, 0x00)); - grayRasterOp>(lighter, image); + GrayImage lighter(erodeGray(image, area, 0x00)); + grayRasterOp>(lighter, image); - return lighter; + return lighter; } GrayImage morphGradientDetectLightSide(const GrayImage& image, const QSize& area) { - GrayImage darker(dilateGray(image, area, 0xff)); - grayRasterOp>(darker, image); + GrayImage darker(dilateGray(image, area, 0xff)); + grayRasterOp>(darker, image); - return darker; + return darker; } } // namespace imageproc diff --git a/imageproc/Morphology.cpp b/imageproc/Morphology.cpp index 87fec7f95..40e64cd6d 100644 --- a/imageproc/Morphology.cpp +++ b/imageproc/Morphology.cpp @@ -17,155 +17,144 @@ */ #include "Morphology.h" -#include "BinaryImage.h" -#include "GrayImage.h" -#include "RasterOp.h" -#include "Grayscale.h" #include #include #include +#include "BinaryImage.h" +#include "GrayImage.h" +#include "Grayscale.h" +#include "RasterOp.h" namespace imageproc { Brick::Brick(const QSize& size) { - const int x_origin = size.width() >> 1; - const int y_origin = size.height() >> 1; - m_minX = -x_origin; - m_minY = -y_origin; - m_maxX = (size.width() - 1) - x_origin; - m_maxY = (size.height() - 1) - y_origin; + const int x_origin = size.width() >> 1; + const int y_origin = size.height() >> 1; + m_minX = -x_origin; + m_minY = -y_origin; + m_maxX = (size.width() - 1) - x_origin; + m_maxY = (size.height() - 1) - y_origin; } Brick::Brick(const QSize& size, const QPoint& origin) { - const int x_origin = origin.x(); - const int y_origin = origin.y(); - m_minX = -x_origin; - m_minY = -y_origin; - m_maxX = (size.width() - 1) - x_origin; - m_maxY = (size.height() - 1) - y_origin; + const int x_origin = origin.x(); + const int y_origin = origin.y(); + m_minX = -x_origin; + m_minY = -y_origin; + m_maxX = (size.width() - 1) - x_origin; + m_maxY = (size.height() - 1) - y_origin; } -Brick::Brick(int min_x, int min_y, int max_x, int max_y) : m_minX(min_x), m_maxX(max_x), m_minY(min_y), m_maxY(max_y) { -} +Brick::Brick(int min_x, int min_y, int max_x, int max_y) : m_minX(min_x), m_maxX(max_x), m_minY(min_y), m_maxY(max_y) {} void Brick::flip() { - const int new_min_x = -m_maxX; - m_maxX = -m_minX; - m_minX = new_min_x; - const int new_min_y = -m_maxY; - m_maxY = -m_minY; - m_minY = new_min_y; + const int new_min_x = -m_maxX; + m_maxX = -m_minX; + m_minX = new_min_x; + const int new_min_y = -m_maxY; + m_maxY = -m_minY; + m_minY = new_min_y; } Brick Brick::flipped() const { - Brick brick(*this); - brick.flip(); + Brick brick(*this); + brick.flip(); - return brick; + return brick; } namespace { class ReusableImages { -public: - void store(BinaryImage& img); + public: + void store(BinaryImage& img); - BinaryImage retrieveOrCreate(const QSize& size); + BinaryImage retrieveOrCreate(const QSize& size); -private: - std::vector m_images; + private: + std::vector m_images; }; void ReusableImages::store(BinaryImage& img) { - assert(!img.isNull()); + assert(!img.isNull()); - // Using push_back(null_image) then swap() avoids atomic operations - // inside BinaryImage. - m_images.emplace_back(); - m_images.back().swap(img); + // Using push_back(null_image) then swap() avoids atomic operations + // inside BinaryImage. + m_images.emplace_back(); + m_images.back().swap(img); } BinaryImage ReusableImages::retrieveOrCreate(const QSize& size) { - if (m_images.empty()) { - return BinaryImage(size); - } else { - BinaryImage img; - m_images.back().swap(img); - m_images.pop_back(); + if (m_images.empty()) { + return BinaryImage(size); + } else { + BinaryImage img; + m_images.back().swap(img); + m_images.pop_back(); - return img; - } + return img; + } } class CoordinateSystem { -public: - /** - * \brief Constructs a global coordinate system. - */ - CoordinateSystem() : m_origin(0, 0) { - } + public: + /** + * \brief Constructs a global coordinate system. + */ + CoordinateSystem() : m_origin(0, 0) {} - /** - * \brief Constructs a coordinate system relative to the global system. - */ - explicit CoordinateSystem(const QPoint& origin) : m_origin(origin) { - } + /** + * \brief Constructs a coordinate system relative to the global system. + */ + explicit CoordinateSystem(const QPoint& origin) : m_origin(origin) {} - const QPoint& origin() const { - return m_origin; - } + const QPoint& origin() const { return m_origin; } - QRect fromGlobal(const QRect& rect) const { - return rect.translated(-m_origin); - } + QRect fromGlobal(const QRect& rect) const { return rect.translated(-m_origin); } - QRect toGlobal(const QRect& rect) const { - return rect.translated(m_origin); - } + QRect toGlobal(const QRect& rect) const { return rect.translated(m_origin); } - QRect mapTo(const QRect& rect, const CoordinateSystem& target_cs) const { - return rect.translated(m_origin).translated(-target_cs.origin()); - } + QRect mapTo(const QRect& rect, const CoordinateSystem& target_cs) const { + return rect.translated(m_origin).translated(-target_cs.origin()); + } - QPoint offsetTo(const CoordinateSystem& target_cs) const { - return m_origin - target_cs.origin(); - } + QPoint offsetTo(const CoordinateSystem& target_cs) const { return m_origin - target_cs.origin(); } -private: - QPoint m_origin; + private: + QPoint m_origin; }; void adjustToFit(const QRect& fit_into, QRect& fit_and_adjust, QRect& adjust_only) { - int adj_left = fit_into.left() - fit_and_adjust.left(); - if (adj_left < 0) { - adj_left = 0; - } - - int adj_right = fit_into.right() - fit_and_adjust.right(); - if (adj_right > 0) { - adj_right = 0; - } - - int adj_top = fit_into.top() - fit_and_adjust.top(); - if (adj_top < 0) { - adj_top = 0; - } - - int adj_bottom = fit_into.bottom() - fit_and_adjust.bottom(); - if (adj_bottom > 0) { - adj_bottom = 0; - } - - fit_and_adjust.adjust(adj_left, adj_top, adj_right, adj_bottom); - adjust_only.adjust(adj_left, adj_top, adj_right, adj_bottom); + int adj_left = fit_into.left() - fit_and_adjust.left(); + if (adj_left < 0) { + adj_left = 0; + } + + int adj_right = fit_into.right() - fit_and_adjust.right(); + if (adj_right > 0) { + adj_right = 0; + } + + int adj_top = fit_into.top() - fit_and_adjust.top(); + if (adj_top < 0) { + adj_top = 0; + } + + int adj_bottom = fit_into.bottom() - fit_and_adjust.bottom(); + if (adj_bottom > 0) { + adj_bottom = 0; + } + + fit_and_adjust.adjust(adj_left, adj_top, adj_right, adj_bottom); + adjust_only.adjust(adj_left, adj_top, adj_right, adj_bottom); } inline QRect extendByBrick(const QRect& rect, const Brick& brick) { - return rect.adjusted(brick.minX(), brick.minY(), brick.maxX(), brick.maxY()); + return rect.adjusted(brick.minX(), brick.minY(), brick.maxX(), brick.maxY()); } inline QRect shrinkByBrick(const QRect& rect, const Brick& brick) { - return rect.adjusted(brick.maxX(), brick.maxY(), brick.minX(), brick.minY()); + return rect.adjusted(brick.maxX(), brick.maxY(), brick.minX(), brick.minY()); } const int COMPOSITE_THRESHOLD = 8; @@ -178,14 +167,14 @@ void doInitialCopy(BinaryImage& dst, const BWColor initial_color, const int dx, const int dy) { - QRect src_rect(src.rect()); - QRect dst_rect(src_cs.mapTo(src_rect, dst_cs)); - dst_rect.translate(dx, dy); - adjustToFit(dst_relevant_rect, dst_rect, src_rect); + QRect src_rect(src.rect()); + QRect dst_rect(src_cs.mapTo(src_rect, dst_cs)); + dst_rect.translate(dx, dy); + adjustToFit(dst_relevant_rect, dst_rect, src_rect); - rasterOp(dst, dst_rect, src, src_rect.topLeft()); + rasterOp(dst, dst_rect, src, src_rect.topLeft()); - dst.fillFrame(dst_relevant_rect, dst_rect, initial_color); + dst.fillFrame(dst_relevant_rect, dst_rect, initial_color); } void spreadInto(BinaryImage& dst, @@ -199,19 +188,19 @@ void spreadInto(BinaryImage& dst, const int dy_step, const int num_steps, const AbstractRasterOp& rop) { - assert(dx_step == 0 || dy_step == 0); + assert(dx_step == 0 || dy_step == 0); - int dx = dx_min; - int dy = dy_min; - for (int i = 0; i < num_steps; ++i, dx += dx_step, dy += dy_step) { - QRect src_rect(src.rect()); - QRect dst_rect(src_cs.mapTo(src_rect, dst_cs)); - dst_rect.translate(dx, dy); + int dx = dx_min; + int dy = dy_min; + for (int i = 0; i < num_steps; ++i, dx += dx_step, dy += dy_step) { + QRect src_rect(src.rect()); + QRect dst_rect(src_cs.mapTo(src_rect, dst_cs)); + dst_rect.translate(dx, dy); - adjustToFit(dst_relevant_rect, dst_rect, src_rect); + adjustToFit(dst_relevant_rect, dst_rect, src_rect); - rop(dst, dst_rect, src, src_rect.topLeft()); - } + rop(dst, dst_rect, src, src_rect.topLeft()); + } } void spreadInDirectionLow(BinaryImage& dst, @@ -227,45 +216,45 @@ void spreadInDirectionLow(BinaryImage& dst, const AbstractRasterOp& rop, const BWColor initial_color, const bool dst_composition_allowed) { - assert(dx_step == 0 || dy_step == 0); + assert(dx_step == 0 || dy_step == 0); - if (num_steps == 0) { - return; - } + if (num_steps == 0) { + return; + } - doInitialCopy(dst, dst_cs, dst_relevant_rect, src, src_cs, initial_color, dx_min, dy_min); + doInitialCopy(dst, dst_cs, dst_relevant_rect, src, src_cs, initial_color, dx_min, dy_min); - if (num_steps == 1) { - return; - } + if (num_steps == 1) { + return; + } - int remaining_dx_min = dx_min + dx_step; - int remaining_dy_min = dy_min + dy_step; - int remaining_steps = num_steps - 1; + int remaining_dx_min = dx_min + dx_step; + int remaining_dy_min = dy_min + dy_step; + int remaining_steps = num_steps - 1; - if (dst_composition_allowed) { - int dx = dx_step; - int dy = dy_step; - int i = 1; - for (; (i << 1) <= num_steps; i <<= 1, dx <<= 1, dy <<= 1) { - QRect dst_rect(dst.rect()); - QRect src_rect(dst_rect); - dst_rect.translate(dx, dy); + if (dst_composition_allowed) { + int dx = dx_step; + int dy = dy_step; + int i = 1; + for (; (i << 1) <= num_steps; i <<= 1, dx <<= 1, dy <<= 1) { + QRect dst_rect(dst.rect()); + QRect src_rect(dst_rect); + dst_rect.translate(dx, dy); - adjustToFit(dst_relevant_rect, dst_rect, src_rect); + adjustToFit(dst_relevant_rect, dst_rect, src_rect); - rop(dst, dst_rect, dst, src_rect.topLeft()); - } - - remaining_dx_min = dx_min + dx; - remaining_dy_min = dy_min + dy; - remaining_steps = num_steps - i; + rop(dst, dst_rect, dst, src_rect.topLeft()); } - if (remaining_steps > 0) { - spreadInto(dst, dst_cs, dst_relevant_rect, src, src_cs, remaining_dx_min, dx_step, remaining_dy_min, dy_step, - remaining_steps, rop); - } + remaining_dx_min = dx_min + dx; + remaining_dy_min = dy_min + dy; + remaining_steps = num_steps - i; + } + + if (remaining_steps > 0) { + spreadInto(dst, dst_cs, dst_relevant_rect, src, src_cs, remaining_dx_min, dx_step, remaining_dy_min, dy_step, + remaining_steps, rop); + } } // spreadInDirectionLow void spreadInDirection(BinaryImage& dst, @@ -284,45 +273,45 @@ void spreadInDirection(BinaryImage& dst, const AbstractRasterOp& rop, const BWColor initial_color, const bool dst_composition_allowed) { - assert(dx_step == 0 || dy_step == 0); + assert(dx_step == 0 || dy_step == 0); - if (num_steps < COMPOSITE_THRESHOLD) { - spreadInDirectionLow(dst, dst_cs, dst_relevant_rect, src, src_cs, dx_min, dx_step, dy_min, dy_step, num_steps, - rop, initial_color, dst_composition_allowed); + if (num_steps < COMPOSITE_THRESHOLD) { + spreadInDirectionLow(dst, dst_cs, dst_relevant_rect, src, src_cs, dx_min, dx_step, dy_min, dy_step, num_steps, rop, + initial_color, dst_composition_allowed); - return; - } + return; + } - const auto first_phase_steps = (int) std::sqrt((double) num_steps); + const auto first_phase_steps = (int) std::sqrt((double) num_steps); - BinaryImage tmp(tmp_images.retrieveOrCreate(tmp_image_size)); + BinaryImage tmp(tmp_images.retrieveOrCreate(tmp_image_size)); - spreadInDirection(tmp, tmp_cs, tmp.rect(), src, src_cs, tmp_images, tmp_cs, tmp_image_size, dx_min, dx_step, dy_min, - dy_step, first_phase_steps, rop, initial_color, true); + spreadInDirection(tmp, tmp_cs, tmp.rect(), src, src_cs, tmp_images, tmp_cs, tmp_image_size, dx_min, dx_step, dy_min, + dy_step, first_phase_steps, rop, initial_color, true); - const int second_phase_steps = num_steps / first_phase_steps; + const int second_phase_steps = num_steps / first_phase_steps; - spreadInDirection(dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, tmp_images, tmp_cs, tmp_image_size, 0, - dx_step * first_phase_steps, 0, dy_step * first_phase_steps, second_phase_steps, rop, - initial_color, dst_composition_allowed); + spreadInDirection(dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, tmp_images, tmp_cs, tmp_image_size, 0, + dx_step * first_phase_steps, 0, dy_step * first_phase_steps, second_phase_steps, rop, initial_color, + dst_composition_allowed); - const int steps_done = first_phase_steps * second_phase_steps; - const int steps_remaining = num_steps - steps_done; + const int steps_done = first_phase_steps * second_phase_steps; + const int steps_remaining = num_steps - steps_done; - if (steps_remaining <= 0) { - assert(steps_remaining == 0); - } else if (steps_remaining < COMPOSITE_THRESHOLD) { - spreadInto(dst, dst_cs, dst_relevant_rect, src, src_cs, dx_min + dx_step * steps_done, dx_step, - dy_min + dy_step * steps_done, dy_step, steps_remaining, rop); - } else { - spreadInDirection(tmp, tmp_cs, tmp.rect(), src, src_cs, tmp_images, tmp_cs, tmp_image_size, - dx_min + dx_step * steps_done, dx_step, dy_min + dy_step * steps_done, dy_step, - steps_remaining, rop, initial_color, true); + if (steps_remaining <= 0) { + assert(steps_remaining == 0); + } else if (steps_remaining < COMPOSITE_THRESHOLD) { + spreadInto(dst, dst_cs, dst_relevant_rect, src, src_cs, dx_min + dx_step * steps_done, dx_step, + dy_min + dy_step * steps_done, dy_step, steps_remaining, rop); + } else { + spreadInDirection(tmp, tmp_cs, tmp.rect(), src, src_cs, tmp_images, tmp_cs, tmp_image_size, + dx_min + dx_step * steps_done, dx_step, dy_min + dy_step * steps_done, dy_step, steps_remaining, + rop, initial_color, true); - spreadInto(dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, 0, 0, 0, 0, 1, rop); - } + spreadInto(dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, 0, 0, 0, 0, 1, rop); + } - tmp_images.store(tmp); + tmp_images.store(tmp); } // spreadInDirection void dilateOrErodeBrick(BinaryImage& dst, @@ -332,168 +321,164 @@ void dilateOrErodeBrick(BinaryImage& dst, const BWColor src_surroundings, const AbstractRasterOp& rop, const BWColor spreading_color) { - assert(!src.isNull()); - assert(!brick.isEmpty()); - assert(!dst_area.isEmpty()); - - if (!extendByBrick(src.rect(), brick).intersects(dst_area)) { - dst.fill(src_surroundings); - - return; - } - - const CoordinateSystem src_cs; // global coordinate system - const CoordinateSystem dst_cs(dst_area.topLeft()); - const QRect dst_image_rect(QPoint(0, 0), dst_area.size()); - - // Area in dst coordinates that matters. - // Everything outside of it will be overwritten. - QRect dst_relevant_rect(dst_image_rect); - - if (src_surroundings == spreading_color) { - dst_relevant_rect = dst_cs.fromGlobal(src.rect()); - dst_relevant_rect = shrinkByBrick(dst_relevant_rect, brick); - dst_relevant_rect = dst_relevant_rect.intersected(dst_image_rect); - if (dst_relevant_rect.isEmpty()) { - dst.fill(src_surroundings); - - return; - } - } - - const QRect tmp_area(dst_cs.toGlobal(dst_relevant_rect) - .adjusted(-(brick.maxX() - brick.minX()), -(brick.maxY() - brick.minY()), 0, 0)); - - CoordinateSystem tmp_cs(tmp_area.topLeft()); - const QRect tmp_image_rect(QPoint(0, 0), tmp_area.size()); - - // Because all temporary images share the same size, it's easy - // to reuse them. Reusing an image not only saves us the - // cost of memory allocation, but also improves chances that - // image data is already in CPU cache. - ReusableImages tmp_images; - - if (brick.minY() == brick.maxY()) { - spreadInDirection( // horizontal - dst, dst_cs, dst_relevant_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 1, - brick.minY(), 0, brick.width(), rop, !spreading_color, false); - } else if (brick.minX() == brick.maxX()) { - spreadInDirection( // vertical - dst, dst_cs, dst_relevant_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 0, - brick.minY(), 1, brick.height(), rop, !spreading_color, false); - } else { - BinaryImage tmp(tmp_area.size()); - spreadInDirection( // horizontal - tmp, tmp_cs, tmp_image_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 1, - brick.minY(), 0, brick.width(), rop, !spreading_color, true); - - spreadInDirection( // vertical - dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, tmp_images, tmp_cs, tmp_image_rect.size(), 0, 0, 0, 1, - brick.height(), rop, !spreading_color, false); - } - - if (src_surroundings == spreading_color) { - dst.fillExcept(dst_relevant_rect, src_surroundings); - } + assert(!src.isNull()); + assert(!brick.isEmpty()); + assert(!dst_area.isEmpty()); + + if (!extendByBrick(src.rect(), brick).intersects(dst_area)) { + dst.fill(src_surroundings); + + return; + } + + const CoordinateSystem src_cs; // global coordinate system + const CoordinateSystem dst_cs(dst_area.topLeft()); + const QRect dst_image_rect(QPoint(0, 0), dst_area.size()); + + // Area in dst coordinates that matters. + // Everything outside of it will be overwritten. + QRect dst_relevant_rect(dst_image_rect); + + if (src_surroundings == spreading_color) { + dst_relevant_rect = dst_cs.fromGlobal(src.rect()); + dst_relevant_rect = shrinkByBrick(dst_relevant_rect, brick); + dst_relevant_rect = dst_relevant_rect.intersected(dst_image_rect); + if (dst_relevant_rect.isEmpty()) { + dst.fill(src_surroundings); + + return; + } + } + + const QRect tmp_area(dst_cs.toGlobal(dst_relevant_rect) + .adjusted(-(brick.maxX() - brick.minX()), -(brick.maxY() - brick.minY()), 0, 0)); + + CoordinateSystem tmp_cs(tmp_area.topLeft()); + const QRect tmp_image_rect(QPoint(0, 0), tmp_area.size()); + + // Because all temporary images share the same size, it's easy + // to reuse them. Reusing an image not only saves us the + // cost of memory allocation, but also improves chances that + // image data is already in CPU cache. + ReusableImages tmp_images; + + if (brick.minY() == brick.maxY()) { + spreadInDirection( // horizontal + dst, dst_cs, dst_relevant_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 1, + brick.minY(), 0, brick.width(), rop, !spreading_color, false); + } else if (brick.minX() == brick.maxX()) { + spreadInDirection( // vertical + dst, dst_cs, dst_relevant_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 0, + brick.minY(), 1, brick.height(), rop, !spreading_color, false); + } else { + BinaryImage tmp(tmp_area.size()); + spreadInDirection( // horizontal + tmp, tmp_cs, tmp_image_rect, src, src_cs, tmp_images, tmp_cs, tmp_image_rect.size(), brick.minX(), 1, + brick.minY(), 0, brick.width(), rop, !spreading_color, true); + + spreadInDirection( // vertical + dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, tmp_images, tmp_cs, tmp_image_rect.size(), 0, 0, 0, 1, + brick.height(), rop, !spreading_color, false); + } + + if (src_surroundings == spreading_color) { + dst.fillExcept(dst_relevant_rect, src_surroundings); + } } // dilateOrErodeBrick class Darker { -public: - static uint8_t select(uint8_t v1, uint8_t v2) { - return std::min(v1, v2); - } + public: + static uint8_t select(uint8_t v1, uint8_t v2) { return std::min(v1, v2); } }; class Lighter { -public: - static uint8_t select(uint8_t v1, uint8_t v2) { - return std::max(v1, v2); - } + public: + static uint8_t select(uint8_t v1, uint8_t v2) { return std::max(v1, v2); } }; -template +template void fillExtremumArrayLeftHalf(uint8_t* dst, const uint8_t* const src_center, const int src_delta, const int src_first_offset, const int src_center_offset) { - const uint8_t* src = src_center; - uint8_t extremum = *src; + const uint8_t* src = src_center; + uint8_t extremum = *src; + *dst = extremum; + + for (int i = src_center_offset - 1; i >= src_first_offset; --i) { + src -= src_delta; + --dst; + extremum = MinOrMax::select(extremum, *src); *dst = extremum; - - for (int i = src_center_offset - 1; i >= src_first_offset; --i) { - src -= src_delta; - --dst; - extremum = MinOrMax::select(extremum, *src); - *dst = extremum; - } + } } -template +template void fillExtremumArrayRightHalf(uint8_t* dst, const uint8_t* const src_center, const int src_delta, const int src_center_offset, const int src_last_offset) { - const uint8_t* src = src_center; - uint8_t extremum = *src; + const uint8_t* src = src_center; + uint8_t extremum = *src; + *dst = extremum; + + for (int i = src_center_offset + 1; i <= src_last_offset; ++i) { + src += src_delta; + ++dst; + extremum = MinOrMax::select(extremum, *src); *dst = extremum; - - for (int i = src_center_offset + 1; i <= src_last_offset; ++i) { - src += src_delta; - ++dst; - extremum = MinOrMax::select(extremum, *src); - *dst = extremum; - } + } } -template +template void spreadGrayHorizontal(GrayImage& dst, const GrayImage& src, const int dy, const int dx1, const int dx2) { - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); - const uint8_t* src_line = src.data() + dy * src_stride; - uint8_t* dst_line = dst.data(); - - const int dst_width = dst.width(); - const int dst_height = dst.height(); - - const int se_len = dx2 - dx1 + 1; - - std::vector min_max_array(se_len * 2 - 1, 0); - uint8_t* const array_center = &min_max_array[se_len - 1]; - - for (int y = 0; y < dst_height; ++y) { - for (int dst_segment_first = 0; dst_segment_first < dst_width; dst_segment_first += se_len) { - const int dst_segment_last = std::min(dst_segment_first + se_len, dst_width) - 1; // inclusive - const int src_segment_first = dst_segment_first + dx1; - const int src_segment_last = dst_segment_last + dx2; - const int src_segment_center = (src_segment_first + src_segment_last) >> 1; - - fillExtremumArrayLeftHalf(array_center, src_line + src_segment_center, 1, src_segment_first, - src_segment_center); - - fillExtremumArrayRightHalf(array_center, src_line + src_segment_center, 1, src_segment_center, - src_segment_last); - - for (int x = dst_segment_first; x <= dst_segment_last; ++x) { - const int src_first = x + dx1; - const int src_last = x + dx2; // inclusive - assert(src_segment_center >= src_first); - assert(src_segment_center <= src_last); - uint8_t v1 = array_center[src_first - src_segment_center]; - uint8_t v2 = array_center[src_last - src_segment_center]; - dst_line[x] = MinOrMax::select(v1, v2); - } - } - - src_line += src_stride; - dst_line += dst_stride; - } + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); + const uint8_t* src_line = src.data() + dy * src_stride; + uint8_t* dst_line = dst.data(); + + const int dst_width = dst.width(); + const int dst_height = dst.height(); + + const int se_len = dx2 - dx1 + 1; + + std::vector min_max_array(se_len * 2 - 1, 0); + uint8_t* const array_center = &min_max_array[se_len - 1]; + + for (int y = 0; y < dst_height; ++y) { + for (int dst_segment_first = 0; dst_segment_first < dst_width; dst_segment_first += se_len) { + const int dst_segment_last = std::min(dst_segment_first + se_len, dst_width) - 1; // inclusive + const int src_segment_first = dst_segment_first + dx1; + const int src_segment_last = dst_segment_last + dx2; + const int src_segment_center = (src_segment_first + src_segment_last) >> 1; + + fillExtremumArrayLeftHalf(array_center, src_line + src_segment_center, 1, src_segment_first, + src_segment_center); + + fillExtremumArrayRightHalf(array_center, src_line + src_segment_center, 1, src_segment_center, + src_segment_last); + + for (int x = dst_segment_first; x <= dst_segment_last; ++x) { + const int src_first = x + dx1; + const int src_last = x + dx2; // inclusive + assert(src_segment_center >= src_first); + assert(src_segment_center <= src_last); + uint8_t v1 = array_center[src_first - src_segment_center]; + uint8_t v2 = array_center[src_last - src_segment_center]; + dst_line[x] = MinOrMax::select(v1, v2); + } + } + + src_line += src_stride; + dst_line += dst_stride; + } } // spreadGrayHorizontal -template +template void spreadGrayHorizontal(GrayImage& dst, const CoordinateSystem& dst_cs, const GrayImage& src, @@ -501,56 +486,56 @@ void spreadGrayHorizontal(GrayImage& dst, const int dy, const int dx1, const int dx2) { - // src_point = dst_point + dst_to_src; - const QPoint dst_to_src(dst_cs.offsetTo(src_cs)); + // src_point = dst_point + dst_to_src; + const QPoint dst_to_src(dst_cs.offsetTo(src_cs)); - spreadGrayHorizontal(dst, src, dy + dst_to_src.y(), dx1 + dst_to_src.x(), dx2 + dst_to_src.x()); + spreadGrayHorizontal(dst, src, dy + dst_to_src.y(), dx1 + dst_to_src.x(), dx2 + dst_to_src.x()); } -template +template void spreadGrayVertical(GrayImage& dst, const GrayImage& src, const int dx, const int dy1, const int dy2) { - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); - const uint8_t* const src_data = src.data() + dx; - uint8_t* const dst_data = dst.data(); - - const int dst_width = dst.width(); - const int dst_height = dst.height(); - - const int se_len = dy2 - dy1 + 1; - - std::vector min_max_array(se_len * 2 - 1, 0); - uint8_t* const array_center = &min_max_array[se_len - 1]; - - for (int x = 0; x < dst_width; ++x) { - for (int dst_segment_first = 0; dst_segment_first < dst_height; dst_segment_first += se_len) { - const int dst_segment_last = std::min(dst_segment_first + se_len, dst_height) - 1; // inclusive - const int src_segment_first = dst_segment_first + dy1; - const int src_segment_last = dst_segment_last + dy2; - const int src_segment_center = (src_segment_first + src_segment_last) >> 1; - - fillExtremumArrayLeftHalf(array_center, src_data + x + src_segment_center * src_stride, - src_stride, src_segment_first, src_segment_center); - - fillExtremumArrayRightHalf(array_center, src_data + x + src_segment_center * src_stride, - src_stride, src_segment_center, src_segment_last); - - uint8_t* dst = dst_data + x + dst_segment_first * dst_stride; - for (int y = dst_segment_first; y <= dst_segment_last; ++y) { - const int src_first = y + dy1; - const int src_last = y + dy2; // inclusive - assert(src_segment_center >= src_first); - assert(src_segment_center <= src_last); - uint8_t v1 = array_center[src_first - src_segment_center]; - uint8_t v2 = array_center[src_last - src_segment_center]; - *dst = MinOrMax::select(v1, v2); - dst += dst_stride; - } - } - } + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); + const uint8_t* const src_data = src.data() + dx; + uint8_t* const dst_data = dst.data(); + + const int dst_width = dst.width(); + const int dst_height = dst.height(); + + const int se_len = dy2 - dy1 + 1; + + std::vector min_max_array(se_len * 2 - 1, 0); + uint8_t* const array_center = &min_max_array[se_len - 1]; + + for (int x = 0; x < dst_width; ++x) { + for (int dst_segment_first = 0; dst_segment_first < dst_height; dst_segment_first += se_len) { + const int dst_segment_last = std::min(dst_segment_first + se_len, dst_height) - 1; // inclusive + const int src_segment_first = dst_segment_first + dy1; + const int src_segment_last = dst_segment_last + dy2; + const int src_segment_center = (src_segment_first + src_segment_last) >> 1; + + fillExtremumArrayLeftHalf(array_center, src_data + x + src_segment_center * src_stride, src_stride, + src_segment_first, src_segment_center); + + fillExtremumArrayRightHalf(array_center, src_data + x + src_segment_center * src_stride, src_stride, + src_segment_center, src_segment_last); + + uint8_t* dst = dst_data + x + dst_segment_first * dst_stride; + for (int y = dst_segment_first; y <= dst_segment_last; ++y) { + const int src_first = y + dy1; + const int src_last = y + dy2; // inclusive + assert(src_segment_center >= src_first); + assert(src_segment_center <= src_last); + uint8_t v1 = array_center[src_first - src_segment_center]; + uint8_t v2 = array_center[src_last - src_segment_center]; + *dst = MinOrMax::select(v1, v2); + dst += dst_stride; + } + } + } } // spreadGrayVertical -template +template void spreadGrayVertical(GrayImage& dst, const CoordinateSystem& dst_cs, const GrayImage& src, @@ -558,134 +543,134 @@ void spreadGrayVertical(GrayImage& dst, const int dx, const int dy1, const int dy2) { - // src_point = dst_point + dst_to_src; - const QPoint dst_to_src(dst_cs.offsetTo(src_cs)); + // src_point = dst_point + dst_to_src; + const QPoint dst_to_src(dst_cs.offsetTo(src_cs)); - spreadGrayVertical(dst, src, dx + dst_to_src.x(), dy1 + dst_to_src.y(), dy2 + dst_to_src.y()); + spreadGrayVertical(dst, src, dx + dst_to_src.x(), dy1 + dst_to_src.y(), dy2 + dst_to_src.y()); } GrayImage extendGrayImage(const GrayImage& src, const QRect& dst_area, const uint8_t background) { - GrayImage dst(dst_area.size()); + GrayImage dst(dst_area.size()); - const CoordinateSystem dst_cs(dst_area.topLeft()); - const QRect src_rect_in_dst_cs(dst_cs.fromGlobal(src.rect())); - const QRect bound_src_rect_in_dst_cs(src_rect_in_dst_cs.intersected(dst.rect())); + const CoordinateSystem dst_cs(dst_area.topLeft()); + const QRect src_rect_in_dst_cs(dst_cs.fromGlobal(src.rect())); + const QRect bound_src_rect_in_dst_cs(src_rect_in_dst_cs.intersected(dst.rect())); - if (bound_src_rect_in_dst_cs.isEmpty()) { - dst.fill(background); + if (bound_src_rect_in_dst_cs.isEmpty()) { + dst.fill(background); - return dst; - } + return dst; + } - const uint8_t* src_line = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); + const uint8_t* src_line = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); - int y = 0; - for (; y < bound_src_rect_in_dst_cs.top(); ++y, dst_line += dst_stride) { - memset(dst_line, background, dst_stride); - } + int y = 0; + for (; y < bound_src_rect_in_dst_cs.top(); ++y, dst_line += dst_stride) { + memset(dst_line, background, dst_stride); + } - const int front_span_len = bound_src_rect_in_dst_cs.left(); - const int data_span_len = bound_src_rect_in_dst_cs.width(); - const int back_span_offset = front_span_len + data_span_len; - const int back_span_len = dst_area.width() - back_span_offset; + const int front_span_len = bound_src_rect_in_dst_cs.left(); + const int data_span_len = bound_src_rect_in_dst_cs.width(); + const int back_span_offset = front_span_len + data_span_len; + const int back_span_len = dst_area.width() - back_span_offset; - const QPoint src_offset(bound_src_rect_in_dst_cs.topLeft() - src_rect_in_dst_cs.topLeft()); + const QPoint src_offset(bound_src_rect_in_dst_cs.topLeft() - src_rect_in_dst_cs.topLeft()); - src_line += src_offset.x() + src_offset.y() * src_stride; - for (; y <= bound_src_rect_in_dst_cs.bottom(); ++y) { - memset(dst_line, background, front_span_len); - memcpy(dst_line + front_span_len, src_line, data_span_len); - memset(dst_line + back_span_offset, background, back_span_len); + src_line += src_offset.x() + src_offset.y() * src_stride; + for (; y <= bound_src_rect_in_dst_cs.bottom(); ++y) { + memset(dst_line, background, front_span_len); + memcpy(dst_line + front_span_len, src_line, data_span_len); + memset(dst_line + back_span_offset, background, back_span_len); - src_line += src_stride; - dst_line += dst_stride; - } + src_line += src_stride; + dst_line += dst_stride; + } - const int height = dst_area.height(); - for (; y < height; ++y, dst_line += dst_stride) { - memset(dst_line, background, dst_stride); - } + const int height = dst_area.height(); + for (; y < height; ++y, dst_line += dst_stride) { + memset(dst_line, background, dst_stride); + } - return dst; + return dst; } // extendGrayImage -template +template GrayImage dilateOrErodeGray(const GrayImage& src, const Brick& brick, const QRect& dst_area, const unsigned char src_surroundings) { - assert(!src.isNull()); - assert(!brick.isEmpty()); - assert(!dst_area.isEmpty()); + assert(!src.isNull()); + assert(!brick.isEmpty()); + assert(!dst_area.isEmpty()); + + GrayImage dst(dst_area.size()); + + if (!extendByBrick(src.rect(), brick).intersects(dst_area)) { + dst.fill(src_surroundings); + + return dst; + } + const CoordinateSystem dst_cs(dst_area.topLeft()); + + // Each pixel will be a minumum or maximum of a group of pixels + // in its neighborhood. The neighborhood is defined by collect_area. + const Brick collect_area(brick.flipped()); - GrayImage dst(dst_area.size()); + if ((collect_area.minY() != collect_area.maxY()) && (collect_area.minX() != collect_area.maxX())) { + // We are going to make two operations: + // src -> tmp, then tmp -> dst + // Those operations will use the following collect areas: + const Brick collect_area1(collect_area.minX(), collect_area.minY(), collect_area.maxX(), collect_area.minY()); + const Brick collect_area2(0, 0, 0, collect_area.maxY() - collect_area.minY()); - if (!extendByBrick(src.rect(), brick).intersects(dst_area)) { - dst.fill(src_surroundings); + const QRect tmp_rect(extendByBrick(dst_area, collect_area2)); + CoordinateSystem tmp_cs(tmp_rect.topLeft()); - return dst; + GrayImage tmp(tmp_rect.size()); + // First operation. The scope is there to destroy the + // effective_src image when it's no longer necessary. + { + const QRect effective_src_rect(extendByBrick(tmp_rect, collect_area1)); + GrayImage effective_src; + CoordinateSystem effective_src_cs; + if (src.rect().contains(effective_src_rect)) { + effective_src = src; + } else { + effective_src = extendGrayImage(src, effective_src_rect, src_surroundings); + effective_src_cs = CoordinateSystem(effective_src_rect.topLeft()); + } + + spreadGrayHorizontal(tmp, tmp_cs, effective_src, effective_src_cs, collect_area1.minY(), + collect_area1.minX(), collect_area1.maxX()); + } + // Second operation. + spreadGrayVertical(dst, dst_cs, tmp, tmp_cs, collect_area2.minX(), collect_area2.minY(), + collect_area2.maxY()); + } else { + const QRect effective_src_rect(extendByBrick(dst_area, collect_area)); + GrayImage effective_src; + CoordinateSystem effective_src_cs; + if (src.rect().contains(effective_src_rect)) { + effective_src = src; + } else { + effective_src = extendGrayImage(src, effective_src_rect, src_surroundings); + effective_src_cs = CoordinateSystem(effective_src_rect.topLeft()); } - const CoordinateSystem dst_cs(dst_area.topLeft()); - - // Each pixel will be a minumum or maximum of a group of pixels - // in its neighborhood. The neighborhood is defined by collect_area. - const Brick collect_area(brick.flipped()); - - if ((collect_area.minY() != collect_area.maxY()) && (collect_area.minX() != collect_area.maxX())) { - // We are going to make two operations: - // src -> tmp, then tmp -> dst - // Those operations will use the following collect areas: - const Brick collect_area1(collect_area.minX(), collect_area.minY(), collect_area.maxX(), collect_area.minY()); - const Brick collect_area2(0, 0, 0, collect_area.maxY() - collect_area.minY()); - - const QRect tmp_rect(extendByBrick(dst_area, collect_area2)); - CoordinateSystem tmp_cs(tmp_rect.topLeft()); - - GrayImage tmp(tmp_rect.size()); - // First operation. The scope is there to destroy the - // effective_src image when it's no longer necessary. - { - const QRect effective_src_rect(extendByBrick(tmp_rect, collect_area1)); - GrayImage effective_src; - CoordinateSystem effective_src_cs; - if (src.rect().contains(effective_src_rect)) { - effective_src = src; - } else { - effective_src = extendGrayImage(src, effective_src_rect, src_surroundings); - effective_src_cs = CoordinateSystem(effective_src_rect.topLeft()); - } - - spreadGrayHorizontal(tmp, tmp_cs, effective_src, effective_src_cs, collect_area1.minY(), - collect_area1.minX(), collect_area1.maxX()); - } - // Second operation. - spreadGrayVertical(dst, dst_cs, tmp, tmp_cs, collect_area2.minX(), collect_area2.minY(), - collect_area2.maxY()); + + if (collect_area.minY() == collect_area.maxY()) { + spreadGrayHorizontal(dst, dst_cs, effective_src, effective_src_cs, collect_area.minY(), + collect_area.minX(), collect_area.maxX()); } else { - const QRect effective_src_rect(extendByBrick(dst_area, collect_area)); - GrayImage effective_src; - CoordinateSystem effective_src_cs; - if (src.rect().contains(effective_src_rect)) { - effective_src = src; - } else { - effective_src = extendGrayImage(src, effective_src_rect, src_surroundings); - effective_src_cs = CoordinateSystem(effective_src_rect.topLeft()); - } - - if (collect_area.minY() == collect_area.maxY()) { - spreadGrayHorizontal(dst, dst_cs, effective_src, effective_src_cs, collect_area.minY(), - collect_area.minX(), collect_area.maxX()); - } else { - assert(collect_area.minX() == collect_area.maxX()); - spreadGrayVertical(dst, dst_cs, effective_src, effective_src_cs, collect_area.minX(), - collect_area.minY(), collect_area.maxY()); - } + assert(collect_area.minX() == collect_area.maxX()); + spreadGrayVertical(dst, dst_cs, effective_src, effective_src_cs, collect_area.minX(), + collect_area.minY(), collect_area.maxY()); } + } - return dst; + return dst; } // dilateOrErodeGray } // anonymous namespace @@ -693,300 +678,300 @@ BinaryImage dilateBrick(const BinaryImage& src, const Brick& brick, const QRect& dst_area, const BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("dilateBrick: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("dilateBrick: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("dilateBrick: dst_area is empty"); - } - - TemplateRasterOp> rop; - BinaryImage dst(dst_area.size()); - dilateOrErodeBrick(dst, src, brick, dst_area, src_surroundings, rop, BLACK); - - return dst; + if (src.isNull()) { + throw std::invalid_argument("dilateBrick: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("dilateBrick: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("dilateBrick: dst_area is empty"); + } + + TemplateRasterOp> rop; + BinaryImage dst(dst_area.size()); + dilateOrErodeBrick(dst, src, brick, dst_area, src_surroundings, rop, BLACK); + + return dst; } GrayImage dilateGray(const GrayImage& src, const Brick& brick, const QRect& dst_area, const unsigned char src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("dilateGray: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("dilateGray: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("dilateGray: dst_area is empty"); - } - - return dilateOrErodeGray(src, brick, dst_area, src_surroundings); + if (src.isNull()) { + throw std::invalid_argument("dilateGray: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("dilateGray: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("dilateGray: dst_area is empty"); + } + + return dilateOrErodeGray(src, brick, dst_area, src_surroundings); } BinaryImage dilateBrick(const BinaryImage& src, const Brick& brick, const BWColor src_surroundings) { - return dilateBrick(src, brick, src.rect(), src_surroundings); + return dilateBrick(src, brick, src.rect(), src_surroundings); } GrayImage dilateGray(const GrayImage& src, const Brick& brick, const unsigned char src_surroundings) { - return dilateGray(src, brick, src.rect(), src_surroundings); + return dilateGray(src, brick, src.rect(), src_surroundings); } BinaryImage erodeBrick(const BinaryImage& src, const Brick& brick, const QRect& dst_area, const BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("erodeBrick: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("erodeBrick: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("erodeBrick: dst_area is empty"); - } - - typedef RopAnd Rop; - - TemplateRasterOp> rop; - BinaryImage dst(dst_area.size()); - dilateOrErodeBrick(dst, src, brick, dst_area, src_surroundings, rop, WHITE); - - return dst; + if (src.isNull()) { + throw std::invalid_argument("erodeBrick: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("erodeBrick: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("erodeBrick: dst_area is empty"); + } + + typedef RopAnd Rop; + + TemplateRasterOp> rop; + BinaryImage dst(dst_area.size()); + dilateOrErodeBrick(dst, src, brick, dst_area, src_surroundings, rop, WHITE); + + return dst; } GrayImage erodeGray(const GrayImage& src, const Brick& brick, const QRect& dst_area, const unsigned char src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("erodeGray: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("erodeGray: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("erodeGray: dst_area is empty"); - } - - return dilateOrErodeGray(src, brick, dst_area, src_surroundings); + if (src.isNull()) { + throw std::invalid_argument("erodeGray: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("erodeGray: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("erodeGray: dst_area is empty"); + } + + return dilateOrErodeGray(src, brick, dst_area, src_surroundings); } BinaryImage erodeBrick(const BinaryImage& src, const Brick& brick, const BWColor src_surroundings) { - return erodeBrick(src, brick, src.rect(), src_surroundings); + return erodeBrick(src, brick, src.rect(), src_surroundings); } GrayImage erodeGray(const GrayImage& src, const Brick& brick, const unsigned char src_surroundings) { - return erodeGray(src, brick, src.rect(), src_surroundings); + return erodeGray(src, brick, src.rect(), src_surroundings); } BinaryImage openBrick(const BinaryImage& src, const QSize& brick, const QRect& dst_area, const BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("openBrick: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("openBrick: brick is empty"); - } + if (src.isNull()) { + throw std::invalid_argument("openBrick: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("openBrick: brick is empty"); + } - Brick actual_brick(brick); + Brick actual_brick(brick); - QRect tmp_area; + QRect tmp_area; - if (src_surroundings == WHITE) { - tmp_area = shrinkByBrick(src.rect(), actual_brick); - if (tmp_area.isEmpty()) { - return BinaryImage(dst_area.size(), WHITE); - } - } else { - tmp_area = extendByBrick(src.rect(), actual_brick); + if (src_surroundings == WHITE) { + tmp_area = shrinkByBrick(src.rect(), actual_brick); + if (tmp_area.isEmpty()) { + return BinaryImage(dst_area.size(), WHITE); } + } else { + tmp_area = extendByBrick(src.rect(), actual_brick); + } - // At this point we could leave tmp_area as is, but a large - // tmp_area would be a waste if dst_area is small. + // At this point we could leave tmp_area as is, but a large + // tmp_area would be a waste if dst_area is small. - tmp_area = extendByBrick(dst_area, actual_brick).intersected(tmp_area); + tmp_area = extendByBrick(dst_area, actual_brick).intersected(tmp_area); - CoordinateSystem tmp_cs(tmp_area.topLeft()); + CoordinateSystem tmp_cs(tmp_area.topLeft()); - const BinaryImage tmp(erodeBrick(src, actual_brick, tmp_area, src_surroundings)); - actual_brick.flip(); + const BinaryImage tmp(erodeBrick(src, actual_brick, tmp_area, src_surroundings)); + actual_brick.flip(); - return dilateBrick(tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings); + return dilateBrick(tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings); } // openBrick BinaryImage openBrick(const BinaryImage& src, const QSize& brick, const BWColor src_surroundings) { - return openBrick(src, brick, src.rect(), src_surroundings); + return openBrick(src, brick, src.rect(), src_surroundings); } GrayImage openGray(const GrayImage& src, const QSize& brick, const QRect& dst_area, const unsigned char src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("openGray: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("openGray: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("openGray: dst_area is empty"); - } - - const Brick brick1(brick); - const Brick brick2(brick1.flipped()); - - // We are going to make two operations: - // tmp = erodeGray(src, brick1), then dst = dilateGray(tmp, brick2) - const QRect tmp_rect(extendByBrick(dst_area, brick1)); - CoordinateSystem tmp_cs(tmp_rect.topLeft()); - - const GrayImage tmp(dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings)); - - return dilateOrErodeGray(tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings); + if (src.isNull()) { + throw std::invalid_argument("openGray: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("openGray: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("openGray: dst_area is empty"); + } + + const Brick brick1(brick); + const Brick brick2(brick1.flipped()); + + // We are going to make two operations: + // tmp = erodeGray(src, brick1), then dst = dilateGray(tmp, brick2) + const QRect tmp_rect(extendByBrick(dst_area, brick1)); + CoordinateSystem tmp_cs(tmp_rect.topLeft()); + + const GrayImage tmp(dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings)); + + return dilateOrErodeGray(tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings); } GrayImage openGray(const GrayImage& src, const QSize& brick, const unsigned char src_surroundings) { - return openGray(src, brick, src.rect(), src_surroundings); + return openGray(src, brick, src.rect(), src_surroundings); } BinaryImage closeBrick(const BinaryImage& src, const QSize& brick, const QRect& dst_area, const BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("closeBrick: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("closeBrick: brick is empty"); - } + if (src.isNull()) { + throw std::invalid_argument("closeBrick: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("closeBrick: brick is empty"); + } - Brick actual_brick(brick); + Brick actual_brick(brick); - QRect tmp_area; + QRect tmp_area; - if (src_surroundings == BLACK) { - tmp_area = shrinkByBrick(src.rect(), actual_brick); - if (tmp_area.isEmpty()) { - return BinaryImage(dst_area.size(), BLACK); - } - } else { - tmp_area = extendByBrick(src.rect(), actual_brick); + if (src_surroundings == BLACK) { + tmp_area = shrinkByBrick(src.rect(), actual_brick); + if (tmp_area.isEmpty()) { + return BinaryImage(dst_area.size(), BLACK); } + } else { + tmp_area = extendByBrick(src.rect(), actual_brick); + } - // At this point we could leave tmp_area as is, but a large - // tmp_area would be a waste if dst_area is small. + // At this point we could leave tmp_area as is, but a large + // tmp_area would be a waste if dst_area is small. - tmp_area = extendByBrick(dst_area, actual_brick).intersected(tmp_area); + tmp_area = extendByBrick(dst_area, actual_brick).intersected(tmp_area); - CoordinateSystem tmp_cs(tmp_area.topLeft()); + CoordinateSystem tmp_cs(tmp_area.topLeft()); - const BinaryImage tmp(dilateBrick(src, actual_brick, tmp_area, src_surroundings)); - actual_brick.flip(); + const BinaryImage tmp(dilateBrick(src, actual_brick, tmp_area, src_surroundings)); + actual_brick.flip(); - return erodeBrick(tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings); + return erodeBrick(tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings); } // closeBrick BinaryImage closeBrick(const BinaryImage& src, const QSize& brick, const BWColor src_surroundings) { - return closeBrick(src, brick, src.rect(), src_surroundings); + return closeBrick(src, brick, src.rect(), src_surroundings); } GrayImage closeGray(const GrayImage& src, const QSize& brick, const QRect& dst_area, const unsigned char src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("closeGray: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("closeGray: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("closeGray: dst_area is empty"); - } - - const Brick brick1(brick); - const Brick brick2(brick1.flipped()); - - // We are going to make two operations: - // tmp = dilateGray(src, brick1), then dst = erodeGray(tmp, brick2) - const QRect tmp_rect(extendByBrick(dst_area, brick2)); - CoordinateSystem tmp_cs(tmp_rect.topLeft()); - - const GrayImage tmp(dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings)); - - return dilateOrErodeGray(tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings); + if (src.isNull()) { + throw std::invalid_argument("closeGray: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("closeGray: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("closeGray: dst_area is empty"); + } + + const Brick brick1(brick); + const Brick brick2(brick1.flipped()); + + // We are going to make two operations: + // tmp = dilateGray(src, brick1), then dst = erodeGray(tmp, brick2) + const QRect tmp_rect(extendByBrick(dst_area, brick2)); + CoordinateSystem tmp_cs(tmp_rect.topLeft()); + + const GrayImage tmp(dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings)); + + return dilateOrErodeGray(tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings); } GrayImage closeGray(const GrayImage& src, const QSize& brick, const unsigned char src_surroundings) { - return closeGray(src, brick, src.rect(), src_surroundings); + return closeGray(src, brick, src.rect(), src_surroundings); } BinaryImage hitMissMatch(const BinaryImage& src, const BWColor src_surroundings, const std::vector& hits, const std::vector& misses) { - if (src.isNull()) { - return BinaryImage(); - } + if (src.isNull()) { + return BinaryImage(); + } + + const QRect rect(src.rect()); // same as dst.rect() + BinaryImage dst(src.size()); + + bool first = true; - const QRect rect(src.rect()); // same as dst.rect() - BinaryImage dst(src.size()); - - bool first = true; - - for (const QPoint& hit : hits) { - QRect src_rect(rect); - QRect dst_rect(rect.translated(-hit)); - adjustToFit(rect, dst_rect, src_rect); - - if (first) { - first = false; - rasterOp(dst, dst_rect, src, src_rect.topLeft()); - if (src_surroundings == BLACK) { - dst.fillExcept(dst_rect, BLACK); - } - } else { - rasterOp>(dst, dst_rect, src, src_rect.topLeft()); - } - - if (src_surroundings == WHITE) { - // No hits on white surroundings. - dst.fillExcept(dst_rect, WHITE); - } + for (const QPoint& hit : hits) { + QRect src_rect(rect); + QRect dst_rect(rect.translated(-hit)); + adjustToFit(rect, dst_rect, src_rect); + + if (first) { + first = false; + rasterOp(dst, dst_rect, src, src_rect.topLeft()); + if (src_surroundings == BLACK) { + dst.fillExcept(dst_rect, BLACK); + } + } else { + rasterOp>(dst, dst_rect, src, src_rect.topLeft()); } - for (const QPoint& miss : misses) { - QRect src_rect(rect); - QRect dst_rect(rect.translated(-miss)); - adjustToFit(rect, dst_rect, src_rect); - - if (first) { - first = false; - rasterOp>(dst, dst_rect, src, src_rect.topLeft()); - if (src_surroundings == WHITE) { - dst.fillExcept(dst_rect, BLACK); - } - } else { - rasterOp, RopDst>>(dst, dst_rect, src, src_rect.topLeft()); - } - - if (src_surroundings == BLACK) { - // No misses on black surroundings. - dst.fillExcept(dst_rect, WHITE); - } + if (src_surroundings == WHITE) { + // No hits on white surroundings. + dst.fillExcept(dst_rect, WHITE); } + } + + for (const QPoint& miss : misses) { + QRect src_rect(rect); + QRect dst_rect(rect.translated(-miss)); + adjustToFit(rect, dst_rect, src_rect); if (first) { - dst.fill(WHITE); // No matches. + first = false; + rasterOp>(dst, dst_rect, src, src_rect.topLeft()); + if (src_surroundings == WHITE) { + dst.fillExcept(dst_rect, BLACK); + } + } else { + rasterOp, RopDst>>(dst, dst_rect, src, src_rect.topLeft()); } - return dst; + if (src_surroundings == BLACK) { + // No misses on black surroundings. + dst.fillExcept(dst_rect, WHITE); + } + } + + if (first) { + dst.fill(WHITE); // No matches. + } + + return dst; } // hitMissMatch BinaryImage hitMissMatch(const BinaryImage& src, @@ -995,28 +980,28 @@ BinaryImage hitMissMatch(const BinaryImage& src, const int pattern_width, const int pattern_height, const QPoint& pattern_origin) { - std::vector hits; - std::vector misses; - - const char* p = pattern; - for (int y = 0; y < pattern_height; ++y) { - for (int x = 0; x < pattern_width; ++x, ++p) { - switch (*p) { - case 'X': - hits.push_back(QPoint(x, y) - pattern_origin); - break; - case ' ': - misses.push_back(QPoint(x, y) - pattern_origin); - break; - case '?': - break; - default: - throw std::invalid_argument("hitMissMatch: invalid character in pattern"); - } - } - } - - return hitMissMatch(src, src_surroundings, hits, misses); + std::vector hits; + std::vector misses; + + const char* p = pattern; + for (int y = 0; y < pattern_height; ++y) { + for (int x = 0; x < pattern_width; ++x, ++p) { + switch (*p) { + case 'X': + hits.push_back(QPoint(x, y) - pattern_origin); + break; + case ' ': + misses.push_back(QPoint(x, y) - pattern_origin); + break; + case '?': + break; + default: + throw std::invalid_argument("hitMissMatch: invalid character in pattern"); + } + } + } + + return hitMissMatch(src, src_surroundings, hits, misses); } BinaryImage hitMissReplace(const BinaryImage& src, @@ -1024,11 +1009,11 @@ BinaryImage hitMissReplace(const BinaryImage& src, const char* const pattern, const int pattern_width, const int pattern_height) { - BinaryImage dst(src); + BinaryImage dst(src); - hitMissReplaceInPlace(dst, src_surroundings, pattern, pattern_width, pattern_height); + hitMissReplaceInPlace(dst, src_surroundings, pattern, pattern_width, pattern_height); - return dst; + return dst; } void hitMissReplaceInPlace(BinaryImage& img, @@ -1036,133 +1021,133 @@ void hitMissReplaceInPlace(BinaryImage& img, const char* const pattern, const int pattern_width, const int pattern_height) { - // It's better to have the origin at one of the replacement positions. - // Otherwise we may miss a partially outside-of-image match because - // the origin point was outside of the image as well. - const int pattern_len = pattern_width * pattern_height; - const auto* const minus_pos = (const char*) memchr(pattern, '-', pattern_len); - const auto* const plus_pos = (const char*) memchr(pattern, '+', pattern_len); - const char* origin_pos; - if (minus_pos && plus_pos) { - origin_pos = std::min(minus_pos, plus_pos); - } else if (minus_pos) { - origin_pos = minus_pos; - } else if (plus_pos) { - origin_pos = plus_pos; - } else { - // No replacements requested - nothing to do. - return; - } - - const QPoint origin(static_cast((origin_pos - pattern) % pattern_width), - static_cast((origin_pos - pattern) / pattern_width)); - - std::vector hits; - std::vector misses; - std::vector white_to_black; - std::vector black_to_white; - - const char* p = pattern; - for (int y = 0; y < pattern_height; ++y) { - for (int x = 0; x < pattern_width; ++x, ++p) { - switch (*p) { - case '-': - black_to_white.push_back(QPoint(x, y) - origin); - // fall through - case 'X': - hits.push_back(QPoint(x, y) - origin); - break; - case '+': - white_to_black.push_back(QPoint(x, y) - origin); - // fall through - case ' ': - misses.push_back(QPoint(x, y) - origin); - break; - case '?': - break; - default: - throw std::invalid_argument("hitMissReplace: invalid character in pattern"); - } - } - } - - const BinaryImage matches(hitMissMatch(img, src_surroundings, hits, misses)); - const QRect rect(img.rect()); - - for (const QPoint& offset : white_to_black) { - QRect src_rect(rect); - QRect dst_rect(rect.translated(offset)); - adjustToFit(rect, dst_rect, src_rect); - - rasterOp>(img, dst_rect, matches, src_rect.topLeft()); - } - - for (const QPoint& offset : black_to_white) { - QRect src_rect(rect); - QRect dst_rect(rect.translated(offset)); - adjustToFit(rect, dst_rect, src_rect); - - rasterOp>(img, dst_rect, matches, src_rect.topLeft()); - } + // It's better to have the origin at one of the replacement positions. + // Otherwise we may miss a partially outside-of-image match because + // the origin point was outside of the image as well. + const int pattern_len = pattern_width * pattern_height; + const auto* const minus_pos = (const char*) memchr(pattern, '-', pattern_len); + const auto* const plus_pos = (const char*) memchr(pattern, '+', pattern_len); + const char* origin_pos; + if (minus_pos && plus_pos) { + origin_pos = std::min(minus_pos, plus_pos); + } else if (minus_pos) { + origin_pos = minus_pos; + } else if (plus_pos) { + origin_pos = plus_pos; + } else { + // No replacements requested - nothing to do. + return; + } + + const QPoint origin(static_cast((origin_pos - pattern) % pattern_width), + static_cast((origin_pos - pattern) / pattern_width)); + + std::vector hits; + std::vector misses; + std::vector white_to_black; + std::vector black_to_white; + + const char* p = pattern; + for (int y = 0; y < pattern_height; ++y) { + for (int x = 0; x < pattern_width; ++x, ++p) { + switch (*p) { + case '-': + black_to_white.push_back(QPoint(x, y) - origin); + // fall through + case 'X': + hits.push_back(QPoint(x, y) - origin); + break; + case '+': + white_to_black.push_back(QPoint(x, y) - origin); + // fall through + case ' ': + misses.push_back(QPoint(x, y) - origin); + break; + case '?': + break; + default: + throw std::invalid_argument("hitMissReplace: invalid character in pattern"); + } + } + } + + const BinaryImage matches(hitMissMatch(img, src_surroundings, hits, misses)); + const QRect rect(img.rect()); + + for (const QPoint& offset : white_to_black) { + QRect src_rect(rect); + QRect dst_rect(rect.translated(offset)); + adjustToFit(rect, dst_rect, src_rect); + + rasterOp>(img, dst_rect, matches, src_rect.topLeft()); + } + + for (const QPoint& offset : black_to_white) { + QRect src_rect(rect); + QRect dst_rect(rect.translated(offset)); + adjustToFit(rect, dst_rect, src_rect); + + rasterOp>(img, dst_rect, matches, src_rect.topLeft()); + } } BinaryImage whiteTopHatTransform(const BinaryImage& src, const QSize& brick, const QRect& dst_area, BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("whiteTopHatTransform: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("whiteTopHatTransform: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("whiteTopHatTransform: dst_area is empty"); - } - - BinaryImage dst(dst_area.size()); - if (dst_area != src.rect()) { - rasterOp(dst, dst.rect(), src, dst_area.topLeft()); - } else { - dst = src; - } - - rasterOp>(dst, openBrick(src, brick, dst_area, src_surroundings)); - - return dst; + if (src.isNull()) { + throw std::invalid_argument("whiteTopHatTransform: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("whiteTopHatTransform: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("whiteTopHatTransform: dst_area is empty"); + } + + BinaryImage dst(dst_area.size()); + if (dst_area != src.rect()) { + rasterOp(dst, dst.rect(), src, dst_area.topLeft()); + } else { + dst = src; + } + + rasterOp>(dst, openBrick(src, brick, dst_area, src_surroundings)); + + return dst; } BinaryImage whiteTopHatTransform(const BinaryImage& src, const QSize& brick, BWColor src_surroundings) { - return whiteTopHatTransform(src, brick, src.rect(), src_surroundings); + return whiteTopHatTransform(src, brick, src.rect(), src_surroundings); } BinaryImage blackTopHatTransform(const BinaryImage& src, const QSize& brick, const QRect& dst_area, BWColor src_surroundings) { - if (src.isNull()) { - throw std::invalid_argument("blackTopHatTransform: src image is null"); - } - if (brick.isEmpty()) { - throw std::invalid_argument("blackTopHatTransform: brick is empty"); - } - if (dst_area.isEmpty()) { - throw std::invalid_argument("blackTopHatTransform: dst_area is empty"); - } - - BinaryImage dst(dst_area.size()); - if (dst_area != src.rect()) { - rasterOp(dst, dst.rect(), src, dst_area.topLeft()); - } else { - dst = src; - } - - rasterOp>(dst, closeBrick(src, brick, dst_area, src_surroundings)); - - return dst; + if (src.isNull()) { + throw std::invalid_argument("blackTopHatTransform: src image is null"); + } + if (brick.isEmpty()) { + throw std::invalid_argument("blackTopHatTransform: brick is empty"); + } + if (dst_area.isEmpty()) { + throw std::invalid_argument("blackTopHatTransform: dst_area is empty"); + } + + BinaryImage dst(dst_area.size()); + if (dst_area != src.rect()) { + rasterOp(dst, dst.rect(), src, dst_area.topLeft()); + } else { + dst = src; + } + + rasterOp>(dst, closeBrick(src, brick, dst_area, src_surroundings)); + + return dst; } BinaryImage blackTopHatTransform(const BinaryImage& src, const QSize& brick, BWColor src_surroundings) { - return blackTopHatTransform(src, brick, src.rect(), src_surroundings); + return blackTopHatTransform(src, brick, src.rect(), src_surroundings); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/Morphology.h b/imageproc/Morphology.h index 6f3aa82ec..7a198b91b 100644 --- a/imageproc/Morphology.h +++ b/imageproc/Morphology.h @@ -19,8 +19,8 @@ #ifndef IMAGEPROC_MORPHOLOGY_H_ #define IMAGEPROC_MORPHOLOGY_H_ -#include "BWColor.h" #include +#include "BWColor.h" class QSize; class QRect; @@ -31,87 +31,73 @@ class BinaryImage; class GrayImage; class Brick { -public: - /** - * \brief Constructs a brick with origin at the center. - */ - Brick(const QSize& size); - - /** - * \brief Constructs a brick with origin specified relative to its size. - * - * For example, a 3x3 brick with origin at the center would be - * constructed as follows: - * \code - * Brick brick(QSize(3, 3), QPoint(1, 1)); - * \endcode - * \note Origin doesn't have to be inside the brick. - */ - Brick(const QSize& size, const QPoint& origin); - - /** - * \brief Constructs a brick by specifying its bounds. - * - * Note that all bounds are inclusive. The order of the arguments - * is the same as for QRect::adjust(). - */ - Brick(int min_x, int min_y, int max_x, int max_y); - - /** - * \brief Get the minimum (inclusive) X offset from the origin. - */ - int minX() const { - return m_minX; - } - - /** - * \brief Get the maximum (inclusive) X offset from the origin. - */ - int maxX() const { - return m_maxX; - } - - /** - * \brief Get the minimum (inclusive) Y offset from the origin. - */ - int minY() const { - return m_minY; - } - - /** - * \brief Get the maximum (inclusive) Y offset from the origin. - */ - int maxY() const { - return m_maxY; - } - - int width() const { - return m_maxX - m_minX + 1; - } - - int height() const { - return m_maxY - m_minY + 1; - } - - bool isEmpty() const { - return m_minX > m_maxX || m_minY > m_maxY; - } - - /** - * \brief Flips the brick both horizontally and vertically around the origin. - */ - void flip(); - - /** - * \brief Returns a brick flipped both horizontally and vertically around the origin. - */ - Brick flipped() const; - -private: - int m_minX; - int m_maxX; - int m_minY; - int m_maxY; + public: + /** + * \brief Constructs a brick with origin at the center. + */ + Brick(const QSize& size); + + /** + * \brief Constructs a brick with origin specified relative to its size. + * + * For example, a 3x3 brick with origin at the center would be + * constructed as follows: + * \code + * Brick brick(QSize(3, 3), QPoint(1, 1)); + * \endcode + * \note Origin doesn't have to be inside the brick. + */ + Brick(const QSize& size, const QPoint& origin); + + /** + * \brief Constructs a brick by specifying its bounds. + * + * Note that all bounds are inclusive. The order of the arguments + * is the same as for QRect::adjust(). + */ + Brick(int min_x, int min_y, int max_x, int max_y); + + /** + * \brief Get the minimum (inclusive) X offset from the origin. + */ + int minX() const { return m_minX; } + + /** + * \brief Get the maximum (inclusive) X offset from the origin. + */ + int maxX() const { return m_maxX; } + + /** + * \brief Get the minimum (inclusive) Y offset from the origin. + */ + int minY() const { return m_minY; } + + /** + * \brief Get the maximum (inclusive) Y offset from the origin. + */ + int maxY() const { return m_maxY; } + + int width() const { return m_maxX - m_minX + 1; } + + int height() const { return m_maxY - m_minY + 1; } + + bool isEmpty() const { return m_minX > m_maxX || m_minY > m_maxY; } + + /** + * \brief Flips the brick both horizontally and vertically around the origin. + */ + void flip(); + + /** + * \brief Returns a brick flipped both horizontally and vertically around the origin. + */ + Brick flipped() const; + + private: + int m_minX; + int m_maxX; + int m_minY; + int m_maxY; }; diff --git a/imageproc/OrthogonalRotation.cpp b/imageproc/OrthogonalRotation.cpp index b07ee0274..7ea3754ca 100644 --- a/imageproc/OrthogonalRotation.cpp +++ b/imageproc/OrthogonalRotation.cpp @@ -22,151 +22,151 @@ namespace imageproc { static inline uint32_t mask(int x) { - return (uint32_t(1) << 31) >> (x % 32); + return (uint32_t(1) << 31) >> (x % 32); } static BinaryImage rotate0(const BinaryImage& src, const QRect& src_rect) { - if (src_rect == src.rect()) { - return src; - } + if (src_rect == src.rect()) { + return src; + } - BinaryImage dst(src_rect.width(), src_rect.height()); - rasterOp(dst, dst.rect(), src, src_rect.topLeft()); + BinaryImage dst(src_rect.width(), src_rect.height()); + rasterOp(dst, dst.rect(), src, src_rect.topLeft()); - return dst; + return dst; } static BinaryImage rotate90(const BinaryImage& src, const QRect& src_rect) { - const int dst_w = src_rect.height(); - const int dst_h = src_rect.width(); - BinaryImage dst(dst_w, dst_h); - dst.fill(WHITE); - const int src_wpl = src.wordsPerLine(); - const int dst_wpl = dst.wordsPerLine(); - const uint32_t* const src_data = src.data() + src_rect.bottom() * src_wpl; - uint32_t* dst_line = dst.data(); - - /* - * dst - * -----> - * ^ - * | src - * | - */ - - for (int dst_y = 0; dst_y < dst_h; ++dst_y) { - const int src_x = src_rect.left() + dst_y; - const uint32_t* src_pword = src_data + src_x / 32; - const uint32_t src_mask = mask(src_x); - - for (int dst_x = 0; dst_x < dst_w; ++dst_x) { - if (*src_pword & src_mask) { - dst_line[dst_x / 32] |= mask(dst_x); - } - src_pword -= src_wpl; - } - - dst_line += dst_wpl; + const int dst_w = src_rect.height(); + const int dst_h = src_rect.width(); + BinaryImage dst(dst_w, dst_h); + dst.fill(WHITE); + const int src_wpl = src.wordsPerLine(); + const int dst_wpl = dst.wordsPerLine(); + const uint32_t* const src_data = src.data() + src_rect.bottom() * src_wpl; + uint32_t* dst_line = dst.data(); + + /* + * dst + * -----> + * ^ + * | src + * | + */ + + for (int dst_y = 0; dst_y < dst_h; ++dst_y) { + const int src_x = src_rect.left() + dst_y; + const uint32_t* src_pword = src_data + src_x / 32; + const uint32_t src_mask = mask(src_x); + + for (int dst_x = 0; dst_x < dst_w; ++dst_x) { + if (*src_pword & src_mask) { + dst_line[dst_x / 32] |= mask(dst_x); + } + src_pword -= src_wpl; } - return dst; + dst_line += dst_wpl; + } + + return dst; } static BinaryImage rotate180(const BinaryImage& src, const QRect& src_rect) { - const int dst_w = src_rect.width(); - const int dst_h = src_rect.height(); - BinaryImage dst(dst_w, dst_h); - dst.fill(WHITE); - const int src_wpl = src.wordsPerLine(); - const int dst_wpl = dst.wordsPerLine(); - const uint32_t* src_line = src.data() + src_rect.bottom() * src_wpl; - uint32_t* dst_line = dst.data(); - - /* - * dst - * -----> - * <----- - * src - */ - - for (int dst_y = 0; dst_y < dst_h; ++dst_y) { - int src_x = src_rect.right(); - for (int dst_x = 0; dst_x < dst_w; --src_x, ++dst_x) { - if (src_line[src_x / 32] & mask(src_x)) { - dst_line[dst_x / 32] |= mask(dst_x); - } - } - - src_line -= src_wpl; - dst_line += dst_wpl; + const int dst_w = src_rect.width(); + const int dst_h = src_rect.height(); + BinaryImage dst(dst_w, dst_h); + dst.fill(WHITE); + const int src_wpl = src.wordsPerLine(); + const int dst_wpl = dst.wordsPerLine(); + const uint32_t* src_line = src.data() + src_rect.bottom() * src_wpl; + uint32_t* dst_line = dst.data(); + + /* + * dst + * -----> + * <----- + * src + */ + + for (int dst_y = 0; dst_y < dst_h; ++dst_y) { + int src_x = src_rect.right(); + for (int dst_x = 0; dst_x < dst_w; --src_x, ++dst_x) { + if (src_line[src_x / 32] & mask(src_x)) { + dst_line[dst_x / 32] |= mask(dst_x); + } } - return dst; + src_line -= src_wpl; + dst_line += dst_wpl; + } + + return dst; } static BinaryImage rotate270(const BinaryImage& src, const QRect& src_rect) { - const int dst_w = src_rect.height(); - const int dst_h = src_rect.width(); - BinaryImage dst(dst_w, dst_h); - dst.fill(WHITE); - const int src_wpl = src.wordsPerLine(); - const int dst_wpl = dst.wordsPerLine(); - const uint32_t* const src_data = src.data() + src_rect.top() * src_wpl; - uint32_t* dst_line = dst.data(); - - /* - * dst - * -----> - * | - * src | - * v - */ - - for (int dst_y = 0; dst_y < dst_h; ++dst_y) { - const int src_x = src_rect.right() - dst_y; - const uint32_t* src_pword = src_data + src_x / 32; - const uint32_t src_mask = mask(src_x); - - for (int dst_x = 0; dst_x < dst_w; ++dst_x) { - if (*src_pword & src_mask) { - dst_line[dst_x / 32] |= mask(dst_x); - } - src_pword += src_wpl; - } - - dst_line += dst_wpl; + const int dst_w = src_rect.height(); + const int dst_h = src_rect.width(); + BinaryImage dst(dst_w, dst_h); + dst.fill(WHITE); + const int src_wpl = src.wordsPerLine(); + const int dst_wpl = dst.wordsPerLine(); + const uint32_t* const src_data = src.data() + src_rect.top() * src_wpl; + uint32_t* dst_line = dst.data(); + + /* + * dst + * -----> + * | + * src | + * v + */ + + for (int dst_y = 0; dst_y < dst_h; ++dst_y) { + const int src_x = src_rect.right() - dst_y; + const uint32_t* src_pword = src_data + src_x / 32; + const uint32_t src_mask = mask(src_x); + + for (int dst_x = 0; dst_x < dst_w; ++dst_x) { + if (*src_pword & src_mask) { + dst_line[dst_x / 32] |= mask(dst_x); + } + src_pword += src_wpl; } - return dst; + dst_line += dst_wpl; + } + + return dst; } BinaryImage orthogonalRotation(const BinaryImage& src, const QRect& src_rect, const int degrees) { - if (src.isNull() || src_rect.isNull()) { - return BinaryImage(); - } - - if (src_rect.intersected(src.rect()) != src_rect) { - throw std::invalid_argument("orthogonalRotation: invalid src_rect"); - } - - switch (degrees % 360) { - case 0: - return rotate0(src, src_rect); - case 90: - case -270: - return rotate90(src, src_rect); - case 180: - case -180: - return rotate180(src, src_rect); - case 270: - case -90: - return rotate270(src, src_rect); - default: - throw std::invalid_argument("orthogonalRotation: invalid angle"); - } + if (src.isNull() || src_rect.isNull()) { + return BinaryImage(); + } + + if (src_rect.intersected(src.rect()) != src_rect) { + throw std::invalid_argument("orthogonalRotation: invalid src_rect"); + } + + switch (degrees % 360) { + case 0: + return rotate0(src, src_rect); + case 90: + case -270: + return rotate90(src, src_rect); + case 180: + case -180: + return rotate180(src, src_rect); + case 270: + case -90: + return rotate270(src, src_rect); + default: + throw std::invalid_argument("orthogonalRotation: invalid angle"); + } } BinaryImage orthogonalRotation(const BinaryImage& src, const int degrees) { - return orthogonalRotation(src, src.rect(), degrees); + return orthogonalRotation(src, src.rect(), degrees); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/PolygonRasterizer.cpp b/imageproc/PolygonRasterizer.cpp index c1a4766af..d317e6886 100644 --- a/imageproc/PolygonRasterizer.cpp +++ b/imageproc/PolygonRasterizer.cpp @@ -17,52 +17,42 @@ */ #include "PolygonRasterizer.h" -#include "PolygonUtils.h" -#include "BinaryImage.h" -#include -#include #include +#include +#include #include #include +#include "BinaryImage.h" +#include "PolygonUtils.h" namespace imageproc { /** * \brief A non-horizontal and non zero-length polygon edge. */ class PolygonRasterizer::Edge { -public: - Edge(const QPointF& top, const QPointF& bottom, int vert_direction); + public: + Edge(const QPointF& top, const QPointF& bottom, int vert_direction); - Edge(const QPointF& from, const QPointF& to); + Edge(const QPointF& from, const QPointF& to); - const QPointF& top() const { - return m_top; - } + const QPointF& top() const { return m_top; } - const QPointF& bottom() const { - return m_bottom; - } + const QPointF& bottom() const { return m_bottom; } - double topY() const { - return m_top.y(); - } + double topY() const { return m_top.y(); } - double bottomY() const { - return m_bottom.y(); - } + double bottomY() const { return m_bottom.y(); } - double xForY(double y) const; + double xForY(double y) const; - int vertDirection() const { - return m_vertDirection; - } + int vertDirection() const { return m_vertDirection; } -private: - QPointF m_top; - QPointF m_bottom; - double m_deltaX; - double m_reDeltaY; - int m_vertDirection; // 1: down, -1: up + private: + QPointF m_top; + QPointF m_bottom; + double m_deltaX; + double m_reDeltaY; + int m_vertDirection; // 1: down, -1: up }; @@ -70,98 +60,81 @@ class PolygonRasterizer::Edge { * \brief A non-overlaping edge component. */ class PolygonRasterizer::EdgeComponent { -public: - EdgeComponent(const Edge* edge, double top, double bottom) : m_top(top), m_bottom(bottom), m_x(), m_pEdge(edge) { - } + public: + EdgeComponent(const Edge* edge, double top, double bottom) : m_top(top), m_bottom(bottom), m_x(), m_edge(edge) {} - double top() const { - return m_top; - } + double top() const { return m_top; } - double bottom() const { - return m_bottom; - } + double bottom() const { return m_bottom; } - const Edge& edge() const { - return *m_pEdge; - } + const Edge& edge() const { return *m_edge; } - double x() const { - return m_x; - } + double x() const { return m_x; } - void setX(double x) { - m_x = x; - } + void setX(double x) { m_x = x; } -private: - double m_top; - double m_bottom; - double m_x; - const Edge* m_pEdge; + private: + double m_top; + double m_bottom; + double m_x; + const Edge* m_edge; }; class PolygonRasterizer::EdgeOrderY { -public: - bool operator()(const EdgeComponent& lhs, const EdgeComponent& rhs) const { - return lhs.top() < rhs.top(); - } + public: + bool operator()(const EdgeComponent& lhs, const EdgeComponent& rhs) const { return lhs.top() < rhs.top(); } - bool operator()(const EdgeComponent& lhs, double rhs) const { - return lhs.bottom() <= rhs; // bottom is not a part of the interval. - } + bool operator()(const EdgeComponent& lhs, double rhs) const { + return lhs.bottom() <= rhs; // bottom is not a part of the interval. + } - bool operator()(double lhs, const EdgeComponent& rhs) const { - return lhs < rhs.top(); - } + bool operator()(double lhs, const EdgeComponent& rhs) const { return lhs < rhs.top(); } }; class PolygonRasterizer::EdgeOrderX { -public: - bool operator()(const EdgeComponent& lhs, const EdgeComponent& rhs) const { - return lhs.x() < rhs.x(); - } + public: + bool operator()(const EdgeComponent& lhs, const EdgeComponent& rhs) const { return lhs.x() < rhs.x(); } }; class PolygonRasterizer::Rasterizer { -public: - Rasterizer(const QRect& image_rect, const QPolygonF& poly, Qt::FillRule fill_rule, bool invert); + public: + Rasterizer(const QRect& image_rect, const QPolygonF& poly, Qt::FillRule fill_rule, bool invert); - void fillBinary(BinaryImage& image, BWColor color) const; + void fillBinary(BinaryImage& image, BWColor color) const; - void fillGrayscale(QImage& image, uint8_t color) const; + void fillGrayscale(QImage& image, uint8_t color) const; -private: - void prepareEdges(); + private: + void prepareEdges(); - static void oddEvenLineBinary(const EdgeComponent* edges, int num_edges, uint32_t* line, uint32_t pattern); + static void oddEvenLineBinary(const EdgeComponent* edges, int num_edges, uint32_t* line, uint32_t pattern); - static void oddEvenLineGrayscale(const EdgeComponent* edges, int num_edges, uint8_t* line, uint8_t color); + static void oddEvenLineGrayscale(const EdgeComponent* edges, int num_edges, uint8_t* line, uint8_t color); - static void windingLineBinary(const EdgeComponent* edges, - int num_edges, - uint32_t* line, - uint32_t pattern, - bool invert); + static void windingLineBinary(const EdgeComponent* edges, + int num_edges, + uint32_t* line, + uint32_t pattern, + bool invert); - static void windingLineGrayscale(const EdgeComponent* edges, - int num_edges, - uint8_t* line, - uint8_t color, - bool invert); + static void windingLineGrayscale(const EdgeComponent* edges, + int num_edges, + uint8_t* line, + uint8_t color, + bool invert); - static void fillBinarySegment(int x_from, int x_to, uint32_t* line, uint32_t pattern); + static void fillBinarySegment(int x_from, int x_to, uint32_t* line, uint32_t pattern); - std::vector m_edges; // m_edgeComponents references m_edges. - std::vector m_edgeComponents; - QRect m_imageRect; - QPolygonF m_fillPoly; - QRectF m_boundingBox; - Qt::FillRule m_fillRule; - bool m_invert; + std::vector m_edges; // m_edgeComponents references m_edges. + std::vector m_edgeComponents; + QRect m_imageRect; + QPolygonF m_fillPoly; + QRectF m_boundingBox; + Qt::FillRule m_fillRule; + bool m_invert; }; @@ -171,84 +144,83 @@ void PolygonRasterizer::fill(BinaryImage& image, const BWColor color, const QPolygonF& poly, const Qt::FillRule fill_rule) { - if (image.isNull()) { - throw std::invalid_argument("PolygonRasterizer: target image is null"); - } + if (image.isNull()) { + throw std::invalid_argument("PolygonRasterizer: target image is null"); + } - Rasterizer rasterizer(image.rect(), poly, fill_rule, false); - rasterizer.fillBinary(image, color); + Rasterizer rasterizer(image.rect(), poly, fill_rule, false); + rasterizer.fillBinary(image, color); } void PolygonRasterizer::fillExcept(BinaryImage& image, const BWColor color, const QPolygonF& poly, const Qt::FillRule fill_rule) { - if (image.isNull()) { - throw std::invalid_argument("PolygonRasterizer: target image is null"); - } + if (image.isNull()) { + throw std::invalid_argument("PolygonRasterizer: target image is null"); + } - Rasterizer rasterizer(image.rect(), poly, fill_rule, true); - rasterizer.fillBinary(image, color); + Rasterizer rasterizer(image.rect(), poly, fill_rule, true); + rasterizer.fillBinary(image, color); } void PolygonRasterizer::grayFill(QImage& image, const unsigned char color, const QPolygonF& poly, const Qt::FillRule fill_rule) { - if (image.isNull()) { - throw std::invalid_argument("PolygonRasterizer: target image is null"); - } - if ((image.format() != QImage::Format_Indexed8) || !image.isGrayscale()) { - throw std::invalid_argument("PolygonRasterizer: target image is not grayscale"); - } - - Rasterizer rasterizer(image.rect(), poly, fill_rule, false); - rasterizer.fillGrayscale(image, color); + if (image.isNull()) { + throw std::invalid_argument("PolygonRasterizer: target image is null"); + } + if ((image.format() != QImage::Format_Indexed8) || !image.isGrayscale()) { + throw std::invalid_argument("PolygonRasterizer: target image is not grayscale"); + } + + Rasterizer rasterizer(image.rect(), poly, fill_rule, false); + rasterizer.fillGrayscale(image, color); } void PolygonRasterizer::grayFillExcept(QImage& image, const unsigned char color, const QPolygonF& poly, const Qt::FillRule fill_rule) { - if (image.isNull()) { - throw std::invalid_argument("PolygonRasterizer: target image is null"); - } - if ((image.format() != QImage::Format_Indexed8) || !image.isGrayscale()) { - throw std::invalid_argument("PolygonRasterizer: target image is not grayscale"); - } - - Rasterizer rasterizer(image.rect(), poly, fill_rule, true); - rasterizer.fillGrayscale(image, color); + if (image.isNull()) { + throw std::invalid_argument("PolygonRasterizer: target image is null"); + } + if ((image.format() != QImage::Format_Indexed8) || !image.isGrayscale()) { + throw std::invalid_argument("PolygonRasterizer: target image is not grayscale"); + } + + Rasterizer rasterizer(image.rect(), poly, fill_rule, true); + rasterizer.fillGrayscale(image, color); } /*======================= PolygonRasterizer::Edge ==========================*/ PolygonRasterizer::Edge::Edge(const QPointF& top, const QPointF& bottom, const int vert_direction) - : m_top(top), - m_bottom(bottom), - m_deltaX(bottom.x() - top.x()), - m_reDeltaY(1.0 / (bottom.y() - top.y())), - m_vertDirection(vert_direction) { -} + : m_top(top), + m_bottom(bottom), + m_deltaX(bottom.x() - top.x()), + m_reDeltaY(1.0 / (bottom.y() - top.y())), + m_vertDirection(vert_direction) {} PolygonRasterizer::Edge::Edge(const QPointF& from, const QPointF& to) { - if (from.y() < to.y()) { - m_vertDirection = 1; - m_top = from; - m_bottom = to; - } else { - m_vertDirection = -1; - m_top = to; - m_bottom = from; - } - m_deltaX = m_bottom.x() - m_top.x(); - m_reDeltaY = 1.0 / (m_bottom.y() - m_top.y()); + if (from.y() < to.y()) { + m_vertDirection = 1; + m_top = from; + m_bottom = to; + } else { + m_vertDirection = -1; + m_top = to; + m_bottom = from; + } + m_deltaX = m_bottom.x() - m_top.x(); + m_reDeltaY = 1.0 / (m_bottom.y() - m_top.y()); } double PolygonRasterizer::Edge::xForY(double y) const { - const double fraction = (y - m_top.y()) * m_reDeltaY; + const double fraction = (y - m_top.y()) * m_reDeltaY; - return m_top.x() + m_deltaX * fraction; + return m_top.x() + m_deltaX * fraction; } /*=================== PolygonRasterizer::Rasterizer ====================*/ @@ -257,189 +229,187 @@ PolygonRasterizer::Rasterizer::Rasterizer(const QRect& image_rect, const QPolygonF& poly, const Qt::FillRule fill_rule, const bool invert) - : m_imageRect(image_rect), m_fillRule(fill_rule), m_invert(invert) { - QPainterPath path1; - path1.setFillRule(fill_rule); - path1.addRect(image_rect); + : m_imageRect(image_rect), m_fillRule(fill_rule), m_invert(invert) { + QPainterPath path1; + path1.setFillRule(fill_rule); + path1.addRect(image_rect); - QPainterPath path2; - path2.setFillRule(fill_rule); - path2.addPolygon(PolygonUtils::round(poly)); - path2.closeSubpath(); + QPainterPath path2; + path2.setFillRule(fill_rule); + path2.addPolygon(PolygonUtils::round(poly)); + path2.closeSubpath(); - m_fillPoly = path1.intersected(path2).toFillPolygon(); + m_fillPoly = path1.intersected(path2).toFillPolygon(); - if (invert) { - m_boundingBox = path1.subtracted(path2).boundingRect(); - } else { - m_boundingBox = m_fillPoly.boundingRect(); - } + if (invert) { + m_boundingBox = path1.subtracted(path2).boundingRect(); + } else { + m_boundingBox = m_fillPoly.boundingRect(); + } - prepareEdges(); + prepareEdges(); } void PolygonRasterizer::Rasterizer::prepareEdges() { - const int num_verts = m_fillPoly.size(); - if (num_verts == 0) { - return; - } + const int num_verts = m_fillPoly.size(); + if (num_verts == 0) { + return; + } + + // Collect the edges, excluding horizontal and null ones. + m_edges.reserve(num_verts + 2); + for (int i = 0; i < num_verts - 1; ++i) { + const QPointF from(m_fillPoly[i]); + const QPointF to(m_fillPoly[i + 1]); + if (from.y() != to.y()) { + m_edges.emplace_back(from, to); + } + } + + assert(m_fillPoly.isClosed()); + + if (m_invert) { + // Add left and right edges with neutral direction (0), + // to avoid confusing a winding fill. + const QRectF rect(m_imageRect); + m_edges.emplace_back(rect.topLeft(), rect.bottomLeft(), 0); + m_edges.emplace_back(rect.topRight(), rect.bottomRight(), 0); + } + + // Create an ordered list of y coordinates of polygon vertexes. + std::vector y_values; + y_values.reserve(num_verts + 2); + for (const QPointF& pt : m_fillPoly) { + y_values.push_back(pt.y()); + } + + if (m_invert) { + y_values.push_back(0.0); + y_values.push_back(m_imageRect.height()); + } + + // Sort and remove duplicates. + std::sort(y_values.begin(), y_values.end()); + y_values.erase(std::unique(y_values.begin(), y_values.end()), y_values.end()); + + // Break edges into non-overlaping components, then sort them. + m_edgeComponents.reserve(m_edges.size()); + for (const Edge& edge : m_edges) { + auto it(std::lower_bound(y_values.begin(), y_values.end(), edge.topY())); + + assert(*it == edge.topY()); + + do { + auto next(it); + ++next; + assert(next != y_values.end()); + m_edgeComponents.emplace_back(&edge, *it, *next); + it = next; + } while (*it != edge.bottomY()); + } + + std::sort(m_edgeComponents.begin(), m_edgeComponents.end(), EdgeOrderY()); +} // PolygonRasterizer::Rasterizer::prepareEdges - // Collect the edges, excluding horizontal and null ones. - m_edges.reserve(num_verts + 2); - for (int i = 0; i < num_verts - 1; ++i) { - const QPointF from(m_fillPoly[i]); - const QPointF to(m_fillPoly[i + 1]); - if (from.y() != to.y()) { - m_edges.emplace_back(from, to); - } - } +void PolygonRasterizer::Rasterizer::fillBinary(BinaryImage& image, const BWColor color) const { + std::vector edges_for_line; + typedef std::vector::const_iterator EdgeIter; - assert(m_fillPoly.isClosed()); + uint32_t* line = image.data(); + const int wpl = image.wordsPerLine(); + const uint32_t pattern = (color == WHITE) ? 0 : ~uint32_t(0); - if (m_invert) { - // Add left and right edges with neutral direction (0), - // to avoid confusing a winding fill. - const QRectF rect(m_imageRect); - m_edges.emplace_back(rect.topLeft(), rect.bottomLeft(), 0); - m_edges.emplace_back(rect.topRight(), rect.bottomRight(), 0); + int i = qRound(m_boundingBox.top()); + line += i * wpl; + const int limit = qRound(m_boundingBox.bottom()); + for (; i < limit; ++i, line += wpl, edges_for_line.clear()) { + const double y = i + 0.5; + + // Get edges intersecting this horizontal line. + const std::pair range( + std::equal_range(m_edgeComponents.begin(), m_edgeComponents.end(), y, EdgeOrderY())); + + if (range.first == range.second) { + continue; } - // Create an ordered list of y coordinates of polygon vertexes. - std::vector y_values; - y_values.reserve(num_verts + 2); - for (const QPointF& pt : m_fillPoly) { - y_values.push_back(pt.y()); + std::copy(range.first, range.second, std::back_inserter(edges_for_line)); + + // Calculate the intersection point of each edge with + // the current horizontal line. + for (EdgeComponent& ecomp : edges_for_line) { + ecomp.setX(ecomp.edge().xForY(y)); } + // Sort edge components by the x value of the intersection point. + std::sort(edges_for_line.begin(), edges_for_line.end(), EdgeOrderX()); - if (m_invert) { - y_values.push_back(0.0); - y_values.push_back(m_imageRect.height()); + if (m_fillRule == Qt::OddEvenFill) { + oddEvenLineBinary(&edges_for_line.front(), static_cast(edges_for_line.size()), line, pattern); + } else { + windingLineBinary(&edges_for_line.front(), static_cast(edges_for_line.size()), line, pattern, m_invert); } + } +} // PolygonRasterizer::Rasterizer::fillBinary + +void PolygonRasterizer::Rasterizer::fillGrayscale(QImage& image, const uint8_t color) const { + std::vector edges_for_line; + typedef std::vector::const_iterator EdgeIter; - // Sort and remove duplicates. - std::sort(y_values.begin(), y_values.end()); - y_values.erase(std::unique(y_values.begin(), y_values.end()), y_values.end()); + uint8_t* line = image.bits(); + const int bpl = image.bytesPerLine(); - // Break edges into non-overlaping components, then sort them. - m_edgeComponents.reserve(m_edges.size()); - for (const Edge& edge : m_edges) { - auto it(std::lower_bound(y_values.begin(), y_values.end(), edge.topY())); + int i = qRound(m_boundingBox.top()); + line += i * bpl; + const int limit = qRound(m_boundingBox.bottom()); + for (; i < limit; ++i, line += bpl, edges_for_line.clear()) { + const double y = i + 0.5; - assert(*it == edge.topY()); + // Get edges intersecting this horizontal line. + const std::pair range( + std::equal_range(m_edgeComponents.begin(), m_edgeComponents.end(), y, EdgeOrderY())); - do { - auto next(it); - ++next; - assert(next != y_values.end()); - m_edgeComponents.emplace_back(&edge, *it, *next); - it = next; - } while (*it != edge.bottomY()); + if (range.first == range.second) { + continue; } - std::sort(m_edgeComponents.begin(), m_edgeComponents.end(), EdgeOrderY()); -} // PolygonRasterizer::Rasterizer::prepareEdges + std::copy(range.first, range.second, std::back_inserter(edges_for_line)); -void PolygonRasterizer::Rasterizer::fillBinary(BinaryImage& image, const BWColor color) const { - std::vector edges_for_line; - typedef std::vector::const_iterator EdgeIter; - - uint32_t* line = image.data(); - const int wpl = image.wordsPerLine(); - const uint32_t pattern = (color == WHITE) ? 0 : ~uint32_t(0); - - int i = qRound(m_boundingBox.top()); - line += i * wpl; - const int limit = qRound(m_boundingBox.bottom()); - for (; i < limit; ++i, line += wpl, edges_for_line.clear()) { - const double y = i + 0.5; - - // Get edges intersecting this horizontal line. - const std::pair range( - std::equal_range(m_edgeComponents.begin(), m_edgeComponents.end(), y, EdgeOrderY())); - - if (range.first == range.second) { - continue; - } - - std::copy(range.first, range.second, std::back_inserter(edges_for_line)); - - // Calculate the intersection point of each edge with - // the current horizontal line. - for (EdgeComponent& ecomp : edges_for_line) { - ecomp.setX(ecomp.edge().xForY(y)); - } - // Sort edge components by the x value of the intersection point. - std::sort(edges_for_line.begin(), edges_for_line.end(), EdgeOrderX()); - - if (m_fillRule == Qt::OddEvenFill) { - oddEvenLineBinary(&edges_for_line.front(), static_cast(edges_for_line.size()), line, pattern); - } else { - windingLineBinary(&edges_for_line.front(), static_cast(edges_for_line.size()), line, pattern, - m_invert); - } + // Calculate the intersection point of each edge with + // the current horizontal line. + for (EdgeComponent& ecomp : edges_for_line) { + ecomp.setX(ecomp.edge().xForY(y)); } -} // PolygonRasterizer::Rasterizer::fillBinary + // Sort edge components by the x value of the intersection point. + std::sort(edges_for_line.begin(), edges_for_line.end(), EdgeOrderX()); -void PolygonRasterizer::Rasterizer::fillGrayscale(QImage& image, const uint8_t color) const { - std::vector edges_for_line; - typedef std::vector::const_iterator EdgeIter; - - uint8_t* line = image.bits(); - const int bpl = image.bytesPerLine(); - - int i = qRound(m_boundingBox.top()); - line += i * bpl; - const int limit = qRound(m_boundingBox.bottom()); - for (; i < limit; ++i, line += bpl, edges_for_line.clear()) { - const double y = i + 0.5; - - // Get edges intersecting this horizontal line. - const std::pair range( - std::equal_range(m_edgeComponents.begin(), m_edgeComponents.end(), y, EdgeOrderY())); - - if (range.first == range.second) { - continue; - } - - std::copy(range.first, range.second, std::back_inserter(edges_for_line)); - - // Calculate the intersection point of each edge with - // the current horizontal line. - for (EdgeComponent& ecomp : edges_for_line) { - ecomp.setX(ecomp.edge().xForY(y)); - } - // Sort edge components by the x value of the intersection point. - std::sort(edges_for_line.begin(), edges_for_line.end(), EdgeOrderX()); - - if (m_fillRule == Qt::OddEvenFill) { - oddEvenLineGrayscale(&edges_for_line.front(), static_cast(edges_for_line.size()), line, color); - } else { - windingLineGrayscale(&edges_for_line.front(), static_cast(edges_for_line.size()), line, color, - m_invert); - } + if (m_fillRule == Qt::OddEvenFill) { + oddEvenLineGrayscale(&edges_for_line.front(), static_cast(edges_for_line.size()), line, color); + } else { + windingLineGrayscale(&edges_for_line.front(), static_cast(edges_for_line.size()), line, color, m_invert); } + } } // PolygonRasterizer::Rasterizer::fillGrayscale void PolygonRasterizer::Rasterizer::oddEvenLineBinary(const EdgeComponent* const edges, const int num_edges, uint32_t* const line, const uint32_t pattern) { - for (int i = 0; i < num_edges - 1; i += 2) { - const double x_from = edges[i].x(); - const double x_to = edges[i + 1].x(); - fillBinarySegment(qRound(x_from), qRound(x_to), line, pattern); - } + for (int i = 0; i < num_edges - 1; i += 2) { + const double x_from = edges[i].x(); + const double x_to = edges[i + 1].x(); + fillBinarySegment(qRound(x_from), qRound(x_to), line, pattern); + } } void PolygonRasterizer::Rasterizer::oddEvenLineGrayscale(const EdgeComponent* const edges, const int num_edges, uint8_t* const line, const uint8_t color) { - for (int i = 0; i < num_edges - 1; i += 2) { - const int from = qRound(edges[i].x()); - const int to = qRound(edges[i + 1].x()); - memset(line + from, color, to - from); - } + for (int i = 0; i < num_edges - 1; i += 2) { + const int from = qRound(edges[i].x()); + const int to = qRound(edges[i + 1].x()); + memset(line + from, color, to - from); + } } void PolygonRasterizer::Rasterizer::windingLineBinary(const EdgeComponent* const edges, @@ -447,15 +417,15 @@ void PolygonRasterizer::Rasterizer::windingLineBinary(const EdgeComponent* const uint32_t* const line, const uint32_t pattern, bool invert) { - int dir_sum = 0; - for (int i = 0; i < num_edges - 1; ++i) { - dir_sum += edges[i].edge().vertDirection(); - if ((dir_sum == 0) == invert) { - const double x_from = edges[i].x(); - const double x_to = edges[i + 1].x(); - fillBinarySegment(qRound(x_from), qRound(x_to), line, pattern); - } - } + int dir_sum = 0; + for (int i = 0; i < num_edges - 1; ++i) { + dir_sum += edges[i].edge().vertDirection(); + if ((dir_sum == 0) == invert) { + const double x_from = edges[i].x(); + const double x_to = edges[i + 1].x(); + fillBinarySegment(qRound(x_from), qRound(x_to), line, pattern); + } + } } void PolygonRasterizer::Rasterizer::windingLineGrayscale(const EdgeComponent* const edges, @@ -463,51 +433,51 @@ void PolygonRasterizer::Rasterizer::windingLineGrayscale(const EdgeComponent* co uint8_t* const line, const uint8_t color, bool invert) { - int dir_sum = 0; - for (int i = 0; i < num_edges - 1; ++i) { - dir_sum += edges[i].edge().vertDirection(); - if ((dir_sum == 0) == invert) { - const int from = qRound(edges[i].x()); - const int to = qRound(edges[i + 1].x()); - memset(line + from, color, to - from); - } - } + int dir_sum = 0; + for (int i = 0; i < num_edges - 1; ++i) { + dir_sum += edges[i].edge().vertDirection(); + if ((dir_sum == 0) == invert) { + const int from = qRound(edges[i].x()); + const int to = qRound(edges[i + 1].x()); + memset(line + from, color, to - from); + } + } } void PolygonRasterizer::Rasterizer::fillBinarySegment(const int x_from, const int x_to, uint32_t* const line, const uint32_t pattern) { - if (x_from == x_to) { - return; - } - - const uint32_t full_mask = ~uint32_t(0); - const uint32_t first_word_mask = full_mask >> (x_from & 31); - const uint32_t last_word_mask = full_mask << (31 - ((x_to - 1) & 31)); - const int first_word_idx = x_from >> 5; - const int last_word_idx = (x_to - 1) >> 5; // x_to is exclusive - if (first_word_idx == last_word_idx) { - const uint32_t mask = first_word_mask & last_word_mask; - uint32_t& word = line[first_word_idx]; - word = (word & ~mask) | (pattern & mask); - - return; - } - - int i = first_word_idx; - - // First word. - uint32_t& first_word = line[i]; - first_word = (first_word & ~first_word_mask) | (pattern & first_word_mask); - - // Middle words. - for (++i; i < last_word_idx; ++i) { - line[i] = pattern; - } - - // Last word. - uint32_t& last_word = line[i]; - last_word = (last_word & ~last_word_mask) | (pattern & last_word_mask); + if (x_from == x_to) { + return; + } + + const uint32_t full_mask = ~uint32_t(0); + const uint32_t first_word_mask = full_mask >> (x_from & 31); + const uint32_t last_word_mask = full_mask << (31 - ((x_to - 1) & 31)); + const int first_word_idx = x_from >> 5; + const int last_word_idx = (x_to - 1) >> 5; // x_to is exclusive + if (first_word_idx == last_word_idx) { + const uint32_t mask = first_word_mask & last_word_mask; + uint32_t& word = line[first_word_idx]; + word = (word & ~mask) | (pattern & mask); + + return; + } + + int i = first_word_idx; + + // First word. + uint32_t& first_word = line[i]; + first_word = (first_word & ~first_word_mask) | (pattern & first_word_mask); + + // Middle words. + for (++i; i < last_word_idx; ++i) { + line[i] = pattern; + } + + // Last word. + uint32_t& last_word = line[i]; + last_word = (last_word & ~last_word_mask) | (pattern & last_word_mask); } } // namespace imageproc diff --git a/imageproc/PolygonRasterizer.h b/imageproc/PolygonRasterizer.h index f89fdff92..3462fa6b0 100644 --- a/imageproc/PolygonRasterizer.h +++ b/imageproc/PolygonRasterizer.h @@ -19,8 +19,8 @@ #ifndef IMAGEPROC_POLYGONRASTERIZER_H_ #define IMAGEPROC_POLYGONRASTERIZER_H_ -#include "BWColor.h" #include +#include "BWColor.h" class QPolygonF; class QRectF; @@ -30,22 +30,22 @@ namespace imageproc { class BinaryImage; class PolygonRasterizer { -public: - static void fill(BinaryImage& image, BWColor color, const QPolygonF& poly, Qt::FillRule fill_rule); + public: + static void fill(BinaryImage& image, BWColor color, const QPolygonF& poly, Qt::FillRule fill_rule); - static void fillExcept(BinaryImage& image, BWColor color, const QPolygonF& poly, Qt::FillRule fill_rule); + static void fillExcept(BinaryImage& image, BWColor color, const QPolygonF& poly, Qt::FillRule fill_rule); - static void grayFill(QImage& image, unsigned char color, const QPolygonF& poly, Qt::FillRule fill_rule); + static void grayFill(QImage& image, unsigned char color, const QPolygonF& poly, Qt::FillRule fill_rule); - static void grayFillExcept(QImage& image, unsigned char color, const QPolygonF& poly, Qt::FillRule fill_rule); + static void grayFillExcept(QImage& image, unsigned char color, const QPolygonF& poly, Qt::FillRule fill_rule); -private: - class Edge; - class EdgeComponent; - class EdgeOrderY; - class EdgeOrderX; + private: + class Edge; + class EdgeComponent; + class EdgeOrderY; + class EdgeOrderX; - class Rasterizer; + class Rasterizer; }; } // namespace imageproc #endif // ifndef IMAGEPROC_POLYGONRASTERIZER_H_ diff --git a/imageproc/PolygonUtils.cpp b/imageproc/PolygonUtils.cpp index c7969e88c..de1449ae7 100644 --- a/imageproc/PolygonUtils.cpp +++ b/imageproc/PolygonUtils.cpp @@ -17,8 +17,8 @@ */ #include "PolygonUtils.h" -#include #include +#include #include #include @@ -27,195 +27,193 @@ const double PolygonUtils::ROUNDING_MULTIPLIER = 1 << 12; const double PolygonUtils::ROUNDING_RECIP_MULTIPLIER = 1.0 / ROUNDING_MULTIPLIER; class PolygonUtils::Before { -public: - bool operator()(const QPointF& lhs, const QPointF& rhs) const { - return compare(lhs, rhs) < 0; - } - - bool operator()(const QLineF& lhs, const QLineF& rhs) { - int comp = compare(lhs.p1(), rhs.p1()); - if (comp != 0) { - return comp < 0; - } - - return compare(lhs.p2(), rhs.p2()) < 0; + public: + bool operator()(const QPointF& lhs, const QPointF& rhs) const { return compare(lhs, rhs) < 0; } + + bool operator()(const QLineF& lhs, const QLineF& rhs) { + int comp = compare(lhs.p1(), rhs.p1()); + if (comp != 0) { + return comp < 0; + } + + return compare(lhs.p2(), rhs.p2()) < 0; + } + + private: + static int compare(const QPointF& lhs, const QPointF& rhs) { + const double dx = lhs.x() - rhs.x(); + const double dy = lhs.y() - rhs.y(); + if (std::fabs(dx) > std::fabs(dy)) { + if (dx < 0.0) { + return -1; + } else if (dx > 0.0) { + return 1; + } + } else { + if (dy < 0.0) { + return -1; + } else if (dy > 0.0) { + return 1; + } } -private: - static int compare(const QPointF& lhs, const QPointF& rhs) { - const double dx = lhs.x() - rhs.x(); - const double dy = lhs.y() - rhs.y(); - if (std::fabs(dx) > std::fabs(dy)) { - if (dx < 0.0) { - return -1; - } else if (dx > 0.0) { - return 1; - } - } else { - if (dy < 0.0) { - return -1; - } else if (dy > 0.0) { - return 1; - } - } - - return 0; - } + return 0; + } }; QPolygonF PolygonUtils::round(const QPolygonF& poly) { - QPolygonF rounded; - rounded.reserve(poly.size()); + QPolygonF rounded; + rounded.reserve(poly.size()); - for (const QPointF& p : poly) { - rounded.push_back(roundPoint(p)); - } + for (const QPointF& p : poly) { + rounded.push_back(roundPoint(p)); + } - return rounded; + return rounded; } bool PolygonUtils::fuzzyCompare(const QPolygonF& poly1, const QPolygonF& poly2) { - if ((poly1.size() < 2) && (poly2.size() < 2)) { - return true; - } else if ((poly1.size() < 2) || (poly2.size() < 2)) { - return false; - } + if ((poly1.size() < 2) && (poly2.size() < 2)) { + return true; + } else if ((poly1.size() < 2) || (poly2.size() < 2)) { + return false; + } - assert(poly1.size() >= 2 && poly2.size() >= 2); + assert(poly1.size() >= 2 && poly2.size() >= 2); - QPolygonF closed1(poly1); - QPolygonF closed2(poly2); - // Close if necessary. - if (closed1.back() != closed1.front()) { - closed1.push_back(closed1.front()); - } - if (closed2.back() != closed2.front()) { - closed2.push_back(closed2.front()); - } + QPolygonF closed1(poly1); + QPolygonF closed2(poly2); + // Close if necessary. + if (closed1.back() != closed1.front()) { + closed1.push_back(closed1.front()); + } + if (closed2.back() != closed2.front()) { + closed2.push_back(closed2.front()); + } - std::vector edges1(extractAndNormalizeEdges(closed1)); - std::vector edges2(extractAndNormalizeEdges(closed2)); + std::vector edges1(extractAndNormalizeEdges(closed1)); + std::vector edges2(extractAndNormalizeEdges(closed2)); - if (edges1.size() != edges2.size()) { - return false; - } + if (edges1.size() != edges2.size()) { + return false; + } - std::sort(edges1.begin(), edges1.end(), Before()); - std::sort(edges2.begin(), edges2.end(), Before()); + std::sort(edges1.begin(), edges1.end(), Before()); + std::sort(edges2.begin(), edges2.end(), Before()); - return fuzzyCompareImpl(edges1, edges2); + return fuzzyCompareImpl(edges1, edges2); } // PolygonUtils::fuzzyCompare QPointF PolygonUtils::roundPoint(const QPointF& p) { - return QPointF(roundValue(p.x()), roundValue(p.y())); + return QPointF(roundValue(p.x()), roundValue(p.y())); } double PolygonUtils::roundValue(const double val) { - return std::floor(val * ROUNDING_MULTIPLIER + 0.5) * ROUNDING_RECIP_MULTIPLIER; + return std::floor(val * ROUNDING_MULTIPLIER + 0.5) * ROUNDING_RECIP_MULTIPLIER; } std::vector PolygonUtils::extractAndNormalizeEdges(const QPolygonF& poly) { - std::vector edges; - - const int num_edges = poly.size(); - if (num_edges > 1) { - for (int i = 1; i < num_edges; ++i) { - maybeAddNormalizedEdge(edges, poly[i - 1], poly[i]); - } - maybeAddNormalizedEdge(edges, poly[num_edges - 1], poly[0]); + std::vector edges; + + const int num_edges = poly.size(); + if (num_edges > 1) { + for (int i = 1; i < num_edges; ++i) { + maybeAddNormalizedEdge(edges, poly[i - 1], poly[i]); } + maybeAddNormalizedEdge(edges, poly[num_edges - 1], poly[0]); + } - return edges; + return edges; } void PolygonUtils::maybeAddNormalizedEdge(std::vector& edges, const QPointF& p1, const QPointF& p2) { - if (fuzzyCompareImpl(p1, p2)) { - return; - } - - if (Before()(p2, p1)) { - edges.emplace_back(p2, p1); - } else { - edges.emplace_back(p1, p2); - } + if (fuzzyCompareImpl(p1, p2)) { + return; + } + + if (Before()(p2, p1)) { + edges.emplace_back(p2, p1); + } else { + edges.emplace_back(p1, p2); + } } bool PolygonUtils::fuzzyCompareImpl(const std::vector& lines1, const std::vector& lines2) { - assert(lines1.size() == lines2.size()); - const size_t size = lines1.size(); - for (size_t i = 0; i < size; ++i) { - if (!fuzzyCompareImpl(lines1[i], lines2[i])) { - return false; - } + assert(lines1.size() == lines2.size()); + const size_t size = lines1.size(); + for (size_t i = 0; i < size; ++i) { + if (!fuzzyCompareImpl(lines1[i], lines2[i])) { + return false; } + } - return true; + return true; } bool PolygonUtils::fuzzyCompareImpl(const QLineF& line1, const QLineF& line2) { - return fuzzyCompareImpl(line1.p1(), line2.p1()) && fuzzyCompareImpl(line1.p2(), line2.p2()); + return fuzzyCompareImpl(line1.p1(), line2.p1()) && fuzzyCompareImpl(line1.p2(), line2.p2()); } bool PolygonUtils::fuzzyCompareImpl(const QPointF& p1, const QPointF& p2) { - const double dx = std::fabs(p1.x() - p2.x()); - const double dy = std::fabs(p1.y() - p2.y()); + const double dx = std::fabs(p1.x() - p2.x()); + const double dy = std::fabs(p1.y() - p2.y()); - return dx <= ROUNDING_RECIP_MULTIPLIER && dy <= ROUNDING_RECIP_MULTIPLIER; + return dx <= ROUNDING_RECIP_MULTIPLIER && dy <= ROUNDING_RECIP_MULTIPLIER; } namespace { struct LexicographicPointComparator { - bool operator()(const QPointF& p1, const QPointF& p2) const { - if (p1.x() != p2.x()) { - return p1.x() < p2.x(); - } else { - return p1.y() < p2.y(); - } + bool operator()(const QPointF& p1, const QPointF& p2) const { + if (p1.x() != p2.x()) { + return p1.x() < p2.x(); + } else { + return p1.y() < p2.y(); } + } }; // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product. // Returns a positive value, if OAB makes a counter-clockwise turn, // negative for clockwise turn, and zero if the points are collinear. double cross(const QPointF& O, const QPointF& A, const QPointF& B) { - return (A.x() - O.x()) * (B.y() - O.y()) - (A.y() - O.y()) * (B.x() - O.x()); + return (A.x() - O.x()) * (B.y() - O.y()) - (A.y() - O.y()) * (B.x() - O.x()); } } // anonymous namespace QPolygonF PolygonUtils::convexHull(std::vector point_cloud) { - // "Monotone chain" algorithm. - // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain - - const auto n = static_cast(point_cloud.size()); - int k = 0; - std::vector hull(n * 2); - - // Sort points by x, then y. - std::sort(point_cloud.begin(), point_cloud.end(), LexicographicPointComparator()); - - // Build lower hull. - for (int i = 0; i < n; ++i) { - while (k >= 2 && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) { - k--; - } - hull[k++] = point_cloud[i]; + // "Monotone chain" algorithm. + // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain + + const auto n = static_cast(point_cloud.size()); + int k = 0; + std::vector hull(n * 2); + + // Sort points by x, then y. + std::sort(point_cloud.begin(), point_cloud.end(), LexicographicPointComparator()); + + // Build lower hull. + for (int i = 0; i < n; ++i) { + while (k >= 2 && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) { + k--; } - // Build upper hull. - for (int i = n - 2, t = k + 1; i >= 0; --i) { - while (k >= t && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) { - k--; - } - hull[k++] = point_cloud[i]; + hull[k++] = point_cloud[i]; + } + // Build upper hull. + for (int i = n - 2, t = k + 1; i >= 0; --i) { + while (k >= t && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) { + k--; } + hull[k++] = point_cloud[i]; + } - hull.resize(k); + hull.resize(k); - QPolygonF poly(k); - for (const QPointF& pt : hull) { - poly << pt; - } + QPolygonF poly(k); + for (const QPointF& pt : hull) { + poly << pt; + } - return poly; + return poly; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/PolygonUtils.h b/imageproc/PolygonUtils.h index 28f60f16c..70784d56e 100644 --- a/imageproc/PolygonUtils.h +++ b/imageproc/PolygonUtils.h @@ -27,50 +27,50 @@ class QLineF; namespace imageproc { class PolygonUtils { -public: - /** - * \brief Adjust vertices to more round coordinates. - * - * This method exists to workaround bugs in QPainterPath and QPolygonF - * composition operations. It turns out rounding vertex coordinates - * solves many of those bugs. We don't round to integer values, we - * only make very minor adjustments. - */ - static QPolygonF round(const QPolygonF& poly); + public: + /** + * \brief Adjust vertices to more round coordinates. + * + * This method exists to workaround bugs in QPainterPath and QPolygonF + * composition operations. It turns out rounding vertex coordinates + * solves many of those bugs. We don't round to integer values, we + * only make very minor adjustments. + */ + static QPolygonF round(const QPolygonF& poly); - /** - * \brief Test if two polygons are logically equal. - * - * By logical equality we mean that the following differences don't matter: - * \li Direction (clockwise vs counter-clockwise). - * \li Closed vs unclosed. - * \li Tiny differences in vertex coordinates. - * - * \return true if polygons are logically equal, false otherwise. - */ - static bool fuzzyCompare(const QPolygonF& poly1, const QPolygonF& poly2); + /** + * \brief Test if two polygons are logically equal. + * + * By logical equality we mean that the following differences don't matter: + * \li Direction (clockwise vs counter-clockwise). + * \li Closed vs unclosed. + * \li Tiny differences in vertex coordinates. + * + * \return true if polygons are logically equal, false otherwise. + */ + static bool fuzzyCompare(const QPolygonF& poly1, const QPolygonF& poly2); - static QPolygonF convexHull(std::vector point_cloud); + static QPolygonF convexHull(std::vector point_cloud); -private: - class Before; + private: + class Before; - static QPointF roundPoint(const QPointF& p); + static QPointF roundPoint(const QPointF& p); - static double roundValue(double val); + static double roundValue(double val); - static std::vector extractAndNormalizeEdges(const QPolygonF& poly); + static std::vector extractAndNormalizeEdges(const QPolygonF& poly); - static void maybeAddNormalizedEdge(std::vector& edges, const QPointF& p1, const QPointF& p2); + static void maybeAddNormalizedEdge(std::vector& edges, const QPointF& p1, const QPointF& p2); - static bool fuzzyCompareImpl(const std::vector& lines1, const std::vector& lines2); + static bool fuzzyCompareImpl(const std::vector& lines1, const std::vector& lines2); - static bool fuzzyCompareImpl(const QLineF& line1, const QLineF& line2); + static bool fuzzyCompareImpl(const QLineF& line1, const QLineF& line2); - static bool fuzzyCompareImpl(const QPointF& p1, const QPointF& p2); + static bool fuzzyCompareImpl(const QPointF& p1, const QPointF& p2); - static const double ROUNDING_MULTIPLIER; - static const double ROUNDING_RECIP_MULTIPLIER; + static const double ROUNDING_MULTIPLIER; + static const double ROUNDING_RECIP_MULTIPLIER; }; } // namespace imageproc #endif // ifndef IMAGEPROC_POLYGONUTILS_H_ diff --git a/imageproc/PolynomialLine.cpp b/imageproc/PolynomialLine.cpp index 1bb5a1300..569fcbf3d 100644 --- a/imageproc/PolynomialLine.cpp +++ b/imageproc/PolynomialLine.cpp @@ -17,74 +17,74 @@ */ #include "PolynomialLine.h" +#include #include "MatT.h" #include "MatrixCalc.h" -#include namespace imageproc { void PolynomialLine::validateArguments(const int degree, const int num_values) { - if (degree < 0) { - throw std::invalid_argument("PolynomialLine: degree is invalid"); - } - if (num_values <= 0) { - throw std::invalid_argument("PolynomialLine: no data points"); - } + if (degree < 0) { + throw std::invalid_argument("PolynomialLine: degree is invalid"); + } + if (num_values <= 0) { + throw std::invalid_argument("PolynomialLine: no data points"); + } } double PolynomialLine::calcScale(const int num_values) { - if (num_values <= 1) { - return 0.0; - } else { - return 1.0 / (num_values - 1); - } + if (num_values <= 1) { + return 0.0; + } else { + return 1.0 / (num_values - 1); + } } void PolynomialLine::doLeastSquares(const VecT& data_points, VecT& coeffs) { - const auto num_terms = static_cast(coeffs.size()); - const auto num_values = static_cast(data_points.size()); - - // The least squares equation is A^T*A*x = A^T*b - // We will be building A^T*A and A^T*b incrementally. - // This allows us not to build matrix A at all. - MatT AtA(num_terms, num_terms); - VecT Atb(num_terms); + const auto num_terms = static_cast(coeffs.size()); + const auto num_values = static_cast(data_points.size()); - // 1, x, x^2, x^3, ... - VecT powers(num_terms); + // The least squares equation is A^T*A*x = A^T*b + // We will be building A^T*A and A^T*b incrementally. + // This allows us not to build matrix A at all. + MatT AtA(num_terms, num_terms); + VecT Atb(num_terms); - // Pretend that data points are positioned in range of [0, 1]. - const double scale = calcScale(num_values); - for (int x = 0; x < num_values; ++x) { - const double position = x * scale; - const double data_point = data_points[x]; + // 1, x, x^2, x^3, ... + VecT powers(num_terms); - // Fill the powers vector. - double pow = 1.0; - for (int j = 0; j < num_terms; ++j) { - powers[j] = pow; - pow *= position; - } + // Pretend that data points are positioned in range of [0, 1]. + const double scale = calcScale(num_values); + for (int x = 0; x < num_values; ++x) { + const double position = x * scale; + const double data_point = data_points[x]; - // Update AtA and Atb. - for (int i = 0; i < num_terms; ++i) { - const double i_val = powers[i]; - Atb[i] += i_val * data_point; - for (int j = 0; j < num_terms; ++j) { - const double j_val = powers[j]; - AtA(i, j) += i_val * j_val; - } - } + // Fill the powers vector. + double pow = 1.0; + for (int j = 0; j < num_terms; ++j) { + powers[j] = pow; + pow *= position; } - // In case AtA is rank-deficient, we can usually fix it like this: + // Update AtA and Atb. for (int i = 0; i < num_terms; ++i) { - AtA(i, i) += 1e-5; // Add a small value to the diagonal. + const double i_val = powers[i]; + Atb[i] += i_val * data_point; + for (int j = 0; j < num_terms; ++j) { + const double j_val = powers[j]; + AtA(i, j) += i_val * j_val; + } } + } - try { - DynamicMatrixCalc mc; - mc(AtA).solve(mc(Atb)).write(coeffs.data()); - } catch (const std::runtime_error&) { - } + // In case AtA is rank-deficient, we can usually fix it like this: + for (int i = 0; i < num_terms; ++i) { + AtA(i, i) += 1e-5; // Add a small value to the diagonal. + } + + try { + DynamicMatrixCalc mc; + mc(AtA).solve(mc(Atb)).write(coeffs.data()); + } catch (const std::runtime_error&) { + } } // PolynomialLine::doLeastSquares } // namespace imageproc diff --git a/imageproc/PolynomialLine.h b/imageproc/PolynomialLine.h index d4e1b90f9..5da240294 100644 --- a/imageproc/PolynomialLine.h +++ b/imageproc/PolynomialLine.h @@ -19,178 +19,177 @@ #ifndef IMAGEPROC_POLYNOMIAL_LINE_H_ #define IMAGEPROC_POLYNOMIAL_LINE_H_ -#include "VecT.h" -#include #include +#include +#include "VecT.h" namespace imageproc { /** * \brief A polynomial function describing a sequence of numbers. */ class PolynomialLine { - // Member-wise copying is OK. -public: - /** - * \brief Calculate a polynomial that approximates a sequence of values. - * - * \param degree The degree of a polynomial to be constructed. - * If there are too few data points, the degree may - * be silently reduced. The minimum degree is 0. - * \param values The data points to be approximated. - * \param num_values The number of data points to be approximated. - * There has to be at least one data point. - * \param step The distance between adjacent data points. - * The data points will be accessed like this:\n - * values[0], values[step], values[step * 2] - */ - template - PolynomialLine(int degree, const T* values, int num_values, int step); - - /** - * \brief Output the polynomial as a sequence of values. - * - * \param values The data points to be written. If T is - * an integer, the values will be rounded and clipped - * to the minimum and maximum values for type T, - * which are taken from std::numeric_limits. - * Otherwise, a static cast will be used to covert - * values from double to type T. If you need - * some other behaviour, use the overloaded version - * of this method and supply your own post-processor. - * \param num_values The number of data points to write. If this - * number is different from the one that was used to - * construct a polynomial, the output will be scaled - * to fit the new size. - * \param step The distance between adjacent data points. - * The data points will be accessed like this:\n - * values[0], values[step], values[step * 2] - */ - template - void output(T* values, int num_values, int step) const; - - /** - * \brief Output the polynomial as a sequence of values. - * - * \param values The data points to be written. - * \param num_values The number of data points to write. If this - * number is different from the one that was used to - * construct a polynomial, the output will be scaled - * to fit the new size. - * \param step The distance between adjacent data points. - * The data points will be accessed like this:\n - * values[0], values[step], values[step * 2] - * \param pp A functor to convert a double value to type T. - * The functor will be called like this:\n - * T t = pp((double)val); - */ - template - void output(T* values, int num_values, int step, PostProcessor pp) const; - -private: - template - class StaticCastPostProcessor { - public: - T operator()(double val) const; - }; - - - template - class RoundAndClipPostProcessor { - public: - RoundAndClipPostProcessor(); - - T operator()(double val) const; - - private: - T m_min; - T m_max; - }; - - - template - struct DefaultPostProcessor : public StaticCastPostProcessor {}; - - template - struct DefaultPostProcessor : public RoundAndClipPostProcessor {}; - - static void validateArguments(int degree, int num_values); - - static double calcScale(int num_values); - - static void doLeastSquares(const VecT& data_points, VecT& coeffs); - - VecT m_coeffs; + // Member-wise copying is OK. + public: + /** + * \brief Calculate a polynomial that approximates a sequence of values. + * + * \param degree The degree of a polynomial to be constructed. + * If there are too few data points, the degree may + * be silently reduced. The minimum degree is 0. + * \param values The data points to be approximated. + * \param num_values The number of data points to be approximated. + * There has to be at least one data point. + * \param step The distance between adjacent data points. + * The data points will be accessed like this:\n + * values[0], values[step], values[step * 2] + */ + template + PolynomialLine(int degree, const T* values, int num_values, int step); + + /** + * \brief Output the polynomial as a sequence of values. + * + * \param values The data points to be written. If T is + * an integer, the values will be rounded and clipped + * to the minimum and maximum values for type T, + * which are taken from std::numeric_limits. + * Otherwise, a static cast will be used to covert + * values from double to type T. If you need + * some other behaviour, use the overloaded version + * of this method and supply your own post-processor. + * \param num_values The number of data points to write. If this + * number is different from the one that was used to + * construct a polynomial, the output will be scaled + * to fit the new size. + * \param step The distance between adjacent data points. + * The data points will be accessed like this:\n + * values[0], values[step], values[step * 2] + */ + template + void output(T* values, int num_values, int step) const; + + /** + * \brief Output the polynomial as a sequence of values. + * + * \param values The data points to be written. + * \param num_values The number of data points to write. If this + * number is different from the one that was used to + * construct a polynomial, the output will be scaled + * to fit the new size. + * \param step The distance between adjacent data points. + * The data points will be accessed like this:\n + * values[0], values[step], values[step * 2] + * \param pp A functor to convert a double value to type T. + * The functor will be called like this:\n + * T t = pp((double)val); + */ + template + void output(T* values, int num_values, int step, PostProcessor pp) const; + + private: + template + class StaticCastPostProcessor { + public: + T operator()(double val) const; + }; + + + template + class RoundAndClipPostProcessor { + public: + RoundAndClipPostProcessor(); + + T operator()(double val) const; + + private: + T m_min; + T m_max; + }; + + + template + struct DefaultPostProcessor : public StaticCastPostProcessor {}; + + template + struct DefaultPostProcessor : public RoundAndClipPostProcessor {}; + + static void validateArguments(int degree, int num_values); + + static double calcScale(int num_values); + + static void doLeastSquares(const VecT& data_points, VecT& coeffs); + + VecT m_coeffs; }; -template +template inline T PolynomialLine::StaticCastPostProcessor::operator()(const double val) const { - return static_cast(val); + return static_cast(val); } -template +template PolynomialLine::RoundAndClipPostProcessor::RoundAndClipPostProcessor() - : m_min(std::numeric_limits::min()), m_max(std::numeric_limits::max()) { -} + : m_min(std::numeric_limits::min()), m_max(std::numeric_limits::max()) {} -template +template inline T PolynomialLine::RoundAndClipPostProcessor::operator()(const double val) const { - const double rounded = std::floor(val + 0.5); - if (rounded < m_min) { - return m_min; - } else if (rounded > m_max) { - return m_max; - } else { - return static_cast(rounded); - } + const double rounded = std::floor(val + 0.5); + if (rounded < m_min) { + return m_min; + } else if (rounded > m_max) { + return m_max; + } else { + return static_cast(rounded); + } } -template +template PolynomialLine::PolynomialLine(int degree, const T* values, const int num_values, const int step) { - validateArguments(degree, num_values); + validateArguments(degree, num_values); - if (degree + 1 > num_values) { - degree = num_values - 1; - } + if (degree + 1 > num_values) { + degree = num_values - 1; + } - const int num_terms = degree + 1; + const int num_terms = degree + 1; - VecT data_points(num_values); - for (int i = 0; i < num_values; ++i, values += step) { - data_points[i] = *values; - } + VecT data_points(num_values); + for (int i = 0; i < num_values; ++i, values += step) { + data_points[i] = *values; + } - VecT(num_terms).swap(m_coeffs); - doLeastSquares(data_points, m_coeffs); + VecT(num_terms).swap(m_coeffs); + doLeastSquares(data_points, m_coeffs); } -template +template void PolynomialLine::output(T* values, int num_values, int step) const { - typedef DefaultPostProcessor::is_integer> PP; - output(values, num_values, step, PP()); + typedef DefaultPostProcessor::is_integer> PP; + output(values, num_values, step, PP()); } -template +template void PolynomialLine::output(T* values, int num_values, int step, PostProcessor pp) const { - if (num_values <= 0) { - return; + if (num_values <= 0) { + return; + } + + // Pretend that data points are positioned in range of [0, 1]. + const double scale = calcScale(num_values); + for (int i = 0; i < num_values; ++i, values += step) { + const double position = i * scale; + double sum = 0.0; + double pow = 1.0; + + const double* p_coeffs = m_coeffs.data(); + const double* const p_coeffs_end = p_coeffs + m_coeffs.size(); + for (; p_coeffs != p_coeffs_end; ++p_coeffs, pow *= position) { + sum += *p_coeffs * pow; } - // Pretend that data points are positioned in range of [0, 1]. - const double scale = calcScale(num_values); - for (int i = 0; i < num_values; ++i, values += step) { - const double position = i * scale; - double sum = 0.0; - double pow = 1.0; - - const double* p_coeffs = m_coeffs.data(); - const double* const p_coeffs_end = p_coeffs + m_coeffs.size(); - for (; p_coeffs != p_coeffs_end; ++p_coeffs, pow *= position) { - sum += *p_coeffs * pow; - } - - *values = pp(sum); - } + *values = pp(sum); + } } } // namespace imageproc diff --git a/imageproc/PolynomialSurface.cpp b/imageproc/PolynomialSurface.cpp index ed3846fda..4759532f8 100644 --- a/imageproc/PolynomialSurface.cpp +++ b/imageproc/PolynomialSurface.cpp @@ -17,190 +17,190 @@ */ #include "PolynomialSurface.h" +#include +#include +#include +#include +#include +#include #include "AlignedArray.h" #include "BinaryImage.h" +#include "BitOps.h" #include "GrayImage.h" #include "Grayscale.h" -#include "BitOps.h" -#include #include "MatT.h" -#include "VecT.h" #include "MatrixCalc.h" -#include -#include -#include -#include -#include +#include "VecT.h" namespace imageproc { PolynomialSurface::PolynomialSurface(const int hor_degree, const int vert_degree, const GrayImage& src) - : m_horDegree(hor_degree), m_vertDegree(vert_degree) { - // Note: m_horDegree and m_vertDegree may still change! - - if (hor_degree < 0) { - throw std::invalid_argument("PolynomialSurface: horizontal degree is invalid"); - } - if (vert_degree < 0) { - throw std::invalid_argument("PolynomialSurface: vertical degree is invalid"); - } - - const int num_data_points = src.width() * src.height(); - if (num_data_points == 0) { - m_horDegree = 0; - m_vertDegree = 0; - VecT(1, 0.0).swap(m_coeffs); - return; - } - - maybeReduceDegrees(num_data_points); - - const int num_terms = calcNumTerms(); - VecT(num_terms, 0.0).swap(m_coeffs); - - // The least squares equation is A^T*A*x = A^T*b - // We will be building A^T*A and A^T*b incrementally. - // This allows us not to build matrix A at all. - MatT AtA(num_terms, num_terms); - VecT Atb(num_terms); - prepareDataForLeastSquares(src, AtA, Atb, m_horDegree, m_vertDegree); - - fixSquareMatrixRankDeficiency(AtA); - - try { - DynamicMatrixCalc mc; - mc(AtA).solve(mc(Atb)).write(m_coeffs.data()); - } catch (const std::runtime_error&) { - } + : m_horDegree(hor_degree), m_vertDegree(vert_degree) { + // Note: m_horDegree and m_vertDegree may still change! + + if (hor_degree < 0) { + throw std::invalid_argument("PolynomialSurface: horizontal degree is invalid"); + } + if (vert_degree < 0) { + throw std::invalid_argument("PolynomialSurface: vertical degree is invalid"); + } + + const int num_data_points = src.width() * src.height(); + if (num_data_points == 0) { + m_horDegree = 0; + m_vertDegree = 0; + VecT(1, 0.0).swap(m_coeffs); + return; + } + + maybeReduceDegrees(num_data_points); + + const int num_terms = calcNumTerms(); + VecT(num_terms, 0.0).swap(m_coeffs); + + // The least squares equation is A^T*A*x = A^T*b + // We will be building A^T*A and A^T*b incrementally. + // This allows us not to build matrix A at all. + MatT AtA(num_terms, num_terms); + VecT Atb(num_terms); + prepareDataForLeastSquares(src, AtA, Atb, m_horDegree, m_vertDegree); + + fixSquareMatrixRankDeficiency(AtA); + + try { + DynamicMatrixCalc mc; + mc(AtA).solve(mc(Atb)).write(m_coeffs.data()); + } catch (const std::runtime_error&) { + } } PolynomialSurface::PolynomialSurface(const int hor_degree, const int vert_degree, const GrayImage& src, const BinaryImage& mask) - : m_horDegree(hor_degree), m_vertDegree(vert_degree) { - // Note: m_horDegree and m_vertDegree may still change! - - if (hor_degree < 0) { - throw std::invalid_argument("PolynomialSurface: horizontal degree is invalid"); - } - if (vert_degree < 0) { - throw std::invalid_argument("PolynomialSurface: vertical degree is invalid"); - } - if (src.size() != mask.size()) { - throw std::invalid_argument("PolynomialSurface: image and mask have different sizes"); - } - - const int num_data_points = mask.countBlackPixels(); - if (num_data_points == 0) { - m_horDegree = 0; - m_vertDegree = 0; - VecT(1, 0.0).swap(m_coeffs); - return; - } - - maybeReduceDegrees(num_data_points); - - const int num_terms = calcNumTerms(); - VecT(num_terms, 0.0).swap(m_coeffs); - - // The least squares equation is A^T*A*x = A^T*b - // We will be building A^T*A and A^T*b incrementally. - // This allows us not to build matrix A at all. - MatT AtA(num_terms, num_terms); - VecT Atb(num_terms); - prepareDataForLeastSquares(src, mask, AtA, Atb, m_horDegree, m_vertDegree); - - fixSquareMatrixRankDeficiency(AtA); - - try { - DynamicMatrixCalc mc; - mc(AtA).solve(mc(Atb)).write(m_coeffs.data()); - } catch (const std::runtime_error&) { - } + : m_horDegree(hor_degree), m_vertDegree(vert_degree) { + // Note: m_horDegree and m_vertDegree may still change! + + if (hor_degree < 0) { + throw std::invalid_argument("PolynomialSurface: horizontal degree is invalid"); + } + if (vert_degree < 0) { + throw std::invalid_argument("PolynomialSurface: vertical degree is invalid"); + } + if (src.size() != mask.size()) { + throw std::invalid_argument("PolynomialSurface: image and mask have different sizes"); + } + + const int num_data_points = mask.countBlackPixels(); + if (num_data_points == 0) { + m_horDegree = 0; + m_vertDegree = 0; + VecT(1, 0.0).swap(m_coeffs); + return; + } + + maybeReduceDegrees(num_data_points); + + const int num_terms = calcNumTerms(); + VecT(num_terms, 0.0).swap(m_coeffs); + + // The least squares equation is A^T*A*x = A^T*b + // We will be building A^T*A and A^T*b incrementally. + // This allows us not to build matrix A at all. + MatT AtA(num_terms, num_terms); + VecT Atb(num_terms); + prepareDataForLeastSquares(src, mask, AtA, Atb, m_horDegree, m_vertDegree); + + fixSquareMatrixRankDeficiency(AtA); + + try { + DynamicMatrixCalc mc; + mc(AtA).solve(mc(Atb)).write(m_coeffs.data()); + } catch (const std::runtime_error&) { + } } GrayImage PolynomialSurface::render(const QSize& size) const { - if (size.isEmpty()) { - return GrayImage(); + if (size.isEmpty()) { + return GrayImage(); + } + + GrayImage image(size); + const int width = size.width(); + const int height = size.height(); + unsigned char* line = image.data(); + const int bpl = image.stride(); + const auto num_coeffs = static_cast(m_coeffs.size()); + + // Pretend that both x and y positions of pixels + // lie in range of [0, 1]. + const double xscale = calcScale(width); + const double yscale = calcScale(height); + + AlignedArray vert_matrix(num_coeffs * height); + float* out = &vert_matrix[0]; + for (int y = 0; y < height; ++y) { + const double y_adjusted = y * yscale; + double pow = 1.0; + int pos = 0; + for (int i = 0; i <= m_vertDegree; ++i) { + for (int j = 0; j <= m_horDegree; ++j, ++pos, ++out) { + *out = static_cast(m_coeffs[pos] * pow); + } + pow *= y_adjusted; } - - GrayImage image(size); - const int width = size.width(); - const int height = size.height(); - unsigned char* line = image.data(); - const int bpl = image.stride(); - const auto num_coeffs = static_cast(m_coeffs.size()); - - // Pretend that both x and y positions of pixels - // lie in range of [0, 1]. - const double xscale = calcScale(width); - const double yscale = calcScale(height); - - AlignedArray vert_matrix(num_coeffs * height); - float* out = &vert_matrix[0]; - for (int y = 0; y < height; ++y) { - const double y_adjusted = y * yscale; - double pow = 1.0; - int pos = 0; - for (int i = 0; i <= m_vertDegree; ++i) { - for (int j = 0; j <= m_horDegree; ++j, ++pos, ++out) { - *out = static_cast(m_coeffs[pos] * pow); - } - pow *= y_adjusted; - } - } - - AlignedArray hor_matrix(num_coeffs * width); - out = &hor_matrix[0]; - for (int x = 0; x < width; ++x) { - const double x_adjusted = x * xscale; - for (int i = 0; i <= m_vertDegree; ++i) { - double pow = 1.0; - for (int j = 0; j <= m_horDegree; ++j, ++out) { - *out = static_cast(pow); - pow *= x_adjusted; - } - } + } + + AlignedArray hor_matrix(num_coeffs * width); + out = &hor_matrix[0]; + for (int x = 0; x < width; ++x) { + const double x_adjusted = x * xscale; + for (int i = 0; i <= m_vertDegree; ++i) { + double pow = 1.0; + for (int j = 0; j <= m_horDegree; ++j, ++out) { + *out = static_cast(pow); + pow *= x_adjusted; + } } - - const float* vert_line = &vert_matrix[0]; - for (int y = 0; y < height; ++y, line += bpl, vert_line += num_coeffs) { - const float* hor_line = &hor_matrix[0]; - for (int x = 0; x < width; ++x, hor_line += num_coeffs) { - float sum = 0.5f / 255.0f; // for rounding purposes. - for (int i = 0; i < num_coeffs; ++i) { - sum += hor_line[i] * vert_line[i]; - } - const auto isum = (int) (sum * 255.0); - line[x] = static_cast(qBound(0, isum, 255)); - } + } + + const float* vert_line = &vert_matrix[0]; + for (int y = 0; y < height; ++y, line += bpl, vert_line += num_coeffs) { + const float* hor_line = &hor_matrix[0]; + for (int x = 0; x < width; ++x, hor_line += num_coeffs) { + float sum = 0.5f / 255.0f; // for rounding purposes. + for (int i = 0; i < num_coeffs; ++i) { + sum += hor_line[i] * vert_line[i]; + } + const auto isum = (int) (sum * 255.0); + line[x] = static_cast(qBound(0, isum, 255)); } + } - return image; + return image; } // PolynomialSurface::render void PolynomialSurface::maybeReduceDegrees(const int num_data_points) { - assert(num_data_points > 0); + assert(num_data_points > 0); - while (num_data_points < calcNumTerms()) { - if (m_horDegree > m_vertDegree) { - --m_horDegree; - } else { - --m_vertDegree; - } + while (num_data_points < calcNumTerms()) { + if (m_horDegree > m_vertDegree) { + --m_horDegree; + } else { + --m_vertDegree; } + } } int PolynomialSurface::calcNumTerms() const { - return (m_horDegree + 1) * (m_vertDegree + 1); + return (m_horDegree + 1) * (m_vertDegree + 1); } double PolynomialSurface::calcScale(const int dimension) { - if (dimension <= 1) { - return 0.0; - } else { - return 1.0 / (dimension - 1); - } + if (dimension <= 1) { + return 0.0; + } else { + return 1.0 / (dimension - 1); + } } void PolynomialSurface::prepareDataForLeastSquares(const GrayImage& image, @@ -208,73 +208,73 @@ void PolynomialSurface::prepareDataForLeastSquares(const GrayImage& image, VecT& Atb, const int h_degree, const int v_degree) { - double* const AtA_data = AtA.data(); - double* const Atb_data = Atb.data(); - - const int width = image.width(); - const int height = image.height(); - const auto num_terms = static_cast(Atb.size()); - - const uint8_t* line = image.data(); - const int stride = image.stride(); - - // Pretend that both x and y positions of pixels - // lie in range of [0, 1]. - const double xscale = calcScale(width); - const double yscale = calcScale(height); + double* const AtA_data = AtA.data(); + double* const Atb_data = Atb.data(); + + const int width = image.width(); + const int height = image.height(); + const auto num_terms = static_cast(Atb.size()); + + const uint8_t* line = image.data(); + const int stride = image.stride(); + + // Pretend that both x and y positions of pixels + // lie in range of [0, 1]. + const double xscale = calcScale(width); + const double yscale = calcScale(height); + + // To force data samples into [0, 1] range. + const double data_scale = 1.0 / 255.0; + + // 1, y, y^2, y^3, ... + VecT y_powers(v_degree + 1); // Initialized to 0. + + // Same as y_powers, except y_powers correspond to a given y, + // while x_powers are computed for all possible x values. + MatT x_powers(h_degree + 1, width); // Initialized to 0. + for (int x = 0; x < width; ++x) { + const double x_adjusted = xscale * x; + double x_power = 1.0; + for (int i = 0; i <= h_degree; ++i) { + x_powers(i, x) = x_power; + x_power *= x_adjusted; + } + } - // To force data samples into [0, 1] range. - const double data_scale = 1.0 / 255.0; + VecT full_powers(num_terms); - // 1, y, y^2, y^3, ... - VecT y_powers(v_degree + 1); // Initialized to 0. + for (int y = 0; y < height; ++y, line += stride) { + const double y_adjusted = yscale * y; - // Same as y_powers, except y_powers correspond to a given y, - // while x_powers are computed for all possible x values. - MatT x_powers(h_degree + 1, width); // Initialized to 0. - for (int x = 0; x < width; ++x) { - const double x_adjusted = xscale * x; - double x_power = 1.0; - for (int i = 0; i <= h_degree; ++i) { - x_powers(i, x) = x_power; - x_power *= x_adjusted; - } + double y_power = 1.0; + for (int i = 0; i <= v_degree; ++i) { + y_powers[i] = y_power; + y_power *= y_adjusted; } - VecT full_powers(num_terms); - - for (int y = 0; y < height; ++y, line += stride) { - const double y_adjusted = yscale * y; + for (int x = 0; x < width; ++x) { + const double data_point = data_scale * line[x]; - double y_power = 1.0; - for (int i = 0; i <= v_degree; ++i) { - y_powers[i] = y_power; - y_power *= y_adjusted; + int pos = 0; + for (int i = 0; i <= v_degree; ++i) { + for (int j = 0; j <= h_degree; ++j, ++pos) { + full_powers[pos] = y_powers[i] * x_powers(j, x); } + } - for (int x = 0; x < width; ++x) { - const double data_point = data_scale * line[x]; - - int pos = 0; - for (int i = 0; i <= v_degree; ++i) { - for (int j = 0; j <= h_degree; ++j, ++pos) { - full_powers[pos] = y_powers[i] * x_powers(j, x); - } - } - - double* p_AtA = AtA_data; - for (int i = 0; i < num_terms; ++i) { - const double i_val = full_powers[i]; - Atb_data[i] += i_val * data_point; - - for (int j = 0; j < num_terms; ++j) { - const double j_val = full_powers[j]; - *p_AtA += i_val * j_val; - ++p_AtA; - } - } + double* p_AtA = AtA_data; + for (int i = 0; i < num_terms; ++i) { + const double i_val = full_powers[i]; + Atb_data[i] += i_val * data_point; + + for (int j = 0; j < num_terms; ++j) { + const double j_val = full_powers[j]; + *p_AtA += i_val * j_val; + ++p_AtA; } + } } + } } // PolynomialSurface::prepareDataForLeastSquares void PolynomialSurface::prepareDataForLeastSquares(const GrayImage& image, @@ -283,92 +283,92 @@ void PolynomialSurface::prepareDataForLeastSquares(const GrayImage& image, VecT& Atb, const int h_degree, const int v_degree) { - double* const AtA_data = AtA.data(); - double* const Atb_data = Atb.data(); - - const int width = image.width(); - const int height = image.height(); - const auto num_terms = static_cast(Atb.size()); - - const uint8_t* image_line = image.data(); - const int image_stride = image.stride(); - - const uint32_t* mask_line = mask.data(); - const int mask_stride = mask.wordsPerLine(); - - // Pretend that both x and y positions of pixels - // lie in range of [0, 1]. - const double xscale = calcScale(width); - const double yscale = calcScale(height); + double* const AtA_data = AtA.data(); + double* const Atb_data = Atb.data(); + + const int width = image.width(); + const int height = image.height(); + const auto num_terms = static_cast(Atb.size()); + + const uint8_t* image_line = image.data(); + const int image_stride = image.stride(); + + const uint32_t* mask_line = mask.data(); + const int mask_stride = mask.wordsPerLine(); + + // Pretend that both x and y positions of pixels + // lie in range of [0, 1]. + const double xscale = calcScale(width); + const double yscale = calcScale(height); + + // To force data samples into [0, 1] range. + const double data_scale = 1.0 / 255.0; + + // 1, y, y^2, y^3, ... + VecT y_powers(v_degree + 1); // Initialized to 0. + + // Same as y_powers, except y_powers correspond to a given y, + // while x_powers are computed for all possible x values. + MatT x_powers(h_degree + 1, width); // Initialized to 0. + for (int x = 0; x < width; ++x) { + const double x_adjusted = xscale * x; + double x_power = 1.0; + for (int i = 0; i <= h_degree; ++i) { + x_powers(i, x) = x_power; + x_power *= x_adjusted; + } + } - // To force data samples into [0, 1] range. - const double data_scale = 1.0 / 255.0; + VecT full_powers(num_terms); - // 1, y, y^2, y^3, ... - VecT y_powers(v_degree + 1); // Initialized to 0. + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + const double y_adjusted = yscale * y; - // Same as y_powers, except y_powers correspond to a given y, - // while x_powers are computed for all possible x values. - MatT x_powers(h_degree + 1, width); // Initialized to 0. - for (int x = 0; x < width; ++x) { - const double x_adjusted = xscale * x; - double x_power = 1.0; - for (int i = 0; i <= h_degree; ++i) { - x_powers(i, x) = x_power; - x_power *= x_adjusted; - } + double y_power = 1.0; + for (int i = 0; i <= v_degree; ++i) { + y_powers[i] = y_power; + y_power *= y_adjusted; } - VecT full_powers(num_terms); + for (int x = 0; x < width; ++x) { + if (!(mask_line[x >> 5] & (msb >> (x & 31)))) { + continue; + } - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - const double y_adjusted = yscale * y; + const double data_point = data_scale * image_line[x]; - double y_power = 1.0; - for (int i = 0; i <= v_degree; ++i) { - y_powers[i] = y_power; - y_power *= y_adjusted; + int pos = 0; + for (int i = 0; i <= v_degree; ++i) { + for (int j = 0; j <= h_degree; ++j, ++pos) { + full_powers[pos] = y_powers[i] * x_powers(j, x); } + } - for (int x = 0; x < width; ++x) { - if (!(mask_line[x >> 5] & (msb >> (x & 31)))) { - continue; - } - - const double data_point = data_scale * image_line[x]; - - int pos = 0; - for (int i = 0; i <= v_degree; ++i) { - for (int j = 0; j <= h_degree; ++j, ++pos) { - full_powers[pos] = y_powers[i] * x_powers(j, x); - } - } - - double* p_AtA = AtA_data; - for (int i = 0; i < num_terms; ++i) { - const double i_val = full_powers[i]; - Atb_data[i] += i_val * data_point; - - for (int j = 0; j < num_terms; ++j) { - const double j_val = full_powers[j]; - *p_AtA += i_val * j_val; - ++p_AtA; - } - } - } + double* p_AtA = AtA_data; + for (int i = 0; i < num_terms; ++i) { + const double i_val = full_powers[i]; + Atb_data[i] += i_val * data_point; - image_line += image_stride; - mask_line += mask_stride; + for (int j = 0; j < num_terms; ++j) { + const double j_val = full_powers[j]; + *p_AtA += i_val * j_val; + ++p_AtA; + } + } } + + image_line += image_stride; + mask_line += mask_stride; + } } // PolynomialSurface::prepareDataForLeastSquares void PolynomialSurface::fixSquareMatrixRankDeficiency(MatT& mat) { - assert(mat.cols() == mat.rows()); + assert(mat.cols() == mat.rows()); - const auto dim = static_cast(mat.cols()); - for (int i = 0; i < dim; ++i) { - mat(i, i) += 1e-5; // Add a small value to the diagonal. - } + const auto dim = static_cast(mat.cols()); + for (int i = 0; i < dim; ++i) { + mat(i, i) += 1e-5; // Add a small value to the diagonal. + } } } // namespace imageproc diff --git a/imageproc/PolynomialSurface.h b/imageproc/PolynomialSurface.h index 8a43bf675..467d2e721 100644 --- a/imageproc/PolynomialSurface.h +++ b/imageproc/PolynomialSurface.h @@ -19,10 +19,10 @@ #ifndef IMAGEPROC_POLYNOMIAL_SURFACE_H_ #define IMAGEPROC_POLYNOMIAL_SURFACE_H_ -#include "MatT.h" -#include "VecT.h" #include #include +#include "MatT.h" +#include "VecT.h" namespace imageproc { class BinaryImage; @@ -32,79 +32,79 @@ class GrayImage; * \brief A polynomial function describing a 2D surface. */ class PolynomialSurface { - // Member-wise copying is OK. -public: - /** - * \brief Calculate a polynomial that approximates the given image. - * - * \param hor_degree The degree of the polynomial in horizontal direction. - * Must not be negative. A value of 3 or 4 should be enough - * to approximate page background. - * \param vert_degree The degree of the polynomial in vertical direction. - * Must not be negative. A value of 3 or 4 should be enough - * to approximate page background. - * \param src The image to approximate. Must be grayscale and not null. - * - * \note Building a polynomial surface for full size 300 DPI scans - * takes forever, so pass a downscaled version here. 300x300 - * pixels will be fine. Once built, the polynomial surface - * may then be rendered in the original size, if necessary. - */ - PolynomialSurface(int hor_degree, int vert_degree, const GrayImage& src); - - /** - * \brief Calculate a polynomial that approximates portions of the given image. - * - * \param hor_degree The degree of the polynomial in horizontal direction. - * Must not be negative. A value of 5 should be enough - * to approximate page background. - * \param vert_degree The degree of the polynomial in vertical direction. - * Must not be negative. A value of 5 should be enough - * to approximate page background. - * \param src The image to approximate. Must be grayscale and not null. - * \param mask Specifies which areas of \p src to consider. - * A pixel in \p src is considered if the corresponding pixel - * in \p mask is black. - * - * \note Building a polynomial surface for full size 300 DPI scans - * takes forever, so pass a downscaled version here. 300x300 - * pixels will be fine. Once built, the polynomial surface - * may then rendered in the original size, if necessary. - */ - PolynomialSurface(int hor_degree, int vert_degree, const GrayImage& src, const BinaryImage& mask); - - /** - * \brief Visualizes the polynomial surface as a grayscale image. - * - * The surface will be stretched / shrunk to fit the new size. - */ - GrayImage render(const QSize& size) const; - -private: - void maybeReduceDegrees(int num_data_points); - - int calcNumTerms() const; - - static double calcScale(int dimension); - - static void prepareDataForLeastSquares(const GrayImage& image, - MatT& AtA, - VecT& Atb, - int h_degree, - int v_degree); - - static void prepareDataForLeastSquares(const GrayImage& image, - const BinaryImage& mask, - MatT& AtA, - VecT& Atb, - int h_degree, - int v_degree); - - static void fixSquareMatrixRankDeficiency(MatT& mat); - - VecT m_coeffs; - int m_horDegree; - int m_vertDegree; + // Member-wise copying is OK. + public: + /** + * \brief Calculate a polynomial that approximates the given image. + * + * \param hor_degree The degree of the polynomial in horizontal direction. + * Must not be negative. A value of 3 or 4 should be enough + * to approximate page background. + * \param vert_degree The degree of the polynomial in vertical direction. + * Must not be negative. A value of 3 or 4 should be enough + * to approximate page background. + * \param src The image to approximate. Must be grayscale and not null. + * + * \note Building a polynomial surface for full size 300 DPI scans + * takes forever, so pass a downscaled version here. 300x300 + * pixels will be fine. Once built, the polynomial surface + * may then be rendered in the original size, if necessary. + */ + PolynomialSurface(int hor_degree, int vert_degree, const GrayImage& src); + + /** + * \brief Calculate a polynomial that approximates portions of the given image. + * + * \param hor_degree The degree of the polynomial in horizontal direction. + * Must not be negative. A value of 5 should be enough + * to approximate page background. + * \param vert_degree The degree of the polynomial in vertical direction. + * Must not be negative. A value of 5 should be enough + * to approximate page background. + * \param src The image to approximate. Must be grayscale and not null. + * \param mask Specifies which areas of \p src to consider. + * A pixel in \p src is considered if the corresponding pixel + * in \p mask is black. + * + * \note Building a polynomial surface for full size 300 DPI scans + * takes forever, so pass a downscaled version here. 300x300 + * pixels will be fine. Once built, the polynomial surface + * may then rendered in the original size, if necessary. + */ + PolynomialSurface(int hor_degree, int vert_degree, const GrayImage& src, const BinaryImage& mask); + + /** + * \brief Visualizes the polynomial surface as a grayscale image. + * + * The surface will be stretched / shrunk to fit the new size. + */ + GrayImage render(const QSize& size) const; + + private: + void maybeReduceDegrees(int num_data_points); + + int calcNumTerms() const; + + static double calcScale(int dimension); + + static void prepareDataForLeastSquares(const GrayImage& image, + MatT& AtA, + VecT& Atb, + int h_degree, + int v_degree); + + static void prepareDataForLeastSquares(const GrayImage& image, + const BinaryImage& mask, + MatT& AtA, + VecT& Atb, + int h_degree, + int v_degree); + + static void fixSquareMatrixRankDeficiency(MatT& mat); + + VecT m_coeffs; + int m_horDegree; + int m_vertDegree; }; } // namespace imageproc diff --git a/imageproc/RastLineFinder.cpp b/imageproc/RastLineFinder.cpp index 09f780237..450487bdc 100644 --- a/imageproc/RastLineFinder.cpp +++ b/imageproc/RastLineFinder.cpp @@ -18,187 +18,185 @@ */ #include "RastLineFinder.h" -#include "VecNT.h" -#include "Constants.h" #include #include #include +#include "Constants.h" +#include "VecNT.h" namespace imageproc { /*========================= RastLineFinderParams ===========================*/ RastLineFinderParams::RastLineFinderParams() - : m_origin(0, 0), - m_minAngleDeg(0), - m_maxAngleDeg(180), - m_angleToleranceDeg(0.1), - m_maxDistFromLine(1.0), - m_minSupportPoints(3) { -} + : m_origin(0, 0), + m_minAngleDeg(0), + m_maxAngleDeg(180), + m_angleToleranceDeg(0.1), + m_maxDistFromLine(1.0), + m_minSupportPoints(3) {} bool RastLineFinderParams::validate(std::string* error) const { - if (m_angleToleranceDeg <= 0) { - if (error) { - *error = "RastLineFinder: angle tolerance must be positive"; - } - - return false; + if (m_angleToleranceDeg <= 0) { + if (error) { + *error = "RastLineFinder: angle tolerance must be positive"; } - if (m_angleToleranceDeg >= 180) { - if (error) { - *error = "RastLineFinder: angle tolerance must be below 180 degrees"; - } + return false; + } - return false; + if (m_angleToleranceDeg >= 180) { + if (error) { + *error = "RastLineFinder: angle tolerance must be below 180 degrees"; } - if (m_maxDistFromLine <= 0) { - if (error) { - *error = "RastLineFinder: max-dist-from-line must be positive"; - } + return false; + } - return false; + if (m_maxDistFromLine <= 0) { + if (error) { + *error = "RastLineFinder: max-dist-from-line must be positive"; } - if (m_minSupportPoints < 2) { - if (error) { - *error = "RastLineFinder: min-support-points must be at least 2"; - } + return false; + } - return false; + if (m_minSupportPoints < 2) { + if (error) { + *error = "RastLineFinder: min-support-points must be at least 2"; } - return true; + return false; + } + + return true; } // RastLineFinderParams::validate RastLineFinder::RastLineFinder(const std::vector& points, const RastLineFinderParams& params) - : m_origin(params.origin()), - m_angleToleranceRad(params.angleToleranceDeg() * constants::DEG2RAD), - m_maxDistFromLine(params.maxDistFromLine()), - m_minSupportPoints(params.minSupportPoints()), - m_firstLine(true) { - std::string error; - if (!params.validate(&error)) { - throw std::invalid_argument(error); + : m_origin(params.origin()), + m_angleToleranceRad(params.angleToleranceDeg() * constants::DEG2RAD), + m_maxDistFromLine(params.maxDistFromLine()), + m_minSupportPoints(params.minSupportPoints()), + m_firstLine(true) { + std::string error; + if (!params.validate(&error)) { + throw std::invalid_argument(error); + } + + m_points.reserve(points.size()); + std::vector candidate_idxs; + candidate_idxs.reserve(points.size()); + + double max_sqdist = 0; + + for (const QPointF& pt : points) { + m_points.emplace_back(pt); + candidate_idxs.push_back(static_cast(candidate_idxs.size())); + + const double sqdist = Vec2d(pt - m_origin).squaredNorm(); + if (sqdist > max_sqdist) { + max_sqdist = sqdist; } + } + + const auto max_dist = static_cast(std::sqrt(max_sqdist) + 1.0); // + 1.0 to combant rounding issues + + double delta_deg = std::fmod(params.maxAngleDeg() - params.minAngleDeg(), 360.0); + if (delta_deg < 0) { + delta_deg += 360; + } + const double min_angle_deg = std::fmod(params.minAngleDeg(), 360.0); + const double max_angle_deg = min_angle_deg + delta_deg; + + SearchSpace ssp(*this, -max_dist, max_dist, static_cast(min_angle_deg * constants::DEG2RAD), + static_cast(max_angle_deg * constants::DEG2RAD), candidate_idxs); + if (ssp.pointIdxs().size() >= m_minSupportPoints) { + m_orderedSearchSpaces.pushDestructive(ssp); + } +} - m_points.reserve(points.size()); - std::vector candidate_idxs; - candidate_idxs.reserve(points.size()); - - double max_sqdist = 0; +QLineF RastLineFinder::findNext(std::vector* point_idxs) { + if (m_firstLine) { + m_firstLine = false; + } else { + pruneUnavailablePoints(); + } - for (const QPointF& pt : points) { - m_points.emplace_back(pt); - candidate_idxs.push_back(static_cast(candidate_idxs.size())); + SearchSpace dist_ssp1, dist_ssp2; + SearchSpace angle_ssp1, angle_ssp2; - const double sqdist = Vec2d(pt - m_origin).squaredNorm(); - if (sqdist > max_sqdist) { - max_sqdist = sqdist; + while (!m_orderedSearchSpaces.empty()) { + SearchSpace ssp; + m_orderedSearchSpaces.retrieveFront(ssp); + + if (!ssp.subdivideDist(*this, dist_ssp1, dist_ssp2)) { + if (!ssp.subdivideAngle(*this, angle_ssp1, angle_ssp2)) { + // Can't subdivide at all - return what we've got then. + markPointsUnavailable(ssp.pointIdxs()); + if (point_idxs) { + point_idxs->swap(ssp.pointIdxs()); } - } - - const auto max_dist = static_cast(std::sqrt(max_sqdist) + 1.0); // + 1.0 to combant rounding issues - double delta_deg = std::fmod(params.maxAngleDeg() - params.minAngleDeg(), 360.0); - if (delta_deg < 0) { - delta_deg += 360; - } - const double min_angle_deg = std::fmod(params.minAngleDeg(), 360.0); - const double max_angle_deg = min_angle_deg + delta_deg; - - SearchSpace ssp(*this, -max_dist, max_dist, static_cast(min_angle_deg * constants::DEG2RAD), - static_cast(max_angle_deg * constants::DEG2RAD), candidate_idxs); - if (ssp.pointIdxs().size() >= m_minSupportPoints) { - m_orderedSearchSpaces.pushDestructive(ssp); - } -} - -QLineF RastLineFinder::findNext(std::vector* point_idxs) { - if (m_firstLine) { - m_firstLine = false; + return ssp.representativeLine(*this); + } else { + // Can only subdivide by angle. + pushIfGoodEnough(angle_ssp1); + pushIfGoodEnough(angle_ssp2); + } } else { - pruneUnavailablePoints(); - } - - SearchSpace dist_ssp1, dist_ssp2; - SearchSpace angle_ssp1, angle_ssp2; - - while (!m_orderedSearchSpaces.empty()) { - SearchSpace ssp; - m_orderedSearchSpaces.retrieveFront(ssp); - - if (!ssp.subdivideDist(*this, dist_ssp1, dist_ssp2)) { - if (!ssp.subdivideAngle(*this, angle_ssp1, angle_ssp2)) { - // Can't subdivide at all - return what we've got then. - markPointsUnavailable(ssp.pointIdxs()); - if (point_idxs) { - point_idxs->swap(ssp.pointIdxs()); - } - - return ssp.representativeLine(*this); - } else { - // Can only subdivide by angle. - pushIfGoodEnough(angle_ssp1); - pushIfGoodEnough(angle_ssp2); - } + if (!ssp.subdivideAngle(*this, angle_ssp1, angle_ssp2)) { + // Can only subdivide by distance. + pushIfGoodEnough(dist_ssp1); + pushIfGoodEnough(dist_ssp2); + } else { + // Can subdivide both by angle and distance. + // Choose the option that results in less combined + // number of points in two resulting sub-spaces. + if (dist_ssp1.pointIdxs().size() + dist_ssp2.pointIdxs().size() + < angle_ssp1.pointIdxs().size() + angle_ssp2.pointIdxs().size()) { + pushIfGoodEnough(dist_ssp1); + pushIfGoodEnough(dist_ssp2); } else { - if (!ssp.subdivideAngle(*this, angle_ssp1, angle_ssp2)) { - // Can only subdivide by distance. - pushIfGoodEnough(dist_ssp1); - pushIfGoodEnough(dist_ssp2); - } else { - // Can subdivide both by angle and distance. - // Choose the option that results in less combined - // number of points in two resulting sub-spaces. - if (dist_ssp1.pointIdxs().size() + dist_ssp2.pointIdxs().size() - < angle_ssp1.pointIdxs().size() + angle_ssp2.pointIdxs().size()) { - pushIfGoodEnough(dist_ssp1); - pushIfGoodEnough(dist_ssp2); - } else { - pushIfGoodEnough(angle_ssp1); - pushIfGoodEnough(angle_ssp2); - } - } + pushIfGoodEnough(angle_ssp1); + pushIfGoodEnough(angle_ssp2); } + } } + } - return QLineF(); + return QLineF(); } // RastLineFinder::findNext void RastLineFinder::pushIfGoodEnough(SearchSpace& ssp) { - if (ssp.pointIdxs().size() >= m_minSupportPoints) { - m_orderedSearchSpaces.pushDestructive(ssp); - } + if (ssp.pointIdxs().size() >= m_minSupportPoints) { + m_orderedSearchSpaces.pushDestructive(ssp); + } } void RastLineFinder::markPointsUnavailable(const std::vector& point_idxs) { - for (unsigned idx : point_idxs) { - m_points[idx].available = false; - } + for (unsigned idx : point_idxs) { + m_points[idx].available = false; + } } void RastLineFinder::pruneUnavailablePoints() { - OrderedSearchSpaces new_search_spaces; - SearchSpace ssp; - PointUnavailablePred pred(&m_points); + OrderedSearchSpaces new_search_spaces; + SearchSpace ssp; + PointUnavailablePred pred(&m_points); - while (!m_orderedSearchSpaces.empty()) { - m_orderedSearchSpaces.retrieveFront(ssp); - ssp.pruneUnavailablePoints(pred); - if (ssp.pointIdxs().size() >= m_minSupportPoints) { - new_search_spaces.pushDestructive(ssp); - } + while (!m_orderedSearchSpaces.empty()) { + m_orderedSearchSpaces.retrieveFront(ssp); + ssp.pruneUnavailablePoints(pred); + if (ssp.pointIdxs().size() >= m_minSupportPoints) { + new_search_spaces.pushDestructive(ssp); } + } - m_orderedSearchSpaces.swapWith(new_search_spaces); + m_orderedSearchSpaces.swapWith(new_search_spaces); } /*============================= SearchSpace ================================*/ -RastLineFinder::SearchSpace::SearchSpace() : m_minDist(0), m_maxDist(0), m_minAngleRad(0), m_maxAngleRad(0) { -} +RastLineFinder::SearchSpace::SearchSpace() : m_minDist(0), m_maxDist(0), m_minAngleRad(0), m_maxAngleRad(0) {} RastLineFinder::SearchSpace::SearchSpace(const RastLineFinder& owner, float min_dist, @@ -206,126 +204,126 @@ RastLineFinder::SearchSpace::SearchSpace(const RastLineFinder& owner, float min_angle_rad, float max_angle_rad, const std::vector& candidate_idxs) - : m_minDist(min_dist), m_maxDist(max_dist), m_minAngleRad(min_angle_rad), m_maxAngleRad(max_angle_rad) { - m_pointIdxs.reserve(candidate_idxs.size()); + : m_minDist(min_dist), m_maxDist(max_dist), m_minAngleRad(min_angle_rad), m_maxAngleRad(max_angle_rad) { + m_pointIdxs.reserve(candidate_idxs.size()); - const QPointF origin(owner.m_origin); + const QPointF origin(owner.m_origin); - const double min_sqdist = double(m_minDist) * double(m_minDist); - const double max_sqdist = double(m_maxDist) * double(m_maxDist); + const double min_sqdist = double(m_minDist) * double(m_minDist); + const double max_sqdist = double(m_maxDist) * double(m_maxDist); - const QPointF min_angle_unit_vec(std::cos(m_minAngleRad), std::sin(m_minAngleRad)); - const QPointF max_angle_unit_vec(std::cos(m_maxAngleRad), std::sin(m_maxAngleRad)); + const QPointF min_angle_unit_vec(std::cos(m_minAngleRad), std::sin(m_minAngleRad)); + const QPointF max_angle_unit_vec(std::cos(m_maxAngleRad), std::sin(m_maxAngleRad)); - const QPointF min_angle_inner_pt(origin + min_angle_unit_vec * m_minDist); - const QPointF max_angle_inner_pt(origin + max_angle_unit_vec * m_minDist); + const QPointF min_angle_inner_pt(origin + min_angle_unit_vec * m_minDist); + const QPointF max_angle_inner_pt(origin + max_angle_unit_vec * m_minDist); - const QPointF min_angle_outer_pt(origin + min_angle_unit_vec * m_maxDist); - const QPointF max_angle_outer_pt(origin + max_angle_unit_vec * m_maxDist); + const QPointF min_angle_outer_pt(origin + min_angle_unit_vec * m_maxDist); + const QPointF max_angle_outer_pt(origin + max_angle_unit_vec * m_maxDist); - const Vec2d min_towards_max_angle_vec(-min_angle_unit_vec.y(), min_angle_unit_vec.x()); - const Vec2d max_towards_min_angle_vec(max_angle_unit_vec.y(), -max_angle_unit_vec.x()); + const Vec2d min_towards_max_angle_vec(-min_angle_unit_vec.y(), min_angle_unit_vec.x()); + const Vec2d max_towards_min_angle_vec(max_angle_unit_vec.y(), -max_angle_unit_vec.x()); - for (unsigned idx : candidate_idxs) { - const Point& pnt = owner.m_points[idx]; - if (!pnt.available) { - continue; - } - - const Vec2d rel_pt(pnt.pt - origin); - - if ((Vec2d(pnt.pt - min_angle_inner_pt).dot(min_angle_unit_vec) >= 0) - && (Vec2d(pnt.pt - max_angle_outer_pt).dot(max_angle_unit_vec) <= 0)) { - // Accepted. - } else if ((Vec2d(pnt.pt - max_angle_inner_pt).dot(max_angle_unit_vec) >= 0) - && (Vec2d(pnt.pt - min_angle_outer_pt).dot(min_angle_unit_vec) <= 0)) { - // Accepted. - } else if ((min_towards_max_angle_vec.dot(rel_pt) >= 0) && (max_towards_min_angle_vec.dot(rel_pt) >= 0) - && (rel_pt.squaredNorm() >= min_sqdist) && (rel_pt.squaredNorm() <= max_sqdist)) { - // Accepted. - } else { - // Rejected. - continue; - } + for (unsigned idx : candidate_idxs) { + const Point& pnt = owner.m_points[idx]; + if (!pnt.available) { + continue; + } - m_pointIdxs.push_back(idx); + const Vec2d rel_pt(pnt.pt - origin); + + if ((Vec2d(pnt.pt - min_angle_inner_pt).dot(min_angle_unit_vec) >= 0) + && (Vec2d(pnt.pt - max_angle_outer_pt).dot(max_angle_unit_vec) <= 0)) { + // Accepted. + } else if ((Vec2d(pnt.pt - max_angle_inner_pt).dot(max_angle_unit_vec) >= 0) + && (Vec2d(pnt.pt - min_angle_outer_pt).dot(min_angle_unit_vec) <= 0)) { + // Accepted. + } else if ((min_towards_max_angle_vec.dot(rel_pt) >= 0) && (max_towards_min_angle_vec.dot(rel_pt) >= 0) + && (rel_pt.squaredNorm() >= min_sqdist) && (rel_pt.squaredNorm() <= max_sqdist)) { + // Accepted. + } else { + // Rejected. + continue; } - // Compact m_pointIdxs, as we expect a lot of SearchSpace objects - // to exist at the same time. - m_pointIdxs.shrink_to_fit(); + m_pointIdxs.push_back(idx); + } + + // Compact m_pointIdxs, as we expect a lot of SearchSpace objects + // to exist at the same time. + m_pointIdxs.shrink_to_fit(); } QLineF RastLineFinder::SearchSpace::representativeLine(const RastLineFinder& owner) const { - const float dist = 0.5f * (m_minDist + m_maxDist); - const float angle = 0.5f * (m_minAngleRad + m_maxAngleRad); - const QPointF angle_unit_vec(std::cos(angle), std::sin(angle)); - const QPointF angle_norm_vec(-angle_unit_vec.y(), angle_unit_vec.x()); - const QPointF p1(owner.m_origin + angle_unit_vec * dist); - const QPointF p2(p1 + angle_norm_vec); - - return QLineF(p1, p2); + const float dist = 0.5f * (m_minDist + m_maxDist); + const float angle = 0.5f * (m_minAngleRad + m_maxAngleRad); + const QPointF angle_unit_vec(std::cos(angle), std::sin(angle)); + const QPointF angle_norm_vec(-angle_unit_vec.y(), angle_unit_vec.x()); + const QPointF p1(owner.m_origin + angle_unit_vec * dist); + const QPointF p2(p1 + angle_norm_vec); + + return QLineF(p1, p2); } bool RastLineFinder::SearchSpace::subdivideDist(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const { - assert(m_maxDist >= m_minDist); - - if ((m_maxDist - m_minDist <= owner.m_maxDistFromLine * 2.0001) || (m_pointIdxs.size() < 2)) { - return false; - } - - if (m_maxDist - m_minDist <= owner.m_angleToleranceRad * 3) { - // This branch prevents near-infinite subdivision that would have happened without it. - SearchSpace ssp1(owner, m_minDist, static_cast(m_minDist + owner.m_maxDistFromLine * 2), m_minAngleRad, - m_maxAngleRad, m_pointIdxs); - SearchSpace ssp2(owner, static_cast(m_maxDist - owner.m_maxDistFromLine * 2), m_maxDist, m_minAngleRad, - m_maxAngleRad, m_pointIdxs); - ssp1.swap(subspace1); - ssp2.swap(subspace2); - } else { - const float mid_dist = 0.5f * (m_maxDist + m_minDist); - SearchSpace ssp1(owner, m_minDist, static_cast(mid_dist + owner.m_maxDistFromLine), m_minAngleRad, - m_maxAngleRad, m_pointIdxs); - SearchSpace ssp2(owner, static_cast(mid_dist - owner.m_maxDistFromLine), m_maxDist, m_minAngleRad, - m_maxAngleRad, m_pointIdxs); - ssp1.swap(subspace1); - ssp2.swap(subspace2); - } + assert(m_maxDist >= m_minDist); + + if ((m_maxDist - m_minDist <= owner.m_maxDistFromLine * 2.0001) || (m_pointIdxs.size() < 2)) { + return false; + } + + if (m_maxDist - m_minDist <= owner.m_angleToleranceRad * 3) { + // This branch prevents near-infinite subdivision that would have happened without it. + SearchSpace ssp1(owner, m_minDist, static_cast(m_minDist + owner.m_maxDistFromLine * 2), m_minAngleRad, + m_maxAngleRad, m_pointIdxs); + SearchSpace ssp2(owner, static_cast(m_maxDist - owner.m_maxDistFromLine * 2), m_maxDist, m_minAngleRad, + m_maxAngleRad, m_pointIdxs); + ssp1.swap(subspace1); + ssp2.swap(subspace2); + } else { + const float mid_dist = 0.5f * (m_maxDist + m_minDist); + SearchSpace ssp1(owner, m_minDist, static_cast(mid_dist + owner.m_maxDistFromLine), m_minAngleRad, + m_maxAngleRad, m_pointIdxs); + SearchSpace ssp2(owner, static_cast(mid_dist - owner.m_maxDistFromLine), m_maxDist, m_minAngleRad, + m_maxAngleRad, m_pointIdxs); + ssp1.swap(subspace1); + ssp2.swap(subspace2); + } - return true; + return true; } bool RastLineFinder::SearchSpace::subdivideAngle(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const { - assert(m_maxAngleRad >= m_minAngleRad); + assert(m_maxAngleRad >= m_minAngleRad); - if ((m_maxAngleRad - m_minAngleRad <= owner.m_angleToleranceRad * 2) || (m_pointIdxs.size() < 2)) { - return false; - } + if ((m_maxAngleRad - m_minAngleRad <= owner.m_angleToleranceRad * 2) || (m_pointIdxs.size() < 2)) { + return false; + } - const float mid_angle_rad = 0.5f * (m_maxAngleRad + m_minAngleRad); + const float mid_angle_rad = 0.5f * (m_maxAngleRad + m_minAngleRad); - SearchSpace ssp1(owner, m_minDist, m_maxDist, m_minAngleRad, mid_angle_rad, m_pointIdxs); - SearchSpace ssp2(owner, m_minDist, m_maxDist, mid_angle_rad, m_maxAngleRad, m_pointIdxs); + SearchSpace ssp1(owner, m_minDist, m_maxDist, m_minAngleRad, mid_angle_rad, m_pointIdxs); + SearchSpace ssp2(owner, m_minDist, m_maxDist, mid_angle_rad, m_maxAngleRad, m_pointIdxs); - ssp1.swap(subspace1); - ssp2.swap(subspace2); + ssp1.swap(subspace1); + ssp2.swap(subspace2); - return true; + return true; } void RastLineFinder::SearchSpace::pruneUnavailablePoints(PointUnavailablePred pred) { - m_pointIdxs.resize(std::remove_if(m_pointIdxs.begin(), m_pointIdxs.end(), pred) - m_pointIdxs.begin()); + m_pointIdxs.resize(std::remove_if(m_pointIdxs.begin(), m_pointIdxs.end(), pred) - m_pointIdxs.begin()); } void RastLineFinder::SearchSpace::swap(SearchSpace& other) { - std::swap(m_minDist, other.m_minDist); - std::swap(m_maxDist, other.m_maxDist); - std::swap(m_minAngleRad, other.m_minAngleRad); - std::swap(m_maxAngleRad, other.m_maxAngleRad); - m_pointIdxs.swap(other.m_pointIdxs); + std::swap(m_minDist, other.m_minDist); + std::swap(m_maxDist, other.m_maxDist); + std::swap(m_minAngleRad, other.m_minAngleRad); + std::swap(m_maxAngleRad, other.m_maxAngleRad); + m_pointIdxs.swap(other.m_pointIdxs); } } // namespace imageproc diff --git a/imageproc/RastLineFinder.h b/imageproc/RastLineFinder.h index 9690cd43e..cc361fa3e 100644 --- a/imageproc/RastLineFinder.h +++ b/imageproc/RastLineFinder.h @@ -20,123 +20,103 @@ #ifndef IMAGEPROC_RAST_LINE_FINDER_H_ #define IMAGEPROC_RAST_LINE_FINDER_H_ -#include "PriorityQueue.h" -#include #include -#include -#include +#include #include +#include +#include +#include "PriorityQueue.h" namespace imageproc { class RastLineFinderParams { -public: - RastLineFinderParams(); - - /** - * The algorithm operates in polar coordinates. One of those coordinates - * is a signed distance to the origin. By default the origin is at (0, 0), - * but you can set it explicitly with this call. - */ - void setOrigin(const QPointF& origin) { - m_origin = origin; - } - - /** \see setOrigin() */ - const QPointF& origin() const { - return m_origin; - } - - /** - * By default, all angles are considered. Keeping in mind that line direction - * doesn't matter, that gives us the range of [0, 180) degrees. - * This method allows you to provide a custom range to consider. - * Cases where min_angle_deg > max_angle_deg are valid. Consider the difference - * between [20, 200) and [200, 20). The latter one is equivalent to [200, 380). - * - * \note This is not the angle between the line and the X axis! - * Instead, you take your origin point (which is customizable) - * and draw a perpendicular to your line. This vector, - * from origin to line, is what defines the line angle. - * In other words, after normalizing it to unit length, its - * coordinates will correspond to cosine and sine of your angle. - */ - void setAngleRangeDeg(double min_angle_deg, double max_angle_deg) { - m_minAngleDeg = min_angle_deg; - m_maxAngleDeg = max_angle_deg; - } - - /** \see setAngleRangeDeg() */ - double minAngleDeg() const { - return m_minAngleDeg; - } - - /** \see setAngleRangeDeg() */ - double maxAngleDeg() const { - return m_maxAngleDeg; - } - - /** - * Being a recursive subdivision algorithm, it has to stop refining the angle - * at some point. Angle tolerance is the maximum acceptable error (in degrees) - * for the lines returned. By default it's set to 0.1 degrees. Setting it to - * a higher value will improve performance. - */ - void setAngleToleranceDeg(double tolerance_deg) { - m_angleToleranceDeg = tolerance_deg; - } - - /** \see setAngleToleranceDeg() */ - double angleToleranceDeg() const { - return m_angleToleranceDeg; - } - - /** - * Sets the maximum distance the point is allowed to be from a line - * to still be considered a part of it. In reality, this value is - * a lower bound. The upper bound depends on angle tolerance and - * will tend to the lower bound as angle tolerance tends to zero. - * - * \see setAngleTolerance() - */ - void setMaxDistFromLine(double dist) { - m_maxDistFromLine = dist; - } - - /** \see setMaxDistFromLine() */ - double maxDistFromLine() const { - return m_maxDistFromLine; - } - - /** - * A support point is a point considered to be a part of a line. - * By default, lines consisting of 3 or more points are considered. - * The minimum allowed value is 2, while higher values improve performance. - * - * \see setMaxDistFromLine() - */ - void setMinSupportPoints(unsigned pts) { - m_minSupportPoints = pts; - } - - /** - * \see setMinSupportPoints() - */ - unsigned minSupportPoints() const { - return m_minSupportPoints; - } - - /** - * \brief Checks if parameters are valid, optionally providing an error string. - */ - bool validate(std::string* error = nullptr) const; - -private: - QPointF m_origin; - double m_minAngleDeg; - double m_maxAngleDeg; - double m_angleToleranceDeg; - double m_maxDistFromLine; - unsigned m_minSupportPoints; + public: + RastLineFinderParams(); + + /** + * The algorithm operates in polar coordinates. One of those coordinates + * is a signed distance to the origin. By default the origin is at (0, 0), + * but you can set it explicitly with this call. + */ + void setOrigin(const QPointF& origin) { m_origin = origin; } + + /** \see setOrigin() */ + const QPointF& origin() const { return m_origin; } + + /** + * By default, all angles are considered. Keeping in mind that line direction + * doesn't matter, that gives us the range of [0, 180) degrees. + * This method allows you to provide a custom range to consider. + * Cases where min_angle_deg > max_angle_deg are valid. Consider the difference + * between [20, 200) and [200, 20). The latter one is equivalent to [200, 380). + * + * \note This is not the angle between the line and the X axis! + * Instead, you take your origin point (which is customizable) + * and draw a perpendicular to your line. This vector, + * from origin to line, is what defines the line angle. + * In other words, after normalizing it to unit length, its + * coordinates will correspond to cosine and sine of your angle. + */ + void setAngleRangeDeg(double min_angle_deg, double max_angle_deg) { + m_minAngleDeg = min_angle_deg; + m_maxAngleDeg = max_angle_deg; + } + + /** \see setAngleRangeDeg() */ + double minAngleDeg() const { return m_minAngleDeg; } + + /** \see setAngleRangeDeg() */ + double maxAngleDeg() const { return m_maxAngleDeg; } + + /** + * Being a recursive subdivision algorithm, it has to stop refining the angle + * at some point. Angle tolerance is the maximum acceptable error (in degrees) + * for the lines returned. By default it's set to 0.1 degrees. Setting it to + * a higher value will improve performance. + */ + void setAngleToleranceDeg(double tolerance_deg) { m_angleToleranceDeg = tolerance_deg; } + + /** \see setAngleToleranceDeg() */ + double angleToleranceDeg() const { return m_angleToleranceDeg; } + + /** + * Sets the maximum distance the point is allowed to be from a line + * to still be considered a part of it. In reality, this value is + * a lower bound. The upper bound depends on angle tolerance and + * will tend to the lower bound as angle tolerance tends to zero. + * + * \see setAngleTolerance() + */ + void setMaxDistFromLine(double dist) { m_maxDistFromLine = dist; } + + /** \see setMaxDistFromLine() */ + double maxDistFromLine() const { return m_maxDistFromLine; } + + /** + * A support point is a point considered to be a part of a line. + * By default, lines consisting of 3 or more points are considered. + * The minimum allowed value is 2, while higher values improve performance. + * + * \see setMaxDistFromLine() + */ + void setMinSupportPoints(unsigned pts) { m_minSupportPoints = pts; } + + /** + * \see setMinSupportPoints() + */ + unsigned minSupportPoints() const { return m_minSupportPoints; } + + /** + * \brief Checks if parameters are valid, optionally providing an error string. + */ + bool validate(std::string* error = nullptr) const; + + private: + QPointF m_origin; + double m_minAngleDeg; + double m_maxAngleDeg; + double m_angleToleranceDeg; + double m_maxDistFromLine; + unsigned m_minSupportPoints; }; @@ -149,135 +129,124 @@ class RastLineFinderParams { * http://infoscience.epfl.ch/record/82286/files/93-11.pdf?version=1 */ class RastLineFinder { -private: - class SearchSpace; - - friend void swap(SearchSpace& o1, SearchSpace& o2) { - o1.swap(o2); - } - -public: - /** - * Construct a line finder from a point cloud and a set of parameters. - * - * \throw std::invalid_argument if \p params are invalid. - * \see RastLineFinderParams::validate() - */ - RastLineFinder(const std::vector& points, const RastLineFinderParams& params); + private: + class SearchSpace; + + friend void swap(SearchSpace& o1, SearchSpace& o2) { o1.swap(o2); } + + public: + /** + * Construct a line finder from a point cloud and a set of parameters. + * + * \throw std::invalid_argument if \p params are invalid. + * \see RastLineFinderParams::validate() + */ + RastLineFinder(const std::vector& points, const RastLineFinderParams& params); + + /** + * Look for the next best line in terms of the number of support points. + * When a line is found, its support points are removed from the lists of + * support points of other candidate lines. + * + * \param[out] point_idxs If provided, it will be filled with indices of support + * points for this line. The indices index the vector of points + * that was passed to RastLineFinder constructor. + * \return If there are no more lines satisfying the search criteria, + * a null (default constructed) QLineF is returned. Otherwise, + * a line that goes near its support points is returned. + * Such a line is not to be treated as a line segment, that is positions + * of its endpoints should not be counted upon. In addition, the + * line won't be properly fit to its support points, but merely be + * close to an optimal line. + */ + QLineF findNext(std::vector* point_idxs = nullptr); + + private: + class Point { + public: + QPointF pt; + bool available; + + explicit Point(const QPointF& p) : pt(p), available(true) {} + }; + + + class PointUnavailablePred { + public: + explicit PointUnavailablePred(const std::vector* points) : m_points(points) {} + + bool operator()(unsigned idx) const { return !(*m_points)[idx].available; } + + private: + const std::vector* m_points; + }; + + + class SearchSpace { + public: + SearchSpace(); + + SearchSpace(const RastLineFinder& owner, + float min_dist, + float max_dist, + float min_angle_rad, + float max_angle_rad, + const std::vector& candidate_idxs); /** - * Look for the next best line in terms of the number of support points. - * When a line is found, its support points are removed from the lists of - * support points of other candidate lines. - * - * \param[out] point_idxs If provided, it will be filled with indices of support - * points for this line. The indices index the vector of points - * that was passed to RastLineFinder constructor. - * \return If there are no more lines satisfying the search criteria, - * a null (default constructed) QLineF is returned. Otherwise, - * a line that goes near its support points is returned. - * Such a line is not to be treated as a line segment, that is positions - * of its endpoints should not be counted upon. In addition, the - * line won't be properly fit to its support points, but merely be - * close to an optimal line. + * Returns a line that corresponds to the center of this search space. + * The returned line should be treated as an unbounded line rather than + * line segment, meaning that exact positions of endpoints can't be + * counted on. */ - QLineF findNext(std::vector* point_idxs = nullptr); - -private: - class Point { - public: - QPointF pt; - bool available; - - explicit Point(const QPointF& p) : pt(p), available(true) { - } - }; - - - class PointUnavailablePred { - public: - explicit PointUnavailablePred(const std::vector* points) : m_pPoints(points) { - } + QLineF representativeLine(const RastLineFinder& owner) const; - bool operator()(unsigned idx) const { - return !(*m_pPoints)[idx].available; - } + bool subdivideDist(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; - private: - const std::vector* m_pPoints; - }; + bool subdivideAngle(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; + void pruneUnavailablePoints(PointUnavailablePred pred); - class SearchSpace { - public: - SearchSpace(); + std::vector& pointIdxs() { return m_pointIdxs; } - SearchSpace(const RastLineFinder& owner, - float min_dist, - float max_dist, - float min_angle_rad, - float max_angle_rad, - const std::vector& candidate_idxs); + const std::vector& pointIdxs() const { return m_pointIdxs; } - /** - * Returns a line that corresponds to the center of this search space. - * The returned line should be treated as an unbounded line rather than - * line segment, meaning that exact positions of endpoints can't be - * counted on. - */ - QLineF representativeLine(const RastLineFinder& owner) const; + void swap(SearchSpace& other); - bool subdivideDist(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; + private: + float m_minDist; // + float m_maxDist; // These are already extended by max-dist-to-line. + float m_minAngleRad; + float m_maxAngleRad; + std::vector m_pointIdxs; // Indexes into m_points of the parent object. + }; - bool subdivideAngle(const RastLineFinder& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; - void pruneUnavailablePoints(PointUnavailablePred pred); + class OrderedSearchSpaces : public PriorityQueue { + friend class PriorityQueue; - std::vector& pointIdxs() { - return m_pointIdxs; - } + private: + void setIndex(SearchSpace& obj, size_t heap_idx) {} - const std::vector& pointIdxs() const { - return m_pointIdxs; - } - - void swap(SearchSpace& other); - - private: - float m_minDist; // - float m_maxDist; // These are already extended by max-dist-to-line. - float m_minAngleRad; - float m_maxAngleRad; - std::vector m_pointIdxs; // Indexes into m_points of the parent object. - }; - - - class OrderedSearchSpaces : public PriorityQueue { - friend class PriorityQueue; - - private: - void setIndex(SearchSpace& obj, size_t heap_idx) { - } - - bool higherThan(const SearchSpace& lhs, const SearchSpace& rhs) const { - return lhs.pointIdxs().size() > rhs.pointIdxs().size(); - } - }; + bool higherThan(const SearchSpace& lhs, const SearchSpace& rhs) const { + return lhs.pointIdxs().size() > rhs.pointIdxs().size(); + } + }; - void pushIfGoodEnough(SearchSpace& ssp); + void pushIfGoodEnough(SearchSpace& ssp); - void markPointsUnavailable(const std::vector& point_idxs); + void markPointsUnavailable(const std::vector& point_idxs); - void pruneUnavailablePoints(); + void pruneUnavailablePoints(); - QPointF m_origin; - double m_angleToleranceRad; - double m_maxDistFromLine; - unsigned m_minSupportPoints; - std::vector m_points; - OrderedSearchSpaces m_orderedSearchSpaces; - bool m_firstLine; + QPointF m_origin; + double m_angleToleranceRad; + double m_maxDistFromLine; + unsigned m_minSupportPoints; + std::vector m_points; + OrderedSearchSpaces m_orderedSearchSpaces; + bool m_firstLine; }; } // namespace imageproc diff --git a/imageproc/RasterOp.h b/imageproc/RasterOp.h index 11f83789b..61930c6a8 100644 --- a/imageproc/RasterOp.h +++ b/imageproc/RasterOp.h @@ -19,13 +19,13 @@ #ifndef IMAGEPROC_RASTEROP_H_ #define IMAGEPROC_RASTEROP_H_ -#include "BinaryImage.h" #include #include #include #include -#include #include +#include +#include "BinaryImage.h" namespace imageproc { /** @@ -41,7 +41,7 @@ namespace imageproc { * The template argument is the operation to perform. This is generally * a combination of several Rop* class templates, such as RopXor\. */ -template +template void rasterOp(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp); /** @@ -54,7 +54,7 @@ void rasterOp(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const Q * The template argument is the operation to perform. This is generally * a combination of several Rop* class templates, such as RopXor\. */ -template +template void rasterOp(BinaryImage& dst, const BinaryImage& src); /** @@ -62,10 +62,8 @@ void rasterOp(BinaryImage& dst, const BinaryImage& src); * \see rasterOp() */ class RopSrc { -public: - static uint32_t transform(uint32_t src, uint32_t /*dst*/) { - return src; - } + public: + static uint32_t transform(uint32_t src, uint32_t /*dst*/) { return src; } }; @@ -74,10 +72,8 @@ class RopSrc { * \see rasterOp() */ class RopDst { -public: - static uint32_t transform(uint32_t /*src*/, uint32_t dst) { - return dst; - } + public: + static uint32_t transform(uint32_t /*src*/, uint32_t dst) { return dst; } }; @@ -85,12 +81,10 @@ class RopDst { * \brief Raster operation that performs a logical NOT operation. * \see rasterOp() */ -template +template class RopNot { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - return ~Arg::transform(src, dst); - } + public: + static uint32_t transform(uint32_t src, uint32_t dst) { return ~Arg::transform(src, dst); } }; @@ -98,12 +92,12 @@ class RopNot { * \brief Raster operation that performs a logical AND operation. * \see rasterOp() */ -template +template class RopAnd { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - return Arg1::transform(src, dst) & Arg2::transform(src, dst); - } + public: + static uint32_t transform(uint32_t src, uint32_t dst) { + return Arg1::transform(src, dst) & Arg2::transform(src, dst); + } }; @@ -111,12 +105,12 @@ class RopAnd { * \brief Raster operation that performs a logical OR operation. * \see rasterOp() */ -template +template class RopOr { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - return Arg1::transform(src, dst) | Arg2::transform(src, dst); - } + public: + static uint32_t transform(uint32_t src, uint32_t dst) { + return Arg1::transform(src, dst) | Arg2::transform(src, dst); + } }; @@ -124,12 +118,12 @@ class RopOr { * \brief Raster operation that performs a logical XOR operation. * \see rasterOp() */ -template +template class RopXor { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - return Arg1::transform(src, dst) ^ Arg2::transform(src, dst); - } + public: + static uint32_t transform(uint32_t src, uint32_t dst) { + return Arg1::transform(src, dst) ^ Arg2::transform(src, dst); + } }; @@ -137,15 +131,15 @@ class RopXor { * \brief Raster operation that subtracts black pixels of Arg2 from Arg1. * \see rasterOp() */ -template +template class RopSubtract { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - uint32_t lhs = Arg1::transform(src, dst); - uint32_t rhs = Arg2::transform(src, dst); + public: + static uint32_t transform(uint32_t src, uint32_t dst) { + uint32_t lhs = Arg1::transform(src, dst); + uint32_t rhs = Arg2::transform(src, dst); - return lhs & (lhs ^ rhs); - } + return lhs & (lhs ^ rhs); + } }; @@ -153,15 +147,15 @@ class RopSubtract { * \brief Raster operation that subtracts white pixels of Arg2 from Arg1. * \see rasterOp() */ -template +template class RopSubtractWhite { -public: - static uint32_t transform(uint32_t src, uint32_t dst) { - uint32_t lhs = Arg1::transform(src, dst); - uint32_t rhs = Arg2::transform(src, dst); + public: + static uint32_t transform(uint32_t src, uint32_t dst) { + uint32_t lhs = Arg1::transform(src, dst); + uint32_t rhs = Arg2::transform(src, dst); - return lhs | ~(lhs ^ rhs); - } + return lhs | ~(lhs ^ rhs); + } }; @@ -173,263 +167,262 @@ class RopSubtractWhite { * and usually better way is to have this class as a non-template argument. */ class AbstractRasterOp { -public: - virtual ~AbstractRasterOp() = default; + public: + virtual ~AbstractRasterOp() = default; - /** - * \see rasterOp() - */ - virtual void operator()(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp) const = 0; + /** + * \see rasterOp() + */ + virtual void operator()(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp) const = 0; }; /** * \brief A pre-defined raster operation to be called polymorphically. */ -template +template class TemplateRasterOp : public AbstractRasterOp { -public: - /** - * \see rasterOp() - */ - void operator()(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp) const override { - rasterOp(dst, dr, src, sp); - } + public: + /** + * \see rasterOp() + */ + void operator()(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp) const override { + rasterOp(dst, dr, src, sp); + } }; namespace detail { -template +template void rasterOpInDirection(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp, const int dy, const int dx) { - const int src_start_bit = sp.x() % 32; - const int dst_start_bit = dr.x() % 32; - const int rightmost_dst_bit = dr.right(); // == dr.x() + dr.width() - 1; - const int rightmost_dst_word = rightmost_dst_bit / 32 - dr.x() / 32; - const uint32_t leftmost_dst_mask = ~uint32_t(0) >> dst_start_bit; - const uint32_t rightmost_dst_mask = ~uint32_t(0) << (31 - rightmost_dst_bit % 32); - - int first_dst_word; - int last_dst_word; - uint32_t first_dst_mask; - uint32_t last_dst_mask; - if (dx == 1) { - first_dst_word = 0; - last_dst_word = rightmost_dst_word; - first_dst_mask = leftmost_dst_mask; - last_dst_mask = rightmost_dst_mask; - } else { - assert(dx == -1); - first_dst_word = rightmost_dst_word; - last_dst_word = 0; - first_dst_mask = rightmost_dst_mask; - last_dst_mask = leftmost_dst_mask; - } - - int src_span_delta; - int dst_span_delta; - uint32_t* dst_span; - const uint32_t* src_span; - if (dy == 1) { - src_span_delta = src.wordsPerLine(); - dst_span_delta = dst.wordsPerLine(); - dst_span = dst.data() + dr.y() * dst_span_delta + dr.x() / 32; - src_span = src.data() + sp.y() * src_span_delta + sp.x() / 32; - } else { - assert(dy == -1); - src_span_delta = -src.wordsPerLine(); - dst_span_delta = -dst.wordsPerLine(); - assert(dr.bottom() == dr.y() + dr.height() - 1); - dst_span = dst.data() - dr.bottom() * dst_span_delta + dr.x() / 32; - src_span = src.data() - (sp.y() + dr.height() - 1) * src_span_delta + sp.x() / 32; - } - - int src_word1_shift; - int src_word2_shift; - if (src_start_bit > dst_start_bit) { - src_word1_shift = src_start_bit - dst_start_bit; - src_word2_shift = 32 - src_word1_shift; - } else if (src_start_bit < dst_start_bit) { - src_word2_shift = dst_start_bit - src_start_bit; - src_word1_shift = 32 - src_word2_shift; - --src_span; + const int src_start_bit = sp.x() % 32; + const int dst_start_bit = dr.x() % 32; + const int rightmost_dst_bit = dr.right(); // == dr.x() + dr.width() - 1; + const int rightmost_dst_word = rightmost_dst_bit / 32 - dr.x() / 32; + const uint32_t leftmost_dst_mask = ~uint32_t(0) >> dst_start_bit; + const uint32_t rightmost_dst_mask = ~uint32_t(0) << (31 - rightmost_dst_bit % 32); + + int first_dst_word; + int last_dst_word; + uint32_t first_dst_mask; + uint32_t last_dst_mask; + if (dx == 1) { + first_dst_word = 0; + last_dst_word = rightmost_dst_word; + first_dst_mask = leftmost_dst_mask; + last_dst_mask = rightmost_dst_mask; + } else { + assert(dx == -1); + first_dst_word = rightmost_dst_word; + last_dst_word = 0; + first_dst_mask = rightmost_dst_mask; + last_dst_mask = leftmost_dst_mask; + } + + int src_span_delta; + int dst_span_delta; + uint32_t* dst_span; + const uint32_t* src_span; + if (dy == 1) { + src_span_delta = src.wordsPerLine(); + dst_span_delta = dst.wordsPerLine(); + dst_span = dst.data() + dr.y() * dst_span_delta + dr.x() / 32; + src_span = src.data() + sp.y() * src_span_delta + sp.x() / 32; + } else { + assert(dy == -1); + src_span_delta = -src.wordsPerLine(); + dst_span_delta = -dst.wordsPerLine(); + assert(dr.bottom() == dr.y() + dr.height() - 1); + dst_span = dst.data() - dr.bottom() * dst_span_delta + dr.x() / 32; + src_span = src.data() - (sp.y() + dr.height() - 1) * src_span_delta + sp.x() / 32; + } + + int src_word1_shift; + int src_word2_shift; + if (src_start_bit > dst_start_bit) { + src_word1_shift = src_start_bit - dst_start_bit; + src_word2_shift = 32 - src_word1_shift; + } else if (src_start_bit < dst_start_bit) { + src_word2_shift = dst_start_bit - src_start_bit; + src_word1_shift = 32 - src_word2_shift; + --src_span; + } else { + // Here we have a simple case of dst_x % 32 == src_x % 32. + // Note that the rest of the code doesn't work with such + // a case because of hardcoded widx + 1. + if (first_dst_word == last_dst_word) { + assert(first_dst_word == 0); + const uint32_t mask = first_dst_mask & last_dst_mask; + + for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { + const uint32_t src_word = src_span[0]; + const uint32_t dst_word = dst_span[0]; + const uint32_t new_dst_word = Rop::transform(src_word, dst_word); + dst_span[0] = (dst_word & ~mask) | (new_dst_word & mask); + } } else { - // Here we have a simple case of dst_x % 32 == src_x % 32. - // Note that the rest of the code doesn't work with such - // a case because of hardcoded widx + 1. - if (first_dst_word == last_dst_word) { - assert(first_dst_word == 0); - const uint32_t mask = first_dst_mask & last_dst_mask; - - for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { - const uint32_t src_word = src_span[0]; - const uint32_t dst_word = dst_span[0]; - const uint32_t new_dst_word = Rop::transform(src_word, dst_word); - dst_span[0] = (dst_word & ~mask) | (new_dst_word & mask); - } - } else { - for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { - int widx = first_dst_word; - // Handle the first (possibly incomplete) dst word in the line. - uint32_t src_word = src_span[widx]; - uint32_t dst_word = dst_span[widx]; - uint32_t new_dst_word = Rop::transform(src_word, dst_word); - dst_span[widx] = (dst_word & ~first_dst_mask) | (new_dst_word & first_dst_mask); - - while ((widx += dx) != last_dst_word) { - src_word = src_span[widx]; - dst_word = dst_span[widx]; - dst_span[widx] = Rop::transform(src_word, dst_word); - } - - // Handle the last (possibly incomplete) dst word in the line. - src_word = src_span[widx]; - dst_word = dst_span[widx]; - new_dst_word = Rop::transform(src_word, dst_word); - dst_span[widx] = (dst_word & ~last_dst_mask) | (new_dst_word & last_dst_mask); - } + for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { + int widx = first_dst_word; + // Handle the first (possibly incomplete) dst word in the line. + uint32_t src_word = src_span[widx]; + uint32_t dst_word = dst_span[widx]; + uint32_t new_dst_word = Rop::transform(src_word, dst_word); + dst_span[widx] = (dst_word & ~first_dst_mask) | (new_dst_word & first_dst_mask); + + while ((widx += dx) != last_dst_word) { + src_word = src_span[widx]; + dst_word = dst_span[widx]; + dst_span[widx] = Rop::transform(src_word, dst_word); } - return; + // Handle the last (possibly incomplete) dst word in the line. + src_word = src_span[widx]; + dst_word = dst_span[widx]; + new_dst_word = Rop::transform(src_word, dst_word); + dst_span[widx] = (dst_word & ~last_dst_mask) | (new_dst_word & last_dst_mask); + } } - if (first_dst_word == last_dst_word) { - assert(first_dst_word == 0); - const uint32_t mask = first_dst_mask & last_dst_mask; - const uint32_t can_word1 = (~uint32_t(0) << src_word1_shift) & mask; - const uint32_t can_word2 = (~uint32_t(0) >> src_word2_shift) & mask; - - for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { - uint32_t src_word = 0; - if (can_word1) { - const uint32_t src_word1 = src_span[0]; - src_word |= src_word1 << src_word1_shift; - } - if (can_word2) { - const uint32_t src_word2 = src_span[1]; - src_word |= src_word2 >> src_word2_shift; - } - const uint32_t dst_word = dst_span[0]; - const uint32_t new_dst_word = Rop::transform(src_word, dst_word); - dst_span[0] = (dst_word & ~mask) | (new_dst_word & mask); - } - } else { - const uint32_t can_first_word1 = (~uint32_t(0) << src_word1_shift) & first_dst_mask; - const uint32_t can_first_word2 = (~uint32_t(0) >> src_word2_shift) & first_dst_mask; - const uint32_t can_last_word1 = (~uint32_t(0) << src_word1_shift) & last_dst_mask; - const uint32_t can_last_word2 = (~uint32_t(0) >> src_word2_shift) & last_dst_mask; - - for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { - int widx = first_dst_word; - // Handle the first (possibly incomplete) dst word in the line. - uint32_t src_word = 0; - if (can_first_word1) { - const uint32_t src_word1 = src_span[widx]; - src_word |= src_word1 << src_word1_shift; - } - if (can_first_word2) { - const uint32_t src_word2 = src_span[widx + 1]; - src_word |= src_word2 >> src_word2_shift; - } - uint32_t dst_word = dst_span[widx]; - uint32_t new_dst_word = Rop::transform(src_word, dst_word); - new_dst_word = (dst_word & ~first_dst_mask) | (new_dst_word & first_dst_mask); - - while ((widx += dx) != last_dst_word) { - const uint32_t src_word1 = src_span[widx]; - const uint32_t src_word2 = src_span[widx + 1]; - - dst_word = dst_span[widx]; - dst_span[widx - dx] = new_dst_word; - - new_dst_word - = Rop::transform((src_word1 << src_word1_shift) | (src_word2 >> src_word2_shift), dst_word); - } - - // Handle the last (possibly incomplete) dst word in the line. - src_word = 0; - if (can_last_word1) { - const uint32_t src_word1 = src_span[widx]; - src_word |= src_word1 << src_word1_shift; - } - if (can_last_word2) { - const uint32_t src_word2 = src_span[widx + 1]; - src_word |= src_word2 >> src_word2_shift; - } - - dst_word = dst_span[widx]; - dst_span[widx - dx] = new_dst_word; - - new_dst_word = Rop::transform(src_word, dst_word); - new_dst_word = (dst_word & ~last_dst_mask) | (new_dst_word & last_dst_mask); - dst_span[widx] = new_dst_word; - } + return; + } + + if (first_dst_word == last_dst_word) { + assert(first_dst_word == 0); + const uint32_t mask = first_dst_mask & last_dst_mask; + const uint32_t can_word1 = (~uint32_t(0) << src_word1_shift) & mask; + const uint32_t can_word2 = (~uint32_t(0) >> src_word2_shift) & mask; + + for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { + uint32_t src_word = 0; + if (can_word1) { + const uint32_t src_word1 = src_span[0]; + src_word |= src_word1 << src_word1_shift; + } + if (can_word2) { + const uint32_t src_word2 = src_span[1]; + src_word |= src_word2 >> src_word2_shift; + } + const uint32_t dst_word = dst_span[0]; + const uint32_t new_dst_word = Rop::transform(src_word, dst_word); + dst_span[0] = (dst_word & ~mask) | (new_dst_word & mask); } + } else { + const uint32_t can_first_word1 = (~uint32_t(0) << src_word1_shift) & first_dst_mask; + const uint32_t can_first_word2 = (~uint32_t(0) >> src_word2_shift) & first_dst_mask; + const uint32_t can_last_word1 = (~uint32_t(0) << src_word1_shift) & last_dst_mask; + const uint32_t can_last_word2 = (~uint32_t(0) >> src_word2_shift) & last_dst_mask; + + for (int i = dr.height(); i > 0; --i, src_span += src_span_delta, dst_span += dst_span_delta) { + int widx = first_dst_word; + // Handle the first (possibly incomplete) dst word in the line. + uint32_t src_word = 0; + if (can_first_word1) { + const uint32_t src_word1 = src_span[widx]; + src_word |= src_word1 << src_word1_shift; + } + if (can_first_word2) { + const uint32_t src_word2 = src_span[widx + 1]; + src_word |= src_word2 >> src_word2_shift; + } + uint32_t dst_word = dst_span[widx]; + uint32_t new_dst_word = Rop::transform(src_word, dst_word); + new_dst_word = (dst_word & ~first_dst_mask) | (new_dst_word & first_dst_mask); + + while ((widx += dx) != last_dst_word) { + const uint32_t src_word1 = src_span[widx]; + const uint32_t src_word2 = src_span[widx + 1]; + + dst_word = dst_span[widx]; + dst_span[widx - dx] = new_dst_word; + + new_dst_word = Rop::transform((src_word1 << src_word1_shift) | (src_word2 >> src_word2_shift), dst_word); + } + + // Handle the last (possibly incomplete) dst word in the line. + src_word = 0; + if (can_last_word1) { + const uint32_t src_word1 = src_span[widx]; + src_word |= src_word1 << src_word1_shift; + } + if (can_last_word2) { + const uint32_t src_word2 = src_span[widx + 1]; + src_word |= src_word2 >> src_word2_shift; + } + + dst_word = dst_span[widx]; + dst_span[widx - dx] = new_dst_word; + + new_dst_word = Rop::transform(src_word, dst_word); + new_dst_word = (dst_word & ~last_dst_mask) | (new_dst_word & last_dst_mask); + dst_span[widx] = new_dst_word; + } + } } // rasterOpInDirection } // namespace detail -template +template void rasterOp(BinaryImage& dst, const QRect& dr, const BinaryImage& src, const QPoint& sp) { - using namespace detail; + using namespace detail; - if (dr.isEmpty()) { - return; - } + if (dr.isEmpty()) { + return; + } - if (dst.isNull() || src.isNull()) { - throw std::invalid_argument("rasterOp: can't operate on null images"); - } + if (dst.isNull() || src.isNull()) { + throw std::invalid_argument("rasterOp: can't operate on null images"); + } - if (!dst.rect().contains(dr)) { - throw std::invalid_argument("rasterOp: raster area exceedes the dst image"); - } + if (!dst.rect().contains(dr)) { + throw std::invalid_argument("rasterOp: raster area exceedes the dst image"); + } - if (!src.rect().contains(QRect(sp, dr.size()))) { - throw std::invalid_argument("rasterOp: raster area exceedes the src image"); - } + if (!src.rect().contains(QRect(sp, dr.size()))) { + throw std::invalid_argument("rasterOp: raster area exceedes the src image"); + } - // We need to avoid a situation where we write some output - // and then read it as input. This can happen if src and dst - // are the same images. + // We need to avoid a situation where we write some output + // and then read it as input. This can happen if src and dst + // are the same images. - if (&dst == &src) { - // Note that if src and dst are different objects sharing - // the same data, dst will get a private copy when - // dst.data() is called. + if (&dst == &src) { + // Note that if src and dst are different objects sharing + // the same data, dst will get a private copy when + // dst.data() is called. - if (dr.y() > sp.y()) { - rasterOpInDirection(dst, dr, src, sp, -1, 1); + if (dr.y() > sp.y()) { + rasterOpInDirection(dst, dr, src, sp, -1, 1); - return; - } + return; + } - if ((dr.y() == sp.y()) && (dr.x() > sp.x())) { - rasterOpInDirection(dst, dr, src, sp, 1, -1); + if ((dr.y() == sp.y()) && (dr.x() > sp.x())) { + rasterOpInDirection(dst, dr, src, sp, 1, -1); - return; - } + return; } + } - rasterOpInDirection(dst, dr, src, sp, 1, 1); + rasterOpInDirection(dst, dr, src, sp, 1, 1); } // rasterOp -template +template void rasterOp(BinaryImage& dst, const BinaryImage& src) { - using namespace detail; + using namespace detail; - if (dst.isNull() || src.isNull()) { - throw std::invalid_argument("rasterOp: can't operate on null images"); - } + if (dst.isNull() || src.isNull()) { + throw std::invalid_argument("rasterOp: can't operate on null images"); + } - if (dst.size() != src.size()) { - throw std::invalid_argument("rasterOp: images have different sizes"); - } + if (dst.size() != src.size()) { + throw std::invalid_argument("rasterOp: images have different sizes"); + } - rasterOpInDirection(dst, dst.rect(), src, QPoint(0, 0), 1, 1); + rasterOpInDirection(dst, dst.rect(), src, QPoint(0, 0), 1, 1); } } // namespace imageproc #endif // ifndef IMAGEPROC_RASTEROP_H_ diff --git a/imageproc/RasterOpGeneric.h b/imageproc/RasterOpGeneric.h index ece77c1ec..208ac6360 100644 --- a/imageproc/RasterOpGeneric.h +++ b/imageproc/RasterOpGeneric.h @@ -19,10 +19,10 @@ #ifndef IMAGEPROC_RASTER_OP_GENERIC_H_ #define IMAGEPROC_RASTER_OP_GENERIC_H_ -#include "BinaryImage.h" #include -#include #include +#include +#include "BinaryImage.h" namespace imageproc { /** @@ -38,7 +38,7 @@ namespace imageproc { * Depending on whether T is const, the operation may be able to modify the image. * Hinst: boost::lambda is an easy way to construct operations. */ -template +template void rasterOpGeneric(T* data, int stride, QSize size, Op operation); /** @@ -57,7 +57,7 @@ void rasterOpGeneric(T* data, int stride, QSize size, Op operation); * one or both of them. * Hinst: boost::lambda is an easy way to construct operations. */ -template +template void rasterOpGeneric(T1* data1, int stride1, QSize size, T2* data2, int stride2, Op operation); @@ -70,7 +70,7 @@ void rasterOpGeneric(T1* data1, int stride1, QSize size, T2* data2, int stride2, * operation(bitl, data2[offset2]); * \endcode */ -template +template void rasterOpGeneric(const BinaryImage& image1, T2* data2, int stride2, Op operation); /** @@ -84,115 +84,112 @@ void rasterOpGeneric(const BinaryImage& image1, T2* data2, int stride2, Op opera * BitProxy will have implicit conversion to uint32_t returning 0 or 1, * and an assignment operator from uint32_t, expecting 0 or 1 only. */ -template +template void rasterOpGeneric(const BinaryImage& image1, T2* data2, int stride2, Op operation); /*======================== Implementation ==========================*/ -template +template void rasterOpGeneric(T* data, int stride, QSize size, Op operation) { - if (size.isEmpty()) { - return; - } + if (size.isEmpty()) { + return; + } - const int w = size.width(); - const int h = size.height(); + const int w = size.width(); + const int h = size.height(); - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - operation(data[x]); - } - data += stride; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + operation(data[x]); } + data += stride; + } } -template +template void rasterOpGeneric(T1* data1, int stride1, QSize size, T2* data2, int stride2, Op operation) { - if (size.isEmpty()) { - return; - } + if (size.isEmpty()) { + return; + } - const int w = size.width(); - const int h = size.height(); + const int w = size.width(); + const int h = size.height(); - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - operation(data1[x], data2[x]); - } - data1 += stride1; - data2 += stride2; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + operation(data1[x], data2[x]); } + data1 += stride1; + data2 += stride2; + } } -template +template void rasterOpGeneric(const BinaryImage& image1, T2* data2, int stride2, Op operation) { - if (image1.isNull()) { - return; - } - - const int w = image1.width(); - const int h = image1.height(); - const int stride1 = image1.wordsPerLine(); - const uint32_t* data1 = image1.data(); - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - const int shift = 31 - (x & 31); - operation((data1[x >> 5] >> shift) & uint32_t(1), data2[x]); - } - data1 += stride1; - data2 += stride2; + if (image1.isNull()) { + return; + } + + const int w = image1.width(); + const int h = image1.height(); + const int stride1 = image1.wordsPerLine(); + const uint32_t* data1 = image1.data(); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int shift = 31 - (x & 31); + operation((data1[x >> 5] >> shift) & uint32_t(1), data2[x]); } + data1 += stride1; + data2 += stride2; + } } namespace rop_generic_impl { class BitProxy { -public: - BitProxy(uint32_t& word, int shift) : m_rWord(word), m_shift(shift) { - } + public: + BitProxy(uint32_t& word, int shift) : m_word(word), m_shift(shift) {} - BitProxy(const BitProxy& other) = default; + BitProxy(const BitProxy& other) = default; - BitProxy& operator=(uint32_t bit) { - assert(bit <= 1); - const uint32_t mask = uint32_t(1) << m_shift; - m_rWord = (m_rWord & ~mask) | (bit << m_shift); + BitProxy& operator=(uint32_t bit) { + assert(bit <= 1); + const uint32_t mask = uint32_t(1) << m_shift; + m_word = (m_word & ~mask) | (bit << m_shift); - return *this; - } + return *this; + } - operator uint32_t() const { - return (m_rWord >> m_shift) & uint32_t(1); - } + operator uint32_t() const { return (m_word >> m_shift) & uint32_t(1); } -private: - uint32_t& m_rWord; - int m_shift; + private: + uint32_t& m_word; + int m_shift; }; } // namespace rop_generic_impl -template +template void rasterOpGeneric(BinaryImage& image1, T2* data2, int stride2, Op operation) { - using namespace rop_generic_impl; + using namespace rop_generic_impl; - if (image1.isNull()) { - return; - } + if (image1.isNull()) { + return; + } + + const int w = image1.width(); + const int h = image1.height(); + const int stride1 = image1.wordsPerLine(); + uint32_t* data1 = image1.data(); - const int w = image1.width(); - const int h = image1.height(); - const int stride1 = image1.wordsPerLine(); - uint32_t* data1 = image1.data(); - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - BitProxy bit1(data1[x >> 5], 31 - (x & 31)); - operation(bit1, data2[x]); - } - data1 += stride1; - data2 += stride2; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + BitProxy bit1(data1[x >> 5], 31 - (x & 31)); + operation(bit1, data2[x]); } + data1 += stride1; + data2 += stride2; + } } } // namespace imageproc #endif // ifndef IMAGEPROC_RASTER_OP_GENERIC_H_ diff --git a/imageproc/ReduceThreshold.cpp b/imageproc/ReduceThreshold.cpp index 40c063ec7..25014f322 100644 --- a/imageproc/ReduceThreshold.cpp +++ b/imageproc/ReduceThreshold.cpp @@ -36,25 +36,25 @@ namespace { * We take every other byte because bit 0 doesn't matter here. */ const uint8_t compressBitsLut[128] - = {0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, - 0x6, 0x7, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x0, 0x1, 0x0, 0x1, - 0x2, 0x3, 0x2, 0x3, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x8, 0x9, - 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, - 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, - 0xa, 0xb, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf}; + = {0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, + 0x6, 0x7, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x0, 0x1, 0x0, 0x1, 0x2, 0x3, 0x2, 0x3, 0x0, 0x1, 0x0, 0x1, + 0x2, 0x3, 0x2, 0x3, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x4, 0x5, 0x4, 0x5, 0x6, 0x7, 0x6, 0x7, 0x8, 0x9, + 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, + 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, 0xa, 0xb, 0x8, 0x9, 0x8, 0x9, 0xa, 0xb, + 0xa, 0xb, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf, 0xc, 0xd, 0xc, 0xd, 0xe, 0xf, 0xe, 0xf}; /** * Throw away every other bit starting with bit 0 and * pack the remaining bits into the upper half of a word. */ inline uint32_t compressBitsUpperHalf(const uint32_t bits) { - uint32_t r; - r = compressBitsLut[(bits >> 25) /*& 0x7F*/] << 28; - r |= compressBitsLut[(bits >> 17) & 0x7F] << 24; - r |= compressBitsLut[(bits >> 9) & 0x7F] << 20; - r |= compressBitsLut[(bits >> 1) & 0x7F] << 16; + uint32_t r; + r = compressBitsLut[(bits >> 25) /*& 0x7F*/] << 28; + r |= compressBitsLut[(bits >> 17) & 0x7F] << 24; + r |= compressBitsLut[(bits >> 9) & 0x7F] << 20; + r |= compressBitsLut[(bits >> 1) & 0x7F] << 16; - return r; + return r; } /** @@ -62,238 +62,237 @@ inline uint32_t compressBitsUpperHalf(const uint32_t bits) { * pack the remaining bits into the lower half of a word. */ inline uint32_t compressBitsLowerHalf(const uint32_t bits) { - uint32_t r; - r = compressBitsLut[(bits >> 25) /*& 0x7F*/] << 12; - r |= compressBitsLut[(bits >> 17) & 0x7F] << 8; - r |= compressBitsLut[(bits >> 9) & 0x7F] << 4; - r |= compressBitsLut[(bits >> 1) & 0x7F]; + uint32_t r; + r = compressBitsLut[(bits >> 25) /*& 0x7F*/] << 12; + r |= compressBitsLut[(bits >> 17) & 0x7F] << 8; + r |= compressBitsLut[(bits >> 9) & 0x7F] << 4; + r |= compressBitsLut[(bits >> 1) & 0x7F]; - return r; + return r; } inline uint32_t threshold1(const uint32_t top, const uint32_t bottom) { - uint32_t word = top | bottom; - word |= word << 1; + uint32_t word = top | bottom; + word |= word << 1; - return word; + return word; } inline uint32_t threshold2(const uint32_t top, const uint32_t bottom) { - uint32_t word1 = top & bottom; - word1 |= word1 << 1; - uint32_t word2 = top | bottom; - word2 &= word2 << 1; + uint32_t word1 = top & bottom; + word1 |= word1 << 1; + uint32_t word2 = top | bottom; + word2 &= word2 << 1; - return word1 | word2; + return word1 | word2; } inline uint32_t threshold3(const uint32_t top, const uint32_t bottom) { - uint32_t word1 = top | bottom; - word1 &= word1 << 1; - uint32_t word2 = top & bottom; - word2 |= word2 << 1; + uint32_t word1 = top | bottom; + word1 &= word1 << 1; + uint32_t word2 = top & bottom; + word2 |= word2 << 1; - return word1 & word2; + return word1 & word2; } inline uint32_t threshold4(const uint32_t top, const uint32_t bottom) { - uint32_t word = top & bottom; - word &= word << 1; + uint32_t word = top & bottom; + word &= word << 1; - return word; + return word; } } // namespace -ReduceThreshold::ReduceThreshold(const BinaryImage& image) : m_image(image) { -} +ReduceThreshold::ReduceThreshold(const BinaryImage& image) : m_image(image) {} ReduceThreshold& ReduceThreshold::reduce(const int threshold) { - if ((threshold < 1) || (threshold > 4)) { - throw std::invalid_argument("ReduceThreshold: invalid threshold"); - } + if ((threshold < 1) || (threshold > 4)) { + throw std::invalid_argument("ReduceThreshold: invalid threshold"); + } - const BinaryImage& src = m_image; + const BinaryImage& src = m_image; - if (src.isNull()) { - return *this; - } + if (src.isNull()) { + return *this; + } - const int dst_w = src.width() / 2; - const int dst_h = src.height() / 2; + const int dst_w = src.width() / 2; + const int dst_h = src.height() / 2; - if (dst_h == 0) { - reduceHorLine(threshold); + if (dst_h == 0) { + reduceHorLine(threshold); - return *this; - } else if (dst_w == 0) { - reduceVertLine(threshold); + return *this; + } else if (dst_w == 0) { + reduceVertLine(threshold); - return *this; + return *this; + } + + BinaryImage dst(dst_w, dst_h); + + const int dst_wpl = dst.wordsPerLine(); + const int src_wpl = src.wordsPerLine(); + const int steps_per_line = (dst_w * 2 + 31) / 32; + assert(steps_per_line <= src_wpl); + assert(steps_per_line / 2 <= dst_wpl); + + const uint32_t* src_line = src.data(); + uint32_t* dst_line = dst.data(); + + uint32_t word; + + if (threshold == 1) { + for (int i = dst_h; i > 0; --i) { + for (int j = 0; j < steps_per_line; j += 2) { + word = threshold1(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = threshold1(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + src_line += src_wpl * 2; + dst_line += dst_wpl; } - - BinaryImage dst(dst_w, dst_h); - - const int dst_wpl = dst.wordsPerLine(); - const int src_wpl = src.wordsPerLine(); - const int steps_per_line = (dst_w * 2 + 31) / 32; - assert(steps_per_line <= src_wpl); - assert(steps_per_line / 2 <= dst_wpl); - - const uint32_t* src_line = src.data(); - uint32_t* dst_line = dst.data(); - - uint32_t word; - - if (threshold == 1) { - for (int i = dst_h; i > 0; --i) { - for (int j = 0; j < steps_per_line; j += 2) { - word = threshold1(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = threshold1(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - src_line += src_wpl * 2; - dst_line += dst_wpl; - } - } else if (threshold == 2) { - for (int i = dst_h; i > 0; --i) { - for (int j = 0; j < steps_per_line; j += 2) { - word = threshold2(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = threshold2(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - src_line += src_wpl * 2; - dst_line += dst_wpl; - } - } else if (threshold == 3) { - for (int i = dst_h; i > 0; --i) { - for (int j = 0; j < steps_per_line; j += 2) { - word = threshold3(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = threshold3(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - src_line += src_wpl * 2; - dst_line += dst_wpl; - } - } else if (threshold == 4) { - for (int i = dst_h; i > 0; --i) { - for (int j = 0; j < steps_per_line; j += 2) { - word = threshold4(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = threshold4(src_line[j], src_line[j + src_wpl]); - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - src_line += src_wpl * 2; - dst_line += dst_wpl; - } + } else if (threshold == 2) { + for (int i = dst_h; i > 0; --i) { + for (int j = 0; j < steps_per_line; j += 2) { + word = threshold2(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = threshold2(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + src_line += src_wpl * 2; + dst_line += dst_wpl; + } + } else if (threshold == 3) { + for (int i = dst_h; i > 0; --i) { + for (int j = 0; j < steps_per_line; j += 2) { + word = threshold3(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = threshold3(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + src_line += src_wpl * 2; + dst_line += dst_wpl; } + } else if (threshold == 4) { + for (int i = dst_h; i > 0; --i) { + for (int j = 0; j < steps_per_line; j += 2) { + word = threshold4(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = threshold4(src_line[j], src_line[j + src_wpl]); + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + src_line += src_wpl * 2; + dst_line += dst_wpl; + } + } - m_image = dst; + m_image = dst; - return *this; + return *this; } // ReduceThreshold::reduce void ReduceThreshold::reduceHorLine(const int threshold) { - const BinaryImage& src = m_image; - assert(src.height() == 1); - - if (src.width() == 1) { - // 1x1 image remains the same no matter the threshold. - return; - } - - BinaryImage dst(src.width() / 2, 1); - - const int steps_per_line = (dst.width() * 2 + 31) / 32; - const uint32_t* src_line = src.data(); - uint32_t* dst_line = dst.data(); - assert(steps_per_line <= src.wordsPerLine()); - assert(steps_per_line / 2 <= dst.wordsPerLine()); - - uint32_t word; - - switch (threshold) { - case 1: - case 2: - for (int j = 0; j < steps_per_line; j += 2) { - word = src_line[j]; - word |= word << 1; - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = src_line[j]; - word |= word << 1; - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - break; - case 3: - case 4: - for (int j = 0; j < steps_per_line; j += 2) { - word = src_line[j]; - word &= word << 1; - dst_line[j / 2] = compressBitsUpperHalf(word); - } - for (int j = 1; j < steps_per_line; j += 2) { - word = src_line[j]; - word &= word << 1; - dst_line[j / 2] |= compressBitsLowerHalf(word); - } - break; - default: - break; - } - - m_image = dst; + const BinaryImage& src = m_image; + assert(src.height() == 1); + + if (src.width() == 1) { + // 1x1 image remains the same no matter the threshold. + return; + } + + BinaryImage dst(src.width() / 2, 1); + + const int steps_per_line = (dst.width() * 2 + 31) / 32; + const uint32_t* src_line = src.data(); + uint32_t* dst_line = dst.data(); + assert(steps_per_line <= src.wordsPerLine()); + assert(steps_per_line / 2 <= dst.wordsPerLine()); + + uint32_t word; + + switch (threshold) { + case 1: + case 2: + for (int j = 0; j < steps_per_line; j += 2) { + word = src_line[j]; + word |= word << 1; + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = src_line[j]; + word |= word << 1; + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + break; + case 3: + case 4: + for (int j = 0; j < steps_per_line; j += 2) { + word = src_line[j]; + word &= word << 1; + dst_line[j / 2] = compressBitsUpperHalf(word); + } + for (int j = 1; j < steps_per_line; j += 2) { + word = src_line[j]; + word &= word << 1; + dst_line[j / 2] |= compressBitsLowerHalf(word); + } + break; + default: + break; + } + + m_image = dst; } // ReduceThreshold::reduceHorLine void ReduceThreshold::reduceVertLine(const int threshold) { - const BinaryImage& src = m_image; - assert(src.width() == 1); - - if (src.height() == 1) { - // 1x1 image remains the same no matter the threshold. - return; - } - - const int dst_h = src.height() / 2; - BinaryImage dst(1, dst_h); - - const int src_wpl = src.wordsPerLine(); - const int dst_wpl = dst.wordsPerLine(); - const uint32_t* src_line = src.data(); - uint32_t* dst_line = dst.data(); - - switch (threshold) { - case 1: - case 2: - for (int i = dst_h; i > 0; --i) { - dst_line[0] = src_line[0] | src_line[src_wpl]; - src_line += src_wpl * 2; - dst_line += dst_wpl; - } - break; - case 3: - case 4: - for (int i = dst_h; i > 0; --i) { - dst_line[0] = src_line[0] & src_line[src_wpl]; - src_line += src_wpl * 2; - dst_line += dst_wpl; - } - break; - default: - break; - } - - m_image = dst; + const BinaryImage& src = m_image; + assert(src.width() == 1); + + if (src.height() == 1) { + // 1x1 image remains the same no matter the threshold. + return; + } + + const int dst_h = src.height() / 2; + BinaryImage dst(1, dst_h); + + const int src_wpl = src.wordsPerLine(); + const int dst_wpl = dst.wordsPerLine(); + const uint32_t* src_line = src.data(); + uint32_t* dst_line = dst.data(); + + switch (threshold) { + case 1: + case 2: + for (int i = dst_h; i > 0; --i) { + dst_line[0] = src_line[0] | src_line[src_wpl]; + src_line += src_wpl * 2; + dst_line += dst_wpl; + } + break; + case 3: + case 4: + for (int i = dst_h; i > 0; --i) { + dst_line[0] = src_line[0] & src_line[src_wpl]; + src_line += src_wpl * 2; + dst_line += dst_wpl; + } + break; + default: + break; + } + + m_image = dst; } // ReduceThreshold::reduceVertLine } // namespace imageproc \ No newline at end of file diff --git a/imageproc/ReduceThreshold.h b/imageproc/ReduceThreshold.h index 166a0c797..1f0194732 100644 --- a/imageproc/ReduceThreshold.h +++ b/imageproc/ReduceThreshold.h @@ -48,47 +48,41 @@ namespace imageproc { * \endcode */ class ReduceThreshold { -public: - /** - * \brief Constructor. Doesn't do any work by itself. - */ - explicit ReduceThreshold(const BinaryImage& image); + public: + /** + * \brief Constructor. Doesn't do any work by itself. + */ + explicit ReduceThreshold(const BinaryImage& image); - /** - * \brief Implicit conversion to BinaryImage. - */ - operator const BinaryImage&() const { - return m_image; - } + /** + * \brief Implicit conversion to BinaryImage. + */ + operator const BinaryImage&() const { return m_image; } - /** - * \brief Returns a reference to the reduced image. - */ - const BinaryImage& image() const { - return m_image; - } + /** + * \brief Returns a reference to the reduced image. + */ + const BinaryImage& image() const { return m_image; } - /** - * \brief Performs a reduction and returns *this. - */ - ReduceThreshold& reduce(int threshold); + /** + * \brief Performs a reduction and returns *this. + */ + ReduceThreshold& reduce(int threshold); - /** - * \brief Operator () performs a reduction and returns *this. - */ - ReduceThreshold& operator()(int threshold) { - return reduce(threshold); - } + /** + * \brief Operator () performs a reduction and returns *this. + */ + ReduceThreshold& operator()(int threshold) { return reduce(threshold); } -private: - void reduceHorLine(int threshold); + private: + void reduceHorLine(int threshold); - void reduceVertLine(int threshold); + void reduceVertLine(int threshold); - /** - * \brief The result of a previous reduction. - */ - BinaryImage m_image; + /** + * \brief The result of a previous reduction. + */ + BinaryImage m_image; }; } // namespace imageproc #endif // ifndef IMAGEPROC_REDUCETHRESHOLD_H_ diff --git a/imageproc/SEDM.cpp b/imageproc/SEDM.cpp index a84b2e4b2..f3d171916 100644 --- a/imageproc/SEDM.cpp +++ b/imageproc/SEDM.cpp @@ -23,463 +23,463 @@ #include "BinaryImage.h" #include "ConnectivityMap.h" #include "Morphology.h" -#include "SeedFill.h" #include "RasterOp.h" +#include "SeedFill.h" namespace imageproc { // Note that -1 is an implementation detail. // It exists to make sure INF_DIST + 1 doesn't overflow. const uint32_t SEDM::INF_DIST = ~uint32_t(0) - 1; -SEDM::SEDM() : m_pData(nullptr), m_size(), m_stride(0) { -} +SEDM::SEDM() : m_plainData(nullptr), m_size(), m_stride(0) {} SEDM::SEDM(const BinaryImage& image, const DistType dist_type, const Borders borders) - : m_pData(nullptr), m_size(image.size()), m_stride(0) { - if (image.isNull()) { - return; - } - - const int width = m_size.width(); - const int height = m_size.height(); - - m_data.resize((width + 2) * (height + 2), INF_DIST); - m_stride = width + 2; - m_pData = &m_data[0] + m_stride + 1; - - if (borders & DIST_TO_TOP_BORDER) { - memset(&m_data[0], 0, m_stride * sizeof(m_data[0])); - } - if (borders & DIST_TO_BOTTOM_BORDER) { - memset(&m_data[m_data.size() - m_stride], 0, m_stride * sizeof(m_data[0])); - } - if (borders & (DIST_TO_LEFT_BORDER | DIST_TO_RIGHT_BORDER)) { - const int last = m_stride - 1; - uint32_t* line = &m_data[0]; - for (int todo = height + 2; todo > 0; --todo) { - if (borders & DIST_TO_LEFT_BORDER) { - line[0] = 0; - } - if (borders & DIST_TO_RIGHT_BORDER) { - line[last] = 0; - } - line += m_stride; - } - } - - uint32_t initial_distance[2]; - if (dist_type == DIST_TO_WHITE) { - initial_distance[0] = 0; // white - initial_distance[1] = INF_DIST; // black - } else { - initial_distance[0] = INF_DIST; // white - initial_distance[1] = 0; // black + : m_plainData(nullptr), m_size(image.size()), m_stride(0) { + if (image.isNull()) { + return; + } + + const int width = m_size.width(); + const int height = m_size.height(); + + m_data.resize((width + 2) * (height + 2), INF_DIST); + m_stride = width + 2; + m_plainData = &m_data[0] + m_stride + 1; + + if (borders & DIST_TO_TOP_BORDER) { + memset(&m_data[0], 0, m_stride * sizeof(m_data[0])); + } + if (borders & DIST_TO_BOTTOM_BORDER) { + memset(&m_data[m_data.size() - m_stride], 0, m_stride * sizeof(m_data[0])); + } + if (borders & (DIST_TO_LEFT_BORDER | DIST_TO_RIGHT_BORDER)) { + const int last = m_stride - 1; + uint32_t* line = &m_data[0]; + for (int todo = height + 2; todo > 0; --todo) { + if (borders & DIST_TO_LEFT_BORDER) { + line[0] = 0; + } + if (borders & DIST_TO_RIGHT_BORDER) { + line[last] = 0; + } + line += m_stride; } - - uint32_t* p_dist = m_pData; - const uint32_t* img_line = image.data(); - const int img_stride = image.wordsPerLine(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x, ++p_dist) { - uint32_t word = img_line[x >> 5]; - word >>= 31 - (x & 31); - *p_dist = initial_distance[word & 1]; - } - p_dist += 2; - img_line += img_stride; + } + + uint32_t initial_distance[2]; + if (dist_type == DIST_TO_WHITE) { + initial_distance[0] = 0; // white + initial_distance[1] = INF_DIST; // black + } else { + initial_distance[0] = INF_DIST; // white + initial_distance[1] = 0; // black + } + + uint32_t* p_dist = m_plainData; + const uint32_t* img_line = image.data(); + const int img_stride = image.wordsPerLine(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x, ++p_dist) { + uint32_t word = img_line[x >> 5]; + word >>= 31 - (x & 31); + *p_dist = initial_distance[word & 1]; } + p_dist += 2; + img_line += img_stride; + } - processColumns(); - processRows(); + processColumns(); + processRows(); } -SEDM::SEDM(ConnectivityMap& cmap) : m_pData(nullptr), m_size(cmap.size()), m_stride(0) { - if (m_size.isEmpty()) { - return; +SEDM::SEDM(ConnectivityMap& cmap) : m_plainData(nullptr), m_size(cmap.size()), m_stride(0) { + if (m_size.isEmpty()) { + return; + } + + const int width = m_size.width(); + const int height = m_size.height(); + + m_data.resize((width + 2) * (height + 2), INF_DIST); + m_stride = width + 2; + m_plainData = &m_data[0] + m_stride + 1; + + uint32_t* p_dist = m_plainData; + const uint32_t* p_label = cmap.data(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x, ++p_dist, ++p_label) { + if (*p_label) { + *p_dist = 0; + } } + p_dist += 2; + p_label += 2; + } - const int width = m_size.width(); - const int height = m_size.height(); - - m_data.resize((width + 2) * (height + 2), INF_DIST); - m_stride = width + 2; - m_pData = &m_data[0] + m_stride + 1; - - uint32_t* p_dist = m_pData; - const uint32_t* p_label = cmap.data(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x, ++p_dist, ++p_label) { - if (*p_label) { - *p_dist = 0; - } - } - p_dist += 2; - p_label += 2; - } - - processColumns(cmap); - processRows(cmap); + processColumns(cmap); + processRows(cmap); } -SEDM::SEDM(const SEDM& other) : m_data(other.m_data), m_pData(nullptr), m_size(other.m_size), m_stride(other.m_stride) { - if (!m_size.isEmpty()) { - m_pData = &m_data[0] + m_stride + 1; - } +SEDM::SEDM(const SEDM& other) + : m_data(other.m_data), m_plainData(nullptr), m_size(other.m_size), m_stride(other.m_stride) { + if (!m_size.isEmpty()) { + m_plainData = &m_data[0] + m_stride + 1; + } } SEDM& SEDM::operator=(const SEDM& other) { - SEDM(other).swap(*this); + SEDM(other).swap(*this); - return *this; + return *this; } void SEDM::swap(SEDM& other) { - m_data.swap(other.m_data); - std::swap(m_pData, other.m_pData); - std::swap(m_size, other.m_size); - std::swap(m_stride, other.m_stride); + m_data.swap(other.m_data); + std::swap(m_plainData, other.m_plainData); + std::swap(m_size, other.m_size); + std::swap(m_stride, other.m_stride); } BinaryImage SEDM::findPeaksDestructive() { - if (m_size.isEmpty()) { - return BinaryImage(); - } - - BinaryImage peak_candidates(findPeakCandidatesNonPadded()); - // To check if a peak candidate is really a peak, we have to check - // that every cell in its neighborhood has a lower value than that - // candidate. We are working with 3x3 neighborhoods. - BinaryImage neighborhood_mask( - dilateBrick(peak_candidates, QSize(3, 3), peak_candidates.rect().adjusted(-1, -1, 1, 1))); - rasterOp>(neighborhood_mask, neighborhood_mask.rect().adjusted(1, 1, -1, -1), - peak_candidates, QPoint(0, 0)); - - // Cells in the neighborhood of a peak candidate fall into two categories: - // 1. The cell has a lower value than the peak candidate. - // 2. The cell has the same value as the peak candidate, - // but it has a cell with a greater value in its neighborhood. - // The second case indicates that our candidate is not relly a peak. - // To test for the second case we are going to increment the values - // of the cells in the neighborhood of peak candidates, find the peak - // candidates again and analize the differences. - - incrementMaskedPadded(neighborhood_mask); - neighborhood_mask.release(); - - BinaryImage diff(findPeakCandidatesNonPadded()); - rasterOp>(diff, peak_candidates); - - // If a bin that has changed its state was a part of a peak candidate, - // it means a neighboring bin went from equal to a greater value, - // which indicates that such candidate is not a peak. - const BinaryImage not_peaks(seedFill(diff, peak_candidates, CONN8)); - diff.release(); - - rasterOp>(peak_candidates, not_peaks); - - return peak_candidates; + if (m_size.isEmpty()) { + return BinaryImage(); + } + + BinaryImage peak_candidates(findPeakCandidatesNonPadded()); + // To check if a peak candidate is really a peak, we have to check + // that every cell in its neighborhood has a lower value than that + // candidate. We are working with 3x3 neighborhoods. + BinaryImage neighborhood_mask( + dilateBrick(peak_candidates, QSize(3, 3), peak_candidates.rect().adjusted(-1, -1, 1, 1))); + rasterOp>(neighborhood_mask, neighborhood_mask.rect().adjusted(1, 1, -1, -1), peak_candidates, + QPoint(0, 0)); + + // Cells in the neighborhood of a peak candidate fall into two categories: + // 1. The cell has a lower value than the peak candidate. + // 2. The cell has the same value as the peak candidate, + // but it has a cell with a greater value in its neighborhood. + // The second case indicates that our candidate is not relly a peak. + // To test for the second case we are going to increment the values + // of the cells in the neighborhood of peak candidates, find the peak + // candidates again and analize the differences. + + incrementMaskedPadded(neighborhood_mask); + neighborhood_mask.release(); + + BinaryImage diff(findPeakCandidatesNonPadded()); + rasterOp>(diff, peak_candidates); + + // If a bin that has changed its state was a part of a peak candidate, + // it means a neighboring bin went from equal to a greater value, + // which indicates that such candidate is not a peak. + const BinaryImage not_peaks(seedFill(diff, peak_candidates, CONN8)); + diff.release(); + + rasterOp>(peak_candidates, not_peaks); + + return peak_candidates; } // SEDM::findPeaksDestructive inline uint32_t SEDM::distSq(const int x1, const int x2, const uint32_t dy_sq) { - if (dy_sq == INF_DIST) { - return INF_DIST; - } - const int dx = x1 - x2; - const uint32_t dx_sq = dx * dx; + if (dy_sq == INF_DIST) { + return INF_DIST; + } + const int dx = x1 - x2; + const uint32_t dx_sq = dx * dx; - return dx_sq + dy_sq; + return dx_sq + dy_sq; } void SEDM::processColumns() { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - uint32_t* p_sqd = &m_data[0]; - for (int x = 0; x < width; ++x, ++p_sqd) { - // (d + 1)^2 = d^2 + 2d + 1 - uint32_t b = 1; // 2d + 1 in the above formula. - for (int todo = height - 1; todo > 0; --todo) { - const uint32_t sqd = *p_sqd + b; - p_sqd += width; - if (*p_sqd > sqd) { - *p_sqd = sqd; - b += 2; - } else { - b = 1; - } - } + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + uint32_t* p_sqd = &m_data[0]; + for (int x = 0; x < width; ++x, ++p_sqd) { + // (d + 1)^2 = d^2 + 2d + 1 + uint32_t b = 1; // 2d + 1 in the above formula. + for (int todo = height - 1; todo > 0; --todo) { + const uint32_t sqd = *p_sqd + b; + p_sqd += width; + if (*p_sqd > sqd) { + *p_sqd = sqd; + b += 2; + } else { + b = 1; + } + } + b = 1; + for (int todo = height - 1; todo > 0; --todo) { + const uint32_t sqd = *p_sqd + b; + p_sqd -= width; + if (*p_sqd > sqd) { + *p_sqd = sqd; + b += 2; + } else { b = 1; - for (int todo = height - 1; todo > 0; --todo) { - const uint32_t sqd = *p_sqd + b; - p_sqd -= width; - if (*p_sqd > sqd) { - *p_sqd = sqd; - b += 2; - } else { - b = 1; - } - } + } } + } } // SEDM::processColumns void SEDM::processColumns(ConnectivityMap& cmap) { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - uint32_t* p_sqd = &m_data[0]; - uint32_t* p_label = cmap.paddedData(); - for (int x = 0; x < width; ++x, ++p_sqd, ++p_label) { - // (d + 1)^2 = d^2 + 2d + 1 - uint32_t b = 1; // 2d + 1 in the above formula. - for (int todo = height - 1; todo > 0; --todo) { - const uint32_t sqd = *p_sqd + b; - p_sqd += width; - p_label += width; - if (sqd < *p_sqd) { - *p_sqd = sqd; - *p_label = p_label[-width]; - b += 2; - } else { - b = 1; - } - } + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + uint32_t* p_sqd = &m_data[0]; + uint32_t* p_label = cmap.paddedData(); + for (int x = 0; x < width; ++x, ++p_sqd, ++p_label) { + // (d + 1)^2 = d^2 + 2d + 1 + uint32_t b = 1; // 2d + 1 in the above formula. + for (int todo = height - 1; todo > 0; --todo) { + const uint32_t sqd = *p_sqd + b; + p_sqd += width; + p_label += width; + if (sqd < *p_sqd) { + *p_sqd = sqd; + *p_label = p_label[-width]; + b += 2; + } else { + b = 1; + } + } + b = 1; + for (int todo = height - 1; todo > 0; --todo) { + const uint32_t sqd = *p_sqd + b; + p_sqd -= width; + p_label -= width; + if (sqd < *p_sqd) { + *p_sqd = sqd; + *p_label = p_label[width]; + b += 2; + } else { b = 1; - for (int todo = height - 1; todo > 0; --todo) { - const uint32_t sqd = *p_sqd + b; - p_sqd -= width; - p_label -= width; - if (sqd < *p_sqd) { - *p_sqd = sqd; - *p_label = p_label[width]; - b += 2; - } else { - b = 1; - } - } + } } + } } // SEDM::processColumns void SEDM::processRows() { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - std::vector s(width, 0); - std::vector t(width, 0); - std::vector row_copy(width, 0); - - uint32_t* line = &m_data[0]; - for (int y = 0; y < height; ++y, line += width) { - int q = 0; - s[0] = 0; - t[0] = 0; - for (int x = 1; x < width; ++x) { - while (q >= 0 && distSq(t[q], s[q], line[s[q]]) > distSq(t[q], x, line[x])) { - --q; - } - - if (q < 0) { - q = 0; - s[0] = x; - } else { - const int x2 = s[q]; - if ((line[x] != INF_DIST) && (line[x2] != INF_DIST)) { - int w = (x * x + line[x]) - (x2 * x2 + line[x2]); - w /= (x - x2) << 1; - ++w; - if ((unsigned) w < (unsigned) width) { - ++q; - s[q] = x; - t[q] = w; - } - } - } + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + std::vector s(width, 0); + std::vector t(width, 0); + std::vector row_copy(width, 0); + + uint32_t* line = &m_data[0]; + for (int y = 0; y < height; ++y, line += width) { + int q = 0; + s[0] = 0; + t[0] = 0; + for (int x = 1; x < width; ++x) { + while (q >= 0 && distSq(t[q], s[q], line[s[q]]) > distSq(t[q], x, line[x])) { + --q; + } + + if (q < 0) { + q = 0; + s[0] = x; + } else { + const int x2 = s[q]; + if ((line[x] != INF_DIST) && (line[x2] != INF_DIST)) { + int w = (x * x + line[x]) - (x2 * x2 + line[x2]); + w /= (x - x2) << 1; + ++w; + if ((unsigned) w < (unsigned) width) { + ++q; + s[q] = x; + t[q] = w; + } } + } + } - memcpy(&row_copy[0], line, width * sizeof(*line)); + memcpy(&row_copy[0], line, width * sizeof(*line)); - for (int x = width - 1; x >= 0; --x) { - const int x2 = s[q]; - line[x] = distSq(x, x2, row_copy[x2]); - if (x == t[q]) { - --q; - } - } + for (int x = width - 1; x >= 0; --x) { + const int x2 = s[q]; + line[x] = distSq(x, x2, row_copy[x2]); + if (x == t[q]) { + --q; + } } + } } // SEDM::processRows void SEDM::processRows(ConnectivityMap& cmap) { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - std::vector s(width, 0); - std::vector t(width, 0); - std::vector row_copy(width, 0); - std::vector cmap_row_copy(width, 0); - - uint32_t* line = &m_data[0]; - uint32_t* cmap_line = cmap.paddedData(); - for (int y = 0; y < height; ++y, line += width, cmap_line += width) { - int q = 0; - s[0] = 0; - t[0] = 0; - for (int x = 1; x < width; ++x) { - while (q >= 0 && distSq(t[q], s[q], line[s[q]]) > distSq(t[q], x, line[x])) { - --q; - } - - if (q < 0) { - q = 0; - s[0] = x; - } else { - const int x2 = s[q]; - if ((line[x] != INF_DIST) && (line[x2] != INF_DIST)) { - int w = (x * x + line[x]) - (x2 * x2 + line[x2]); - w /= (x - x2) << 1; - ++w; - if ((unsigned) w < (unsigned) width) { - ++q; - s[q] = x; - t[q] = w; - } - } - } + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + std::vector s(width, 0); + std::vector t(width, 0); + std::vector row_copy(width, 0); + std::vector cmap_row_copy(width, 0); + + uint32_t* line = &m_data[0]; + uint32_t* cmap_line = cmap.paddedData(); + for (int y = 0; y < height; ++y, line += width, cmap_line += width) { + int q = 0; + s[0] = 0; + t[0] = 0; + for (int x = 1; x < width; ++x) { + while (q >= 0 && distSq(t[q], s[q], line[s[q]]) > distSq(t[q], x, line[x])) { + --q; + } + + if (q < 0) { + q = 0; + s[0] = x; + } else { + const int x2 = s[q]; + if ((line[x] != INF_DIST) && (line[x2] != INF_DIST)) { + int w = (x * x + line[x]) - (x2 * x2 + line[x2]); + w /= (x - x2) << 1; + ++w; + if ((unsigned) w < (unsigned) width) { + ++q; + s[q] = x; + t[q] = w; + } } + } + } - memcpy(&row_copy[0], line, width * sizeof(*line)); - memcpy(&cmap_row_copy[0], cmap_line, width * sizeof(*cmap_line)); + memcpy(&row_copy[0], line, width * sizeof(*line)); + memcpy(&cmap_row_copy[0], cmap_line, width * sizeof(*cmap_line)); - for (int x = width - 1; x >= 0; --x) { - const int x2 = s[q]; - line[x] = distSq(x, x2, row_copy[x2]); - cmap_line[x] = cmap_row_copy[x2]; - if (x == t[q]) { - --q; - } - } + for (int x = width - 1; x >= 0; --x) { + const int x2 = s[q]; + line[x] = distSq(x, x2, row_copy[x2]); + cmap_line[x] = cmap_row_copy[x2]; + if (x == t[q]) { + --q; + } } + } } // SEDM::processRows /*====================== Peak finding stuff goes below ====================*/ BinaryImage SEDM::findPeakCandidatesNonPadded() const { - std::vector maxed(m_data.size(), 0); + std::vector maxed(m_data.size(), 0); - // Every cell becomes the maximum of itself and its neighbors. - max3x3(&m_data[0], &maxed[0]); + // Every cell becomes the maximum of itself and its neighbors. + max3x3(&m_data[0], &maxed[0]); - return buildEqualMapNonPadded(&m_data[0], &maxed[0]); + return buildEqualMapNonPadded(&m_data[0], &maxed[0]); } BinaryImage SEDM::buildEqualMapNonPadded(const uint32_t* src1, const uint32_t* src2) const { - const int width = m_size.width(); - const int height = m_size.height(); - - BinaryImage dst(width, height, WHITE); - uint32_t* dst_line = dst.data(); - const int dst_wpl = dst.wordsPerLine(); - const int src_stride = m_stride; - const uint32_t* src1_line = src1 + src_stride + 1; - const uint32_t* src2_line = src2 + src_stride + 1; - const uint32_t msb = uint32_t(1) << 31; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (std::max(src1_line[x], src2_line[x]) - std::min(src1_line[x], src2_line[x]) == 0) { - dst_line[x >> 5] |= msb >> (x & 31); - } - } - dst_line += dst_wpl; - src1_line += src_stride; - src2_line += src_stride; + const int width = m_size.width(); + const int height = m_size.height(); + + BinaryImage dst(width, height, WHITE); + uint32_t* dst_line = dst.data(); + const int dst_wpl = dst.wordsPerLine(); + const int src_stride = m_stride; + const uint32_t* src1_line = src1 + src_stride + 1; + const uint32_t* src2_line = src2 + src_stride + 1; + const uint32_t msb = uint32_t(1) << 31; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (std::max(src1_line[x], src2_line[x]) - std::min(src1_line[x], src2_line[x]) == 0) { + dst_line[x >> 5] |= msb >> (x & 31); + } } + dst_line += dst_wpl; + src1_line += src_stride; + src2_line += src_stride; + } - return dst; + return dst; } void SEDM::max3x3(const uint32_t* src, uint32_t* dst) const { - std::vector tmp(m_data.size(), 0); - max3x1(src, &tmp[0]); - max1x3(&tmp[0], dst); + std::vector tmp(m_data.size(), 0); + max3x1(src, &tmp[0]); + max1x3(&tmp[0], dst); } void SEDM::max3x1(const uint32_t* src, uint32_t* dst) const { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - const uint32_t* src_line = &src[0]; - uint32_t* dst_line = &dst[0]; - - for (int y = 0; y < height; ++y) { - // First column (no left neighbors). - int x = 0; - dst_line[x] = std::max(src_line[x], src_line[x + 1]); - - for (++x; x < width - 1; ++x) { - const uint32_t prev = src_line[x - 1]; - const uint32_t cur = src_line[x]; - const uint32_t next = src_line[x + 1]; - dst_line[x] = std::max(prev, std::max(cur, next)); - } + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + const uint32_t* src_line = &src[0]; + uint32_t* dst_line = &dst[0]; + + for (int y = 0; y < height; ++y) { + // First column (no left neighbors). + int x = 0; + dst_line[x] = std::max(src_line[x], src_line[x + 1]); + + for (++x; x < width - 1; ++x) { + const uint32_t prev = src_line[x - 1]; + const uint32_t cur = src_line[x]; + const uint32_t next = src_line[x + 1]; + dst_line[x] = std::max(prev, std::max(cur, next)); + } - // Last column (no right neighbors). - dst_line[x] = std::max(src_line[x], src_line[x - 1]); + // Last column (no right neighbors). + dst_line[x] = std::max(src_line[x], src_line[x - 1]); - src_line += width; - dst_line += width; - } + src_line += width; + dst_line += width; + } } void SEDM::max1x3(const uint32_t* src, uint32_t* dst) const { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - // First row (no top neighbors). - const uint32_t* p_src = &src[0]; - uint32_t* p_dst = &dst[0]; + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + // First row (no top neighbors). + const uint32_t* p_src = &src[0]; + uint32_t* p_dst = &dst[0]; + for (int x = 0; x < width; ++x) { + *p_dst = std::max(p_src[0], p_src[width]); + ++p_src; + ++p_dst; + } + + for (int y = 1; y < height - 1; ++y) { for (int x = 0; x < width; ++x) { - *p_dst = std::max(p_src[0], p_src[width]); - ++p_src; - ++p_dst; + const uint32_t prev = p_src[x - width]; + const uint32_t cur = p_src[x]; + const uint32_t next = p_src[x + width]; + p_dst[x] = std::max(prev, std::max(cur, next)); } - for (int y = 1; y < height - 1; ++y) { - for (int x = 0; x < width; ++x) { - const uint32_t prev = p_src[x - width]; - const uint32_t cur = p_src[x]; - const uint32_t next = p_src[x + width]; - p_dst[x] = std::max(prev, std::max(cur, next)); - } - - p_src += width; - p_dst += width; - } + p_src += width; + p_dst += width; + } - // Last row (no bottom neighbors). - for (int x = 0; x < width; ++x) { - *p_dst = std::max(p_src[0], p_src[-width]); - ++p_src; - ++p_dst; - } + // Last row (no bottom neighbors). + for (int x = 0; x < width; ++x) { + *p_dst = std::max(p_src[0], p_src[-width]); + ++p_src; + ++p_dst; + } } void SEDM::incrementMaskedPadded(const BinaryImage& mask) { - const int width = m_size.width() + 2; - const int height = m_size.height() + 2; - - uint32_t* data_line = &m_data[0]; - const uint32_t* mask_line = mask.data(); - const int mask_wpl = mask.wordsPerLine(); - - const uint32_t msb = uint32_t(1) << 31; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (mask_line[x >> 5] & (msb >> (x & 31))) { - ++data_line[x]; - } - } - data_line += width; - mask_line += mask_wpl; + const int width = m_size.width() + 2; + const int height = m_size.height() + 2; + + uint32_t* data_line = &m_data[0]; + const uint32_t* mask_line = mask.data(); + const int mask_wpl = mask.wordsPerLine(); + + const uint32_t msb = uint32_t(1) << 31; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (mask_line[x >> 5] & (msb >> (x & 31))) { + ++data_line[x]; + } } + data_line += width; + mask_line += mask_wpl; + } } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SEDM.h b/imageproc/SEDM.h index 9df7f5ec4..d70f2557b 100644 --- a/imageproc/SEDM.h +++ b/imageproc/SEDM.h @@ -19,10 +19,10 @@ #ifndef IMAGEPROC_SEDM_H_ #define IMAGEPROC_SEDM_H_ -#include "foundation/FlagOps.h" -#include #include #include +#include +#include "foundation/FlagOps.h" namespace imageproc { class BinaryImage; @@ -41,172 +41,164 @@ class ConnectivityMap; * Morphology and its Applications to Image and Signal Processing. */ class SEDM { -public: - /** - * \brief The type of distance to compute. - */ - enum DistType { - /** - * For every black pixel, the distance to the nearest - * white one is computed. - */ - DIST_TO_WHITE, - - /** - * For every white pixel, the distance to the nearest - * black one is computed. - */ - DIST_TO_BLACK - }; - - /** - * \brief Determines whether to compute the distance to borders. - */ - enum Borders { - DIST_TO_NO_BORDERS = 0, - DIST_TO_TOP_BORDER = 1, - DIST_TO_LEFT_BORDER = 2, - DIST_TO_RIGHT_BORDER = 4, - DIST_TO_BOTTOM_BORDER = 8, - DIST_TO_VERT_BORDERS = DIST_TO_LEFT_BORDER | DIST_TO_RIGHT_BORDER, - DIST_TO_HOR_BORDERS = DIST_TO_TOP_BORDER | DIST_TO_BOTTOM_BORDER, - DIST_TO_ALL_BORDERS = DIST_TO_HOR_BORDERS | DIST_TO_VERT_BORDERS - }; - - /** - * \brief The infinite distance. - * - * If the input image doesn't have any objects to compute - * distance to, and borders are to DIST_TO_NO_BORDERS, - * then the whole distance map will consist of these values. - */ - static const uint32_t INF_DIST; - - /** - * \brief Constructs a null distance map. - * - * The data() method returns null on such maps. - */ - SEDM(); - - /** - * \brief Build a distance map from a binary image. - * - * For every black pixel in the image, the distance - * map will store the squared straight-line distance - * to the nearest white pixel. The distance between - * two pixels is the distance between their center points. - * - * \param image The image to compute the distance map from. - * \param dist_type Determines whether to compute distance - * to white or black pixels in the image. - * \param borders Determines whether to compute - * distance to particular borders. The borders - * are assumed to lie one pixel off the image area. - */ - explicit SEDM(const BinaryImage& image, DistType dist_type = DIST_TO_WHITE, Borders borders = DIST_TO_ALL_BORDERS); - - /** - * \brief Build a distance map from a connectivity map. - * - * For every zero label in the connectivity map, the distance - * map will store the squared straight-line distance to the - * nearest non-zero label. - * \note Besides building a distance map, it will modify - * the connectivity map by overwriting zero labels - * with the nearest non-zero label. This applies to - * the padding areas of the connectivity map as well. - */ - explicit SEDM(ConnectivityMap& cmap); - - SEDM(const SEDM& other); - - SEDM& operator=(const SEDM& other); - - void swap(SEDM& other); - - /** - * \brief Return the dimensions of the distance map. - */ - QSize size() const { - return m_size; - } - - /** - * \brief Return the number of 32bit words in a line. - * - * This value is going to be size().width() + 2. - */ - int stride() const { - return m_stride; - } - - /** - * \brief Return a matrix of squared distances in row-major order. - */ - uint32_t* data() { - return m_pData; - } - + public: + /** + * \brief The type of distance to compute. + */ + enum DistType { /** - * \brief Return a matrix of squared distances in row-major order. + * For every black pixel, the distance to the nearest + * white one is computed. */ - const uint32_t* data() const { - return m_pData; - } + DIST_TO_WHITE, /** - * \brief Finds peaks on the distance map, altering it in the process. - * - * A peak region is a 4-connected group of cells having the same - * distance value, that doesn't have any neighbors with a higher - * distance value. - * - * Peaks on a Euclidean distance map are also known as ultimate - * eroded points. - * - * The Borders flags used to build this SEDM also affect the peaks - * on it. If the distance to a particular object was considered, - * that border was considered an object, so a peak may be found - * between this border and another object. - * - * Peaks are returned in a BinaryImage, and the distance - * map is altered in an uspecified way. + * For every white pixel, the distance to the nearest + * black one is computed. */ - BinaryImage findPeaksDestructive(); - -private: - static uint32_t distSq(int x1, int x2, uint32_t dy_sq); - - void processColumns(); - - void processColumns(ConnectivityMap& cmap); - - void processRows(); - - void processRows(ConnectivityMap& cmap); - - BinaryImage findPeakCandidatesNonPadded() const; - - BinaryImage buildEqualMapNonPadded(const uint32_t* src1, const uint32_t* src2) const; - - void max3x3(const uint32_t* src, uint32_t* dst) const; - - void max3x1(const uint32_t* src, uint32_t* dst) const; - - void max1x3(const uint32_t* src, uint32_t* dst) const; - - void incrementMaskedPadded(const BinaryImage& mask); - - std::vector m_data; - uint32_t* m_pData; - QSize m_size; - int m_stride; + DIST_TO_BLACK + }; + + /** + * \brief Determines whether to compute the distance to borders. + */ + enum Borders { + DIST_TO_NO_BORDERS = 0, + DIST_TO_TOP_BORDER = 1, + DIST_TO_LEFT_BORDER = 2, + DIST_TO_RIGHT_BORDER = 4, + DIST_TO_BOTTOM_BORDER = 8, + DIST_TO_VERT_BORDERS = DIST_TO_LEFT_BORDER | DIST_TO_RIGHT_BORDER, + DIST_TO_HOR_BORDERS = DIST_TO_TOP_BORDER | DIST_TO_BOTTOM_BORDER, + DIST_TO_ALL_BORDERS = DIST_TO_HOR_BORDERS | DIST_TO_VERT_BORDERS + }; + + /** + * \brief The infinite distance. + * + * If the input image doesn't have any objects to compute + * distance to, and borders are to DIST_TO_NO_BORDERS, + * then the whole distance map will consist of these values. + */ + static const uint32_t INF_DIST; + + /** + * \brief Constructs a null distance map. + * + * The data() method returns null on such maps. + */ + SEDM(); + + /** + * \brief Build a distance map from a binary image. + * + * For every black pixel in the image, the distance + * map will store the squared straight-line distance + * to the nearest white pixel. The distance between + * two pixels is the distance between their center points. + * + * \param image The image to compute the distance map from. + * \param dist_type Determines whether to compute distance + * to white or black pixels in the image. + * \param borders Determines whether to compute + * distance to particular borders. The borders + * are assumed to lie one pixel off the image area. + */ + explicit SEDM(const BinaryImage& image, DistType dist_type = DIST_TO_WHITE, Borders borders = DIST_TO_ALL_BORDERS); + + /** + * \brief Build a distance map from a connectivity map. + * + * For every zero label in the connectivity map, the distance + * map will store the squared straight-line distance to the + * nearest non-zero label. + * \note Besides building a distance map, it will modify + * the connectivity map by overwriting zero labels + * with the nearest non-zero label. This applies to + * the padding areas of the connectivity map as well. + */ + explicit SEDM(ConnectivityMap& cmap); + + SEDM(const SEDM& other); + + SEDM& operator=(const SEDM& other); + + void swap(SEDM& other); + + /** + * \brief Return the dimensions of the distance map. + */ + QSize size() const { return m_size; } + + /** + * \brief Return the number of 32bit words in a line. + * + * This value is going to be size().width() + 2. + */ + int stride() const { return m_stride; } + + /** + * \brief Return a matrix of squared distances in row-major order. + */ + uint32_t* data() { return m_plainData; } + + /** + * \brief Return a matrix of squared distances in row-major order. + */ + const uint32_t* data() const { return m_plainData; } + + /** + * \brief Finds peaks on the distance map, altering it in the process. + * + * A peak region is a 4-connected group of cells having the same + * distance value, that doesn't have any neighbors with a higher + * distance value. + * + * Peaks on a Euclidean distance map are also known as ultimate + * eroded points. + * + * The Borders flags used to build this SEDM also affect the peaks + * on it. If the distance to a particular object was considered, + * that border was considered an object, so a peak may be found + * between this border and another object. + * + * Peaks are returned in a BinaryImage, and the distance + * map is altered in an uspecified way. + */ + BinaryImage findPeaksDestructive(); + + private: + static uint32_t distSq(int x1, int x2, uint32_t dy_sq); + + void processColumns(); + + void processColumns(ConnectivityMap& cmap); + + void processRows(); + + void processRows(ConnectivityMap& cmap); + + BinaryImage findPeakCandidatesNonPadded() const; + + BinaryImage buildEqualMapNonPadded(const uint32_t* src1, const uint32_t* src2) const; + + void max3x3(const uint32_t* src, uint32_t* dst) const; + + void max3x1(const uint32_t* src, uint32_t* dst) const; + + void max1x3(const uint32_t* src, uint32_t* dst) const; + + void incrementMaskedPadded(const BinaryImage& mask); + + std::vector m_data; + uint32_t* m_plainData; + QSize m_size; + int m_stride; }; inline void swap(SEDM& o1, SEDM& o2) { - o1.swap(o2); + o1.swap(o2); } DEFINE_FLAG_OPS(SEDM::Borders) diff --git a/imageproc/SavGolFilter.cpp b/imageproc/SavGolFilter.cpp index 2b31e2f14..e61540cf6 100644 --- a/imageproc/SavGolFilter.cpp +++ b/imageproc/SavGolFilter.cpp @@ -17,136 +17,135 @@ */ #include "SavGolFilter.h" -#include "SavGolKernel.h" #include "Grayscale.h" +#include "SavGolKernel.h" namespace imageproc { namespace { int calcNumTerms(const int hor_degree, const int vert_degree) { - return (hor_degree + 1) * (vert_degree + 1); + return (hor_degree + 1) * (vert_degree + 1); } class Kernel : public SavGolKernel { -public: - Kernel(const QSize& size, const QPoint& origin, int hor_degree, int vert_degree) - : SavGolKernel(size, origin, hor_degree, vert_degree) { - } + public: + Kernel(const QSize& size, const QPoint& origin, int hor_degree, int vert_degree) + : SavGolKernel(size, origin, hor_degree, vert_degree) {} - void convolve(uint8_t* dst, const uint8_t* src_top_left, int src_bpl) const; + void convolve(uint8_t* dst, const uint8_t* src_top_left, int src_bpl) const; }; inline void Kernel::convolve(uint8_t* dst, const uint8_t* src_top_left, int src_bpl) const { - const uint8_t* p_src = src_top_left; - const float* p_kernel = data(); - float sum = 0.5; // For rounding purposes. - const int w = width(); - const int h = height(); - - for (int y = 0; y < h; ++y, p_src += src_bpl) { - for (int x = 0; x < w; ++x) { - sum += p_src[x] * *p_kernel; - ++p_kernel; - } + const uint8_t* p_src = src_top_left; + const float* p_kernel = data(); + float sum = 0.5; // For rounding purposes. + const int w = width(); + const int h = height(); + + for (int y = 0; y < h; ++y, p_src += src_bpl) { + for (int x = 0; x < w; ++x) { + sum += p_src[x] * *p_kernel; + ++p_kernel; } + } - const auto val = static_cast(sum); - *dst = static_cast(qBound(0, val, 255)); + const auto val = static_cast(sum); + *dst = static_cast(qBound(0, val, 255)); } QImage savGolFilterGrayToGray(const QImage& src, const QSize& window_size, const int hor_degree, const int vert_degree) { - const int width = src.width(); - const int height = src.height(); - - // Kernel width and height. - const int kw = window_size.width(); - const int kh = window_size.height(); - - if ((kw > width) || (kh > height)) { - return src; - } - - /* - * Consider a 5x5 kernel: - * |x|x|T|x|x| - * |x|x|T|x|x| - * |L|L|C|R|R| - * |x|x|B|x|x| - * |x|x|B|x|x| - */ - - // Co-ordinates of the central point (C) of the kernel. - const QPoint k_center(kw / 2, kh / 2); - - // Origin is the current hot spot of the kernel. - // Normally it's at k_center, but sometimes we move - // it to other locations to avoid parts of the kernel - // from going outside of the image area. - QPoint k_origin; - - // Length of the top segment (T) of the kernel. - const int k_top = k_center.y(); - - // Length of the bottom segment (B) of the kernel. - const int k_bottom = kh - k_top - 1; - - // Length of the left segment (L) of the kernel. - const int k_left = k_center.x(); - // Length of the right segment (R) of the kernel. - const int k_right = kw - k_left - 1; - - const uint8_t* const src_data = src.bits(); - const int src_bpl = src.bytesPerLine(); - - QImage dst(width, height, QImage::Format_Indexed8); - dst.setColorTable(createGrayscalePalette()); - if ((width > 0) && (height > 0) && dst.isNull()) { - throw std::bad_alloc(); - } - - uint8_t* const dst_data = dst.bits(); - const int dst_bpl = dst.bytesPerLine(); - // Top-left corner. - const uint8_t* src_line = src_data; - uint8_t* dst_line = dst_data; - Kernel kernel(window_size, QPoint(0, 0), hor_degree, vert_degree); - for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { - k_origin.setY(y); - for (int x = 0; x < k_left; ++x) { - k_origin.setX(x); - kernel.recalcForOrigin(k_origin); - kernel.convolve(dst_line + x, src_line, src_bpl); - } + const int width = src.width(); + const int height = src.height(); + + // Kernel width and height. + const int kw = window_size.width(); + const int kh = window_size.height(); + + if ((kw > width) || (kh > height)) { + return src; + } + + /* + * Consider a 5x5 kernel: + * |x|x|T|x|x| + * |x|x|T|x|x| + * |L|L|C|R|R| + * |x|x|B|x|x| + * |x|x|B|x|x| + */ + + // Co-ordinates of the central point (C) of the kernel. + const QPoint k_center(kw / 2, kh / 2); + + // Origin is the current hot spot of the kernel. + // Normally it's at k_center, but sometimes we move + // it to other locations to avoid parts of the kernel + // from going outside of the image area. + QPoint k_origin; + + // Length of the top segment (T) of the kernel. + const int k_top = k_center.y(); + + // Length of the bottom segment (B) of the kernel. + const int k_bottom = kh - k_top - 1; + + // Length of the left segment (L) of the kernel. + const int k_left = k_center.x(); + // Length of the right segment (R) of the kernel. + const int k_right = kw - k_left - 1; + + const uint8_t* const src_data = src.bits(); + const int src_bpl = src.bytesPerLine(); + + QImage dst(width, height, QImage::Format_Indexed8); + dst.setColorTable(createGrayscalePalette()); + if ((width > 0) && (height > 0) && dst.isNull()) { + throw std::bad_alloc(); + } + + uint8_t* const dst_data = dst.bits(); + const int dst_bpl = dst.bytesPerLine(); + // Top-left corner. + const uint8_t* src_line = src_data; + uint8_t* dst_line = dst_data; + Kernel kernel(window_size, QPoint(0, 0), hor_degree, vert_degree); + for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { + k_origin.setY(y); + for (int x = 0; x < k_left; ++x) { + k_origin.setX(x); + kernel.recalcForOrigin(k_origin); + kernel.convolve(dst_line + x, src_line, src_bpl); } - - // Top area between two corners. - k_origin.setX(k_center.x()); - src_line = src_data - k_left; - dst_line = dst_data; - for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { - k_origin.setY(y); - kernel.recalcForOrigin(k_origin); - for (int x = k_left; x < width - k_right; ++x) { - kernel.convolve(dst_line + x, src_line + x, src_bpl); - } + } + + // Top area between two corners. + k_origin.setX(k_center.x()); + src_line = src_data - k_left; + dst_line = dst_data; + for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { + k_origin.setY(y); + kernel.recalcForOrigin(k_origin); + for (int x = k_left; x < width - k_right; ++x) { + kernel.convolve(dst_line + x, src_line + x, src_bpl); } - // Top-right corner. - k_origin.setY(0); - src_line = src_data + width - kw; - dst_line = dst_data; - for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { - k_origin.setX(k_center.x() + 1); - for (int x = width - k_right; x < width; ++x) { - kernel.recalcForOrigin(k_origin); - kernel.convolve(dst_line + x, src_line, src_bpl); - k_origin.rx() += 1; - } - k_origin.ry() += 1; + } + // Top-right corner. + k_origin.setY(0); + src_line = src_data + width - kw; + dst_line = dst_data; + for (int y = 0; y < k_top; ++y, dst_line += dst_bpl) { + k_origin.setX(k_center.x() + 1); + for (int x = width - k_right; x < width; ++x) { + kernel.recalcForOrigin(k_origin); + kernel.convolve(dst_line + x, src_line, src_bpl); + k_origin.rx() += 1; } - // Central area. + k_origin.ry() += 1; + } + // Central area. #if 0 // Simple but slow implementation. kernel.recalcForOrigin(k_center); @@ -160,138 +159,138 @@ QImage savGolFilterGrayToGray(const QImage& src, dst_line += dst_bpl; } #else - // Take advantage of Savitzky-Golay filter being separable. - const SavGolKernel hor_kernel(QSize(window_size.width(), 1), QPoint(k_center.x(), 0), hor_degree, 0); - const SavGolKernel vert_kernel(QSize(1, window_size.height()), QPoint(0, k_center.y()), 0, vert_degree); - - const int shift = kw - 1; - - // Allocate a 16-byte aligned temporary storage. - // That may help the compiler to emit efficient SSE code. - const int temp_stride = (width - shift + 3) & ~3; - AlignedArray temp_array(temp_stride * height); - // Horizontal pass. - src_line = src_data - shift; - float* temp_line = temp_array.data() - shift; - for (int y = 0; y < height; ++y) { - for (int i = shift; i < width; ++i) { - float sum = 0.0f; - - const uint8_t* src = src_line + i; - for (int j = 0; j < kw; ++j) { - sum += src[j] * hor_kernel[j]; - } - temp_line[i] = sum; - } - temp_line += temp_stride; - src_line += src_bpl; + // Take advantage of Savitzky-Golay filter being separable. + const SavGolKernel hor_kernel(QSize(window_size.width(), 1), QPoint(k_center.x(), 0), hor_degree, 0); + const SavGolKernel vert_kernel(QSize(1, window_size.height()), QPoint(0, k_center.y()), 0, vert_degree); + + const int shift = kw - 1; + + // Allocate a 16-byte aligned temporary storage. + // That may help the compiler to emit efficient SSE code. + const int temp_stride = (width - shift + 3) & ~3; + AlignedArray temp_array(temp_stride * height); + // Horizontal pass. + src_line = src_data - shift; + float* temp_line = temp_array.data() - shift; + for (int y = 0; y < height; ++y) { + for (int i = shift; i < width; ++i) { + float sum = 0.0f; + + const uint8_t* src = src_line + i; + for (int j = 0; j < kw; ++j) { + sum += src[j] * hor_kernel[j]; + } + temp_line[i] = sum; } - // Vertical pass. - dst_line = dst_data + k_top * dst_bpl + k_left - shift; - temp_line = temp_array.data() - shift; - for (int y = k_top; y < height - k_bottom; ++y) { - for (int i = shift; i < width; ++i) { - float sum = 0.0f; - - float* tmp = temp_line + i; - for (int j = 0; j < kh; ++j, tmp += temp_stride) { - sum += *tmp * vert_kernel[j]; - } - const auto val = static_cast(sum); - dst_line[i] = static_cast(qBound(0, val, 255)); - } - - temp_line += temp_stride; - dst_line += dst_bpl; + temp_line += temp_stride; + src_line += src_bpl; + } + // Vertical pass. + dst_line = dst_data + k_top * dst_bpl + k_left - shift; + temp_line = temp_array.data() - shift; + for (int y = k_top; y < height - k_bottom; ++y) { + for (int i = shift; i < width; ++i) { + float sum = 0.0f; + + float* tmp = temp_line + i; + for (int j = 0; j < kh; ++j, tmp += temp_stride) { + sum += *tmp * vert_kernel[j]; + } + const auto val = static_cast(sum); + dst_line[i] = static_cast(qBound(0, val, 255)); } + + temp_line += temp_stride; + dst_line += dst_bpl; + } #endif // if 0 - // Left area between two corners. - k_origin.setX(0); - k_origin.setY(k_center.y() + 1); - for (int x = 0; x < k_left; ++x) { - src_line = src_data; - dst_line = dst_data + dst_bpl * k_top; - - kernel.recalcForOrigin(k_origin); - for (int y = k_top; y < height - k_bottom; ++y) { - kernel.convolve(dst_line + x, src_line, src_bpl); - src_line += src_bpl; - dst_line += dst_bpl; - } - k_origin.rx() += 1; - } - // Right area between two corners. - k_origin.setX(k_center.x() + 1); - k_origin.setY(k_center.y()); - for (int x = width - k_right; x < width; ++x) { - src_line = src_data + width - kw; - dst_line = dst_data + dst_bpl * k_top; - - kernel.recalcForOrigin(k_origin); - for (int y = k_top; y < height - k_bottom; ++y) { - kernel.convolve(dst_line + x, src_line, src_bpl); - src_line += src_bpl; - dst_line += dst_bpl; - } - k_origin.rx() += 1; - } + // Left area between two corners. + k_origin.setX(0); + k_origin.setY(k_center.y() + 1); + for (int x = 0; x < k_left; ++x) { + src_line = src_data; + dst_line = dst_data + dst_bpl * k_top; - // Bottom-left corner. - k_origin.setY(k_center.y() + 1); - src_line = src_data + src_bpl * (height - kh); - dst_line = dst_data + dst_bpl * (height - k_bottom); - for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { - for (int x = 0; x < k_left; ++x) { - k_origin.setX(x); - kernel.recalcForOrigin(k_origin); - kernel.convolve(dst_line + x, src_line, src_bpl); - } - k_origin.ry() += 1; + kernel.recalcForOrigin(k_origin); + for (int y = k_top; y < height - k_bottom; ++y) { + kernel.convolve(dst_line + x, src_line, src_bpl); + src_line += src_bpl; + dst_line += dst_bpl; } + k_origin.rx() += 1; + } + // Right area between two corners. + k_origin.setX(k_center.x() + 1); + k_origin.setY(k_center.y()); + for (int x = width - k_right; x < width; ++x) { + src_line = src_data + width - kw; + dst_line = dst_data + dst_bpl * k_top; - // Bottom area between two corners. - k_origin.setX(k_center.x()); - k_origin.setY(k_center.y() + 1); - src_line = src_data + src_bpl * (height - kh) - k_left; - dst_line = dst_data + dst_bpl * (height - k_bottom); - for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { - kernel.recalcForOrigin(k_origin); - for (int x = k_left; x < width - k_right; ++x) { - kernel.convolve(dst_line + x, src_line + x, src_bpl); - } - k_origin.ry() += 1; + kernel.recalcForOrigin(k_origin); + for (int y = k_top; y < height - k_bottom; ++y) { + kernel.convolve(dst_line + x, src_line, src_bpl); + src_line += src_bpl; + dst_line += dst_bpl; + } + k_origin.rx() += 1; + } + + // Bottom-left corner. + k_origin.setY(k_center.y() + 1); + src_line = src_data + src_bpl * (height - kh); + dst_line = dst_data + dst_bpl * (height - k_bottom); + for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { + for (int x = 0; x < k_left; ++x) { + k_origin.setX(x); + kernel.recalcForOrigin(k_origin); + kernel.convolve(dst_line + x, src_line, src_bpl); } - // Bottom-right corner. - k_origin.setY(k_center.y() + 1); - src_line = src_data + src_bpl * (height - kh) + (width - kw); - dst_line = dst_data + dst_bpl * (height - k_bottom); - for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { - k_origin.setX(k_center.x() + 1); - for (int x = width - k_right; x < width; ++x) { - kernel.recalcForOrigin(k_origin); - kernel.convolve(dst_line + x, src_line, src_bpl); - k_origin.rx() += 1; - } - k_origin.ry() += 1; + k_origin.ry() += 1; + } + + // Bottom area between two corners. + k_origin.setX(k_center.x()); + k_origin.setY(k_center.y() + 1); + src_line = src_data + src_bpl * (height - kh) - k_left; + dst_line = dst_data + dst_bpl * (height - k_bottom); + for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { + kernel.recalcForOrigin(k_origin); + for (int x = k_left; x < width - k_right; ++x) { + kernel.convolve(dst_line + x, src_line + x, src_bpl); } + k_origin.ry() += 1; + } + // Bottom-right corner. + k_origin.setY(k_center.y() + 1); + src_line = src_data + src_bpl * (height - kh) + (width - kw); + dst_line = dst_data + dst_bpl * (height - k_bottom); + for (int y = height - k_bottom; y < height; ++y, dst_line += dst_bpl) { + k_origin.setX(k_center.x() + 1); + for (int x = width - k_right; x < width; ++x) { + kernel.recalcForOrigin(k_origin); + kernel.convolve(dst_line + x, src_line, src_bpl); + k_origin.rx() += 1; + } + k_origin.ry() += 1; + } - return dst; + return dst; } // savGolFilterGrayToGray } // namespace QImage savGolFilter(const QImage& src, const QSize& window_size, const int hor_degree, const int vert_degree) { - if ((hor_degree < 0) || (vert_degree < 0)) { - throw std::invalid_argument("savGolFilter: invalid polynomial degree"); - } - if (window_size.isEmpty()) { - throw std::invalid_argument("savGolFilter: invalid window size"); - } - - if (calcNumTerms(hor_degree, vert_degree) > window_size.width() * window_size.height()) { - throw std::invalid_argument("savGolFilter: order is too big for that window"); - } - - return savGolFilterGrayToGray(toGrayscale(src), window_size, hor_degree, vert_degree); + if ((hor_degree < 0) || (vert_degree < 0)) { + throw std::invalid_argument("savGolFilter: invalid polynomial degree"); + } + if (window_size.isEmpty()) { + throw std::invalid_argument("savGolFilter: invalid window size"); + } + + if (calcNumTerms(hor_degree, vert_degree) > window_size.width() * window_size.height()) { + throw std::invalid_argument("savGolFilter: order is too big for that window"); + } + + return savGolFilterGrayToGray(toGrayscale(src), window_size, hor_degree, vert_degree); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SavGolKernel.cpp b/imageproc/SavGolKernel.cpp index 21fa04bdf..805d02623 100644 --- a/imageproc/SavGolKernel.cpp +++ b/imageproc/SavGolKernel.cpp @@ -19,62 +19,62 @@ #define _ISOC99SOURCE // For std::copysign() #include "SavGolKernel.h" -#include #include -#include +#include #include #include +#include namespace imageproc { namespace { int calcNumTerms(const int hor_degree, const int vert_degree) { - return (hor_degree + 1) * (vert_degree + 1); + return (hor_degree + 1) * (vert_degree + 1); } } // anonymous namespace SavGolKernel::SavGolKernel(const QSize& size, const QPoint& origin, const int hor_degree, const int vert_degree) - : m_horDegree(hor_degree), - m_vertDegree(vert_degree), - m_width(size.width()), - m_height(size.height()), - m_numTerms(calcNumTerms(hor_degree, vert_degree)), - m_numDataPoints(size.width() * size.height()) { - if (size.isEmpty()) { - throw std::invalid_argument("SavGolKernel: invalid size"); - } - if (hor_degree < 0) { - throw std::invalid_argument("SavGolKernel: invalid hor_degree"); - } - if (vert_degree < 0) { - throw std::invalid_argument("SavGolKernel: invalid vert_degree"); - } - if (m_numTerms > m_numDataPoints) { - throw std::invalid_argument("SavGolKernel: too high degree for this amount of data"); - } - - // Allocate memory. - m_dataPoints.resize(m_numDataPoints, 0.0); - m_coeffs.resize(m_numTerms); - AlignedArray(m_numDataPoints).swap(m_kernel); - // Prepare equations. - m_equations.reserve(m_numTerms * m_numDataPoints); - for (int y = 1; y <= m_height; ++y) { - for (int x = 1; x <= m_width; ++x) { - double pow1 = 1.0; - for (int i = 0; i <= m_vertDegree; ++i) { - double pow2 = pow1; - for (int j = 0; j <= m_horDegree; ++j) { - m_equations.push_back(pow2); - pow2 *= x; - } - pow1 *= y; - } + : m_horDegree(hor_degree), + m_vertDegree(vert_degree), + m_width(size.width()), + m_height(size.height()), + m_numTerms(calcNumTerms(hor_degree, vert_degree)), + m_numDataPoints(size.width() * size.height()) { + if (size.isEmpty()) { + throw std::invalid_argument("SavGolKernel: invalid size"); + } + if (hor_degree < 0) { + throw std::invalid_argument("SavGolKernel: invalid hor_degree"); + } + if (vert_degree < 0) { + throw std::invalid_argument("SavGolKernel: invalid vert_degree"); + } + if (m_numTerms > m_numDataPoints) { + throw std::invalid_argument("SavGolKernel: too high degree for this amount of data"); + } + + // Allocate memory. + m_dataPoints.resize(m_numDataPoints, 0.0); + m_coeffs.resize(m_numTerms); + AlignedArray(m_numDataPoints).swap(m_kernel); + // Prepare equations. + m_equations.reserve(m_numTerms * m_numDataPoints); + for (int y = 1; y <= m_height; ++y) { + for (int x = 1; x <= m_width; ++x) { + double pow1 = 1.0; + for (int i = 0; i <= m_vertDegree; ++i) { + double pow2 = pow1; + for (int j = 0; j <= m_horDegree; ++j) { + m_equations.push_back(pow2); + pow2 *= x; } + pow1 *= y; + } } + } - QR(); - recalcForOrigin(origin); + QR(); + recalcForOrigin(origin); } /** @@ -83,100 +83,100 @@ SavGolKernel::SavGolKernel(const QSize& size, const QPoint& origin, const int ho * but we do store the rotations in the order they were performed. */ void SavGolKernel::QR() { - m_rotations.clear(); - m_rotations.reserve(m_numTerms * (m_numTerms - 1) / 2 + (m_numDataPoints - m_numTerms) * m_numTerms); - - int jj = 0; // j * m_numTerms + j - for (int j = 0; j < m_numTerms; ++j, jj += m_numTerms + 1) { - int ij = jj + m_numTerms; // i * m_numTerms + j - for (int i = j + 1; i < m_numDataPoints; ++i, ij += m_numTerms) { - const double a = m_equations[jj]; - const double b = m_equations[ij]; - - if (b == 0.0) { - m_rotations.emplace_back(1.0, 0.0); - continue; - } - - double sin, cos; - - if (a == 0.0) { - cos = 0.0; - sin = std::copysign(1.0, b); - m_equations[jj] = std::fabs(b); - } else if (std::fabs(b) > std::fabs(a)) { - const double t = a / b; - const double u = std::copysign(std::sqrt(1.0 + t * t), b); - sin = 1.0 / u; - cos = sin * t; - m_equations[jj] = b * u; - } else { - const double t = b / a; - const double u = std::copysign(std::sqrt(1.0 + t * t), a); - cos = 1.0 / u; - sin = cos * t; - m_equations[jj] = a * u; - } - m_equations[ij] = 0.0; - - m_rotations.emplace_back(sin, cos); - - int ik = ij + 1; // i * m_numTerms + k - int jk = jj + 1; // j * m_numTerms + k - for (int k = j + 1; k < m_numTerms; ++k, ++ik, ++jk) { - const double temp = cos * m_equations[jk] + sin * m_equations[ik]; - m_equations[ik] = cos * m_equations[ik] - sin * m_equations[jk]; - m_equations[jk] = temp; - } - } + m_rotations.clear(); + m_rotations.reserve(m_numTerms * (m_numTerms - 1) / 2 + (m_numDataPoints - m_numTerms) * m_numTerms); + + int jj = 0; // j * m_numTerms + j + for (int j = 0; j < m_numTerms; ++j, jj += m_numTerms + 1) { + int ij = jj + m_numTerms; // i * m_numTerms + j + for (int i = j + 1; i < m_numDataPoints; ++i, ij += m_numTerms) { + const double a = m_equations[jj]; + const double b = m_equations[ij]; + + if (b == 0.0) { + m_rotations.emplace_back(1.0, 0.0); + continue; + } + + double sin, cos; + + if (a == 0.0) { + cos = 0.0; + sin = std::copysign(1.0, b); + m_equations[jj] = std::fabs(b); + } else if (std::fabs(b) > std::fabs(a)) { + const double t = a / b; + const double u = std::copysign(std::sqrt(1.0 + t * t), b); + sin = 1.0 / u; + cos = sin * t; + m_equations[jj] = b * u; + } else { + const double t = b / a; + const double u = std::copysign(std::sqrt(1.0 + t * t), a); + cos = 1.0 / u; + sin = cos * t; + m_equations[jj] = a * u; + } + m_equations[ij] = 0.0; + + m_rotations.emplace_back(sin, cos); + + int ik = ij + 1; // i * m_numTerms + k + int jk = jj + 1; // j * m_numTerms + k + for (int k = j + 1; k < m_numTerms; ++k, ++ik, ++jk) { + const double temp = cos * m_equations[jk] + sin * m_equations[ik]; + m_equations[ik] = cos * m_equations[ik] - sin * m_equations[jk]; + m_equations[jk] = temp; + } } + } } // SavGolKernel::QR void SavGolKernel::recalcForOrigin(const QPoint& origin) { - std::fill(m_dataPoints.begin(), m_dataPoints.end(), 0.0); - m_dataPoints[origin.y() * m_width + origin.x()] = 1.0; - - // Rotate data points. - double* const dp = &m_dataPoints[0]; - std::vector::const_iterator rot(m_rotations.begin()); - for (int j = 0; j < m_numTerms; ++j) { - for (int i = j + 1; i < m_numDataPoints; ++i, ++rot) { - const double temp = rot->cos * dp[j] + rot->sin * dp[i]; - dp[i] = rot->cos * dp[i] - rot->sin * dp[j]; - dp[j] = temp; - } + std::fill(m_dataPoints.begin(), m_dataPoints.end(), 0.0); + m_dataPoints[origin.y() * m_width + origin.x()] = 1.0; + + // Rotate data points. + double* const dp = &m_dataPoints[0]; + std::vector::const_iterator rot(m_rotations.begin()); + for (int j = 0; j < m_numTerms; ++j) { + for (int i = j + 1; i < m_numDataPoints; ++i, ++rot) { + const double temp = rot->cos * dp[j] + rot->sin * dp[i]; + dp[i] = rot->cos * dp[i] - rot->sin * dp[j]; + dp[j] = temp; } - // Solve R*x = d by back-substitution. - int ii = m_numTerms * m_numTerms - 1; // i * m_numTerms + i - for (int i = m_numTerms - 1; i >= 0; --i, ii -= m_numTerms + 1) { - double sum = dp[i]; - int ik = ii + 1; - for (int k = i + 1; k < m_numTerms; ++k, ++ik) { - sum -= m_equations[ik] * m_coeffs[k]; - } - - assert(m_equations[ii] != 0.0); - m_coeffs[i] = sum / m_equations[ii]; + } + // Solve R*x = d by back-substitution. + int ii = m_numTerms * m_numTerms - 1; // i * m_numTerms + i + for (int i = m_numTerms - 1; i >= 0; --i, ii -= m_numTerms + 1) { + double sum = dp[i]; + int ik = ii + 1; + for (int k = i + 1; k < m_numTerms; ++k, ++ik) { + sum -= m_equations[ik] * m_coeffs[k]; } - int ki = 0; - for (int y = 1; y <= m_height; ++y) { - for (int x = 1; x <= m_width; ++x) { - double sum = 0.0; - double pow1 = 1.0; - int ci = 0; - for (int i = 0; i <= m_vertDegree; ++i) { - double pow2 = pow1; - for (int j = 0; j <= m_horDegree; ++j) { - sum += pow2 * m_coeffs[ci]; - ++ci; - pow2 *= x; - } - pow1 *= y; - } - m_kernel[ki] = (float) sum; - ++ki; + assert(m_equations[ii] != 0.0); + m_coeffs[i] = sum / m_equations[ii]; + } + + int ki = 0; + for (int y = 1; y <= m_height; ++y) { + for (int x = 1; x <= m_width; ++x) { + double sum = 0.0; + double pow1 = 1.0; + int ci = 0; + for (int i = 0; i <= m_vertDegree; ++i) { + double pow2 = pow1; + for (int j = 0; j <= m_horDegree; ++j) { + sum += pow2 * m_coeffs[ci]; + ++ci; + pow2 *= x; } + pow1 *= y; + } + m_kernel[ki] = (float) sum; + ++ki; } + } } // SavGolKernel::recalcForOrigin } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SavGolKernel.h b/imageproc/SavGolKernel.h index 88899386c..078077fcc 100644 --- a/imageproc/SavGolKernel.h +++ b/imageproc/SavGolKernel.h @@ -19,108 +19,99 @@ #ifndef IMAGEPROC_SAVGOL_KERNEL_H_ #define IMAGEPROC_SAVGOL_KERNEL_H_ -#include "AlignedArray.h" -#include #include +#include +#include "AlignedArray.h" class QPoint; class QSize; namespace imageproc { class SavGolKernel { -public: - SavGolKernel(const QSize& size, const QPoint& origin, int hor_degree, int vert_degree); - - void recalcForOrigin(const QPoint& origin); - - int width() const { - return m_width; - } - - int height() const { - return m_height; - } - - const float* data() const { - return m_kernel.data(); - } - - float operator[](size_t idx) const { - return m_kernel[idx]; - } - -private: - struct Rotation { - double sin; - double cos; - - Rotation(double s, double c) : sin(s), cos(c) { - } - }; - - void QR(); - - /** - * A matrix of m_numDataPoints rows and m_numVars columns. - * Stored row by row. - */ - std::vector m_equations; - - /** - * The data points, in the same order as rows in m_equations. - */ - std::vector m_dataPoints; - - /** - * The polynomial coefficients of size m_numVars. Only exists to save - * one allocation when recalculating the kernel for different data points. - */ - std::vector m_coeffs; - - /** - * The rotations applied to m_equations as part of QR factorization. - * Later these same rotations are applied to a copy of m_dataPoints. - * We could avoid storing rotations and rotate m_dataPoints on the fly, - * but in that case we would have to rotate m_equations again when - * recalculating the kernel for different data points. - */ - std::vector m_rotations; - - /** - * 16-byte aligned convolution kernel of size m_numDataPoints. - */ - AlignedArray m_kernel; - - /** - * The degree of the polynomial in horizontal direction. - */ - int m_horDegree; - - /** - * The degree of the polynomial in vertical direction. - */ - int m_vertDegree; - - /** - * The width of the convolution kernel. - */ - int m_width; - - /** - * The height of the convolution kernel. - */ - int m_height; - - /** - * The number of terms in the polynomial. - */ - int m_numTerms; - - /** - * The number of data points. This corresponds to the number of items - * in the convolution kernel. - */ - int m_numDataPoints; + public: + SavGolKernel(const QSize& size, const QPoint& origin, int hor_degree, int vert_degree); + + void recalcForOrigin(const QPoint& origin); + + int width() const { return m_width; } + + int height() const { return m_height; } + + const float* data() const { return m_kernel.data(); } + + float operator[](size_t idx) const { return m_kernel[idx]; } + + private: + struct Rotation { + double sin; + double cos; + + Rotation(double s, double c) : sin(s), cos(c) {} + }; + + void QR(); + + /** + * A matrix of m_numDataPoints rows and m_numVars columns. + * Stored row by row. + */ + std::vector m_equations; + + /** + * The data points, in the same order as rows in m_equations. + */ + std::vector m_dataPoints; + + /** + * The polynomial coefficients of size m_numVars. Only exists to save + * one allocation when recalculating the kernel for different data points. + */ + std::vector m_coeffs; + + /** + * The rotations applied to m_equations as part of QR factorization. + * Later these same rotations are applied to a copy of m_dataPoints. + * We could avoid storing rotations and rotate m_dataPoints on the fly, + * but in that case we would have to rotate m_equations again when + * recalculating the kernel for different data points. + */ + std::vector m_rotations; + + /** + * 16-byte aligned convolution kernel of size m_numDataPoints. + */ + AlignedArray m_kernel; + + /** + * The degree of the polynomial in horizontal direction. + */ + int m_horDegree; + + /** + * The degree of the polynomial in vertical direction. + */ + int m_vertDegree; + + /** + * The width of the convolution kernel. + */ + int m_width; + + /** + * The height of the convolution kernel. + */ + int m_height; + + /** + * The number of terms in the polynomial. + */ + int m_numTerms; + + /** + * The number of data points. This corresponds to the number of items + * in the convolution kernel. + */ + int m_numDataPoints; }; } // namespace imageproc #endif // ifndef IMAGEPROC_SAVGOL_KERNEL_H_ diff --git a/imageproc/Scale.cpp b/imageproc/Scale.cpp index 2d2f2571c..a53cfbaea 100644 --- a/imageproc/Scale.cpp +++ b/imageproc/Scale.cpp @@ -17,8 +17,8 @@ */ #include "Scale.h" -#include "GrayImage.h" #include +#include "GrayImage.h" namespace imageproc { /** @@ -26,48 +26,48 @@ namespace imageproc { * pixel maps exactly to a M x N block of source pixels. */ static GrayImage scaleDownIntGrayToGray(const GrayImage& src, const QSize& dst_size) { - const int sw = src.width(); - const int sh = src.height(); - const int dw = dst_size.width(); - const int dh = dst_size.height(); - - const int xscale = sw / dw; - const int yscale = sh / dh; - const int total_area = xscale * yscale; - - GrayImage dst(dst_size); - - const uint8_t* src_line = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int src_stride_scaled = src_stride * yscale; - const int dst_stride = dst.stride(); - - int sy = 0; - int dy = 0; - for (; dy < dh; ++dy, sy += yscale) { - int sx = 0; - int dx = 0; - for (; dx < dw; ++dx, sx += xscale) { - unsigned gray_level = 0; - const uint8_t* psrc = src_line + sx; - - for (int i = 0; i < yscale; ++i, psrc += src_stride) { - for (int j = 0; j < xscale; ++j) { - gray_level += psrc[j]; - } - } - - const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; - assert(pix_value < 256); - dst_line[dx] = static_cast(pix_value); + const int sw = src.width(); + const int sh = src.height(); + const int dw = dst_size.width(); + const int dh = dst_size.height(); + + const int xscale = sw / dw; + const int yscale = sh / dh; + const int total_area = xscale * yscale; + + GrayImage dst(dst_size); + + const uint8_t* src_line = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int src_stride_scaled = src_stride * yscale; + const int dst_stride = dst.stride(); + + int sy = 0; + int dy = 0; + for (; dy < dh; ++dy, sy += yscale) { + int sx = 0; + int dx = 0; + for (; dx < dw; ++dx, sx += xscale) { + unsigned gray_level = 0; + const uint8_t* psrc = src_line + sx; + + for (int i = 0; i < yscale; ++i, psrc += src_stride) { + for (int j = 0; j < xscale; ++j) { + gray_level += psrc[j]; } + } - src_line += src_stride_scaled; - dst_line += dst_stride; + const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; + assert(pix_value < 256); + dst_line[dx] = static_cast(pix_value); } - return dst; + src_line += src_stride_scaled; + dst_line += dst_stride; + } + + return dst; } // scaleDownIntGrayToGray /** @@ -75,43 +75,43 @@ static GrayImage scaleDownIntGrayToGray(const GrayImage& src, const QSize& dst_s * pixel maps to a single source pixel (possibly to a part of it). */ static GrayImage scaleUpIntGrayToGray(const GrayImage& src, const QSize& dst_size) { - const int sw = src.width(); - const int sh = src.height(); - const int dw = dst_size.width(); - const int dh = dst_size.height(); - - const int xscale = dw / sw; - const int yscale = dh / sh; - - GrayImage dst(dst_size); - - const uint8_t* src_line = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); - const int dst_stride_scaled = dst_stride * yscale; - - int sy = 0; - int dy = 0; - for (; dy < dh; ++sy, dy += xscale) { - int sx = 0; - int dx = 0; - - for (; dx < dw; ++sx, dx += xscale) { - uint8_t* pdst = dst_line + dx; - - for (int i = 0; i < yscale; ++i, pdst += dst_stride) { - for (int j = 0; j < xscale; ++j) { - pdst[j] = src_line[sx]; - } - } + const int sw = src.width(); + const int sh = src.height(); + const int dw = dst_size.width(); + const int dh = dst_size.height(); + + const int xscale = dw / sw; + const int yscale = dh / sh; + + GrayImage dst(dst_size); + + const uint8_t* src_line = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); + const int dst_stride_scaled = dst_stride * yscale; + + int sy = 0; + int dy = 0; + for (; dy < dh; ++sy, dy += xscale) { + int sx = 0; + int dx = 0; + + for (; dx < dw; ++sx, dx += xscale) { + uint8_t* pdst = dst_line + dx; + + for (int i = 0; i < yscale; ++i, pdst += dst_stride) { + for (int j = 0; j < xscale; ++j) { + pdst[j] = src_line[sx]; } - - src_line += src_stride; - dst_line += dst_stride_scaled; + } } - return dst; + src_line += src_stride; + dst_line += dst_stride_scaled; + } + + return dst; } // scaleUpIntGrayToGray /** @@ -122,17 +122,17 @@ static GrayImage scaleUpIntGrayToGray(const GrayImage& src, const QSize& dst_siz * \endcode */ static double calc32xRatio1(const int dst, const int src) { - assert(dst > 0); - assert(src > 0); - - int src32 = src << 5; - double ratio = (double) src32 / dst; - while ((int(ratio * (dst - 1)) >> 5) + 1 >= src) { - --src32; - ratio = (double) src32 / dst; - } + assert(dst > 0); + assert(src > 0); + + int src32 = src << 5; + double ratio = (double) src32 / dst; + while ((int(ratio * (dst - 1)) >> 5) + 1 >= src) { + --src32; + ratio = (double) src32 / dst; + } - return ratio; + return ratio; } /** @@ -141,54 +141,54 @@ static double calc32xRatio1(const int dst, const int src) { * horizontally and vertically. */ static GrayImage scaleUpGrayToGray(const GrayImage& src, const QSize& dst_size) { - const int sw = src.width(); - const int sh = src.height(); - const int dw = dst_size.width(); - const int dh = dst_size.height(); - - const double dx2sx32 = calc32xRatio1(dw, sw); - const double dy2sy32 = calc32xRatio1(dh, sh); - - GrayImage dst(dst_size); - - const uint8_t* const src_data = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); - - for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { - const auto sy32 = (int) (dy * dy2sy32); - const int sy = sy32 >> 5; - const unsigned top_fraction = 32 - (sy32 & 31); - const unsigned bottom_fraction = sy32 & 31; - assert(sy + 1 < sh); // calc32xRatio1() ensures that. - const uint8_t* src_line = src_data + sy * src_stride; - - for (int dx = 0; dx < dw; ++dx) { - const auto sx32 = (int) (dx * dx2sx32); - const int sx = sx32 >> 5; - const unsigned left_fraction = 32 - (sx32 & 31); - const unsigned right_fraction = sx32 & 31; - assert(sx + 1 < sw); // calc32xRatio1() ensures that. - unsigned gray_level = 0; - - const uint8_t* psrc = src_line + sx; - gray_level += *psrc * left_fraction * top_fraction; - ++psrc; - gray_level += *psrc * right_fraction * top_fraction; - psrc += src_stride; - gray_level += *psrc * right_fraction * bottom_fraction; - --psrc; - gray_level += *psrc * left_fraction * bottom_fraction; - - const unsigned total_area = 32 * 32; - const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; - assert(pix_value < 256); - dst_line[dx] = static_cast(pix_value); - } + const int sw = src.width(); + const int sh = src.height(); + const int dw = dst_size.width(); + const int dh = dst_size.height(); + + const double dx2sx32 = calc32xRatio1(dw, sw); + const double dy2sy32 = calc32xRatio1(dh, sh); + + GrayImage dst(dst_size); + + const uint8_t* const src_data = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); + + for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { + const auto sy32 = (int) (dy * dy2sy32); + const int sy = sy32 >> 5; + const unsigned top_fraction = 32 - (sy32 & 31); + const unsigned bottom_fraction = sy32 & 31; + assert(sy + 1 < sh); // calc32xRatio1() ensures that. + const uint8_t* src_line = src_data + sy * src_stride; + + for (int dx = 0; dx < dw; ++dx) { + const auto sx32 = (int) (dx * dx2sx32); + const int sx = sx32 >> 5; + const unsigned left_fraction = 32 - (sx32 & 31); + const unsigned right_fraction = sx32 & 31; + assert(sx + 1 < sw); // calc32xRatio1() ensures that. + unsigned gray_level = 0; + + const uint8_t* psrc = src_line + sx; + gray_level += *psrc * left_fraction * top_fraction; + ++psrc; + gray_level += *psrc * right_fraction * top_fraction; + psrc += src_stride; + gray_level += *psrc * right_fraction * bottom_fraction; + --psrc; + gray_level += *psrc * left_fraction * bottom_fraction; + + const unsigned total_area = 32 * 32; + const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; + assert(pix_value < 256); + dst_line[dx] = static_cast(pix_value); } + } - return dst; + return dst; } // scaleUpGrayToGray /** @@ -199,180 +199,180 @@ static GrayImage scaleUpGrayToGray(const GrayImage& src, const QSize& dst_size) * \endcode */ static double calc32xRatio2(const int dst, const int src) { - assert(dst > 0); - assert(src > 0); - - int src32 = src << 5; - double ratio = (double) src32 / dst; - while ((int(ratio * dst) - 1) >> 5 >= src) { - --src32; - ratio = (double) src32 / dst; - } + assert(dst > 0); + assert(src > 0); - return ratio; + int src32 = src << 5; + double ratio = (double) src32 / dst; + while ((int(ratio * dst) - 1) >> 5 >= src) { + --src32; + ratio = (double) src32 / dst; + } + + return ratio; } /** * This is a generic implementation of the scaling algorithm. */ static GrayImage scaleGrayToGray(const GrayImage& src, const QSize& dst_size) { - const int sw = src.width(); - const int sh = src.height(); - const int dw = dst_size.width(); - const int dh = dst_size.height(); - - // Try versions optimized for a particular case. - if ((sw == dw) && (sh == dh)) { - return src; - } else if ((sw % dw == 0) && (sh % dh == 0)) { - return scaleDownIntGrayToGray(src, dst_size); - } else if ((dw % sw == 0) && (dh % sh == 0)) { - return scaleUpIntGrayToGray(src, dst_size); - } else if ((dw > sw) && (dh > sh)) { - return scaleUpGrayToGray(src, dst_size); - } + const int sw = src.width(); + const int sh = src.height(); + const int dw = dst_size.width(); + const int dh = dst_size.height(); + + // Try versions optimized for a particular case. + if ((sw == dw) && (sh == dh)) { + return src; + } else if ((sw % dw == 0) && (sh % dh == 0)) { + return scaleDownIntGrayToGray(src, dst_size); + } else if ((dw % sw == 0) && (dh % sh == 0)) { + return scaleUpIntGrayToGray(src, dst_size); + } else if ((dw > sw) && (dh > sh)) { + return scaleUpGrayToGray(src, dst_size); + } + + const double dx2sx32 = calc32xRatio2(dw, sw); + const double dy2sy32 = calc32xRatio2(dh, sh); + + GrayImage dst(dst_size); + + const uint8_t* const src_data = src.data(); + uint8_t* dst_line = dst.data(); + const int src_stride = src.stride(); + const int dst_stride = dst.stride(); + + int sy32bottom = 0; + for (int dy1 = 1; dy1 <= dh; ++dy1, dst_line += dst_stride) { + const int sy32top = sy32bottom; + sy32bottom = (int) (dy1 * dy2sy32); + const int sytop = sy32top >> 5; + const int sybottom = (sy32bottom - 1) >> 5; + const unsigned top_fraction = 32 - (sy32top & 31); + const unsigned bottom_fraction = sy32bottom - (sybottom << 5); + assert(sybottom < sh); // calc32xRatio2() ensures that. + const unsigned top_area = top_fraction << 5; + const unsigned bottom_area = bottom_fraction << 5; + + const uint8_t* const src_line_const = src_data + sytop * src_stride; + + int sx32right = 0; + for (int dx = 0; dx < dw; ++dx) { + const int sx32left = sx32right; + sx32right = (int) ((dx + 1) * dx2sx32); + const int sxleft = sx32left >> 5; + const int sxright = (sx32right - 1) >> 5; + const unsigned left_fraction = 32 - (sx32left & 31); + const unsigned right_fraction = sx32right - (sxright << 5); + assert(sxright < sw); // calc32xRatio2() ensures that. + const uint8_t* src_line = src_line_const; + unsigned gray_level = 0; + + if (sytop == sybottom) { + if (sxleft == sxright) { + // dst pixel maps to a single src pixel + dst_line[dx] = src_line[sxleft]; + continue; + } else { + // dst pixel maps to a horizontal line of src pixels + const unsigned vert_fraction = sy32bottom - sy32top; + const unsigned left_area = vert_fraction * left_fraction; + const unsigned middle_area = vert_fraction << 5; + const unsigned right_area = vert_fraction * right_fraction; + + gray_level += src_line[sxleft] * left_area; + + for (int sx = sxleft + 1; sx < sxright; ++sx) { + gray_level += src_line[sx] * middle_area; + } + + gray_level += src_line[sxright] * right_area; + } + } else if (sxleft == sxright) { + // dst pixel maps to a vertical line of src pixels + const unsigned hor_fraction = sx32right - sx32left; + const unsigned top_area = hor_fraction * top_fraction; + const unsigned middle_area = hor_fraction << 5; + const unsigned bottom_area = hor_fraction * bottom_fraction; + + gray_level += src_line[sxleft] * top_area; + + src_line += src_stride; - const double dx2sx32 = calc32xRatio2(dw, sw); - const double dy2sy32 = calc32xRatio2(dh, sh); - - GrayImage dst(dst_size); - - const uint8_t* const src_data = src.data(); - uint8_t* dst_line = dst.data(); - const int src_stride = src.stride(); - const int dst_stride = dst.stride(); - - int sy32bottom = 0; - for (int dy1 = 1; dy1 <= dh; ++dy1, dst_line += dst_stride) { - const int sy32top = sy32bottom; - sy32bottom = (int) (dy1 * dy2sy32); - const int sytop = sy32top >> 5; - const int sybottom = (sy32bottom - 1) >> 5; - const unsigned top_fraction = 32 - (sy32top & 31); - const unsigned bottom_fraction = sy32bottom - (sybottom << 5); - assert(sybottom < sh); // calc32xRatio2() ensures that. - const unsigned top_area = top_fraction << 5; - const unsigned bottom_area = bottom_fraction << 5; - - const uint8_t* const src_line_const = src_data + sytop * src_stride; - - int sx32right = 0; - for (int dx = 0; dx < dw; ++dx) { - const int sx32left = sx32right; - sx32right = (int) ((dx + 1) * dx2sx32); - const int sxleft = sx32left >> 5; - const int sxright = (sx32right - 1) >> 5; - const unsigned left_fraction = 32 - (sx32left & 31); - const unsigned right_fraction = sx32right - (sxright << 5); - assert(sxright < sw); // calc32xRatio2() ensures that. - const uint8_t* src_line = src_line_const; - unsigned gray_level = 0; - - if (sytop == sybottom) { - if (sxleft == sxright) { - // dst pixel maps to a single src pixel - dst_line[dx] = src_line[sxleft]; - continue; - } else { - // dst pixel maps to a horizontal line of src pixels - const unsigned vert_fraction = sy32bottom - sy32top; - const unsigned left_area = vert_fraction * left_fraction; - const unsigned middle_area = vert_fraction << 5; - const unsigned right_area = vert_fraction * right_fraction; - - gray_level += src_line[sxleft] * left_area; - - for (int sx = sxleft + 1; sx < sxright; ++sx) { - gray_level += src_line[sx] * middle_area; - } - - gray_level += src_line[sxright] * right_area; - } - } else if (sxleft == sxright) { - // dst pixel maps to a vertical line of src pixels - const unsigned hor_fraction = sx32right - sx32left; - const unsigned top_area = hor_fraction * top_fraction; - const unsigned middle_area = hor_fraction << 5; - const unsigned bottom_area = hor_fraction * bottom_fraction; - - gray_level += src_line[sxleft] * top_area; - - src_line += src_stride; - - for (int sy = sytop + 1; sy < sybottom; ++sy) { - gray_level += src_line[sxleft] * middle_area; - src_line += src_stride; - } - - gray_level += src_line[sxleft] * bottom_area; - } else { - // dst pixel maps to a block of src pixels - const unsigned left_area = left_fraction << 5; - const unsigned right_area = right_fraction << 5; - const unsigned topleft_area = top_fraction * left_fraction; - const unsigned topright_area = top_fraction * right_fraction; - const unsigned bottomleft_area = bottom_fraction * left_fraction; - const unsigned bottomright_area = bottom_fraction * right_fraction; - - // process the top-left corner - gray_level += src_line[sxleft] * topleft_area; - - // process the top line (without corners) - for (int sx = sxleft + 1; sx < sxright; ++sx) { - gray_level += src_line[sx] * top_area; - } - - // process the top-right corner - gray_level += src_line[sxright] * topright_area; - - src_line += src_stride; - // process middle lines - for (int sy = sytop + 1; sy < sybottom; ++sy) { - gray_level += src_line[sxleft] * left_area; - - for (int sx = sxleft + 1; sx < sxright; ++sx) { - gray_level += src_line[sx] << (5 + 5); - } - - gray_level += src_line[sxright] * right_area; - - src_line += src_stride; - } - - // process bottom-left corner - gray_level += src_line[sxleft] * bottomleft_area; - - // process the bottom line (without corners) - for (int sx = sxleft + 1; sx < sxright; ++sx) { - gray_level += src_line[sx] * bottom_area; - } - // process the bottom-right corner - gray_level += src_line[sxright] * bottomright_area; - } - - const unsigned total_area = (sy32bottom - sy32top) * (sx32right - sx32left); - const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; - assert(pix_value < 256); - dst_line[dx] = static_cast(pix_value); + for (int sy = sytop + 1; sy < sybottom; ++sy) { + gray_level += src_line[sxleft] * middle_area; + src_line += src_stride; } + + gray_level += src_line[sxleft] * bottom_area; + } else { + // dst pixel maps to a block of src pixels + const unsigned left_area = left_fraction << 5; + const unsigned right_area = right_fraction << 5; + const unsigned topleft_area = top_fraction * left_fraction; + const unsigned topright_area = top_fraction * right_fraction; + const unsigned bottomleft_area = bottom_fraction * left_fraction; + const unsigned bottomright_area = bottom_fraction * right_fraction; + + // process the top-left corner + gray_level += src_line[sxleft] * topleft_area; + + // process the top line (without corners) + for (int sx = sxleft + 1; sx < sxright; ++sx) { + gray_level += src_line[sx] * top_area; + } + + // process the top-right corner + gray_level += src_line[sxright] * topright_area; + + src_line += src_stride; + // process middle lines + for (int sy = sytop + 1; sy < sybottom; ++sy) { + gray_level += src_line[sxleft] * left_area; + + for (int sx = sxleft + 1; sx < sxright; ++sx) { + gray_level += src_line[sx] << (5 + 5); + } + + gray_level += src_line[sxright] * right_area; + + src_line += src_stride; + } + + // process bottom-left corner + gray_level += src_line[sxleft] * bottomleft_area; + + // process the bottom line (without corners) + for (int sx = sxleft + 1; sx < sxright; ++sx) { + gray_level += src_line[sx] * bottom_area; + } + // process the bottom-right corner + gray_level += src_line[sxright] * bottomright_area; + } + + const unsigned total_area = (sy32bottom - sy32top) * (sx32right - sx32left); + const unsigned pix_value = (gray_level + (total_area >> 1)) / total_area; + assert(pix_value < 256); + dst_line[dx] = static_cast(pix_value); } + } - return dst; + return dst; } // scaleGrayToGray GrayImage scaleToGray(const GrayImage& src, const QSize& dst_size) { - if (src.isNull()) { - return src; - } + if (src.isNull()) { + return src; + } - if (!dst_size.isValid()) { - throw std::invalid_argument("scaleToGray: dst_size is invalid"); - } + if (!dst_size.isValid()) { + throw std::invalid_argument("scaleToGray: dst_size is invalid"); + } - if (dst_size.isEmpty()) { - return GrayImage(); - } + if (dst_size.isEmpty()) { + return GrayImage(); + } - return scaleGrayToGray(src, dst_size); + return scaleGrayToGray(src, dst_size); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SeedFill.cpp b/imageproc/SeedFill.cpp index 39ac31d99..2b33da1b2 100644 --- a/imageproc/SeedFill.cpp +++ b/imageproc/SeedFill.cpp @@ -17,229 +17,229 @@ */ #include "SeedFill.h" -#include "SeedFillGeneric.h" -#include "GrayImage.h" #include +#include "GrayImage.h" +#include "SeedFillGeneric.h" namespace imageproc { namespace { inline uint32_t fillWordHorizontally(uint32_t word, const uint32_t mask) { - uint32_t prev_word; + uint32_t prev_word; - do { - prev_word = word; - word |= (word << 1) | (word >> 1); - word &= mask; - } while (word != prev_word); + do { + prev_word = word; + word |= (word << 1) | (word >> 1); + word &= mask; + } while (word != prev_word); - return word; + return word; } void seedFill4Iteration(BinaryImage& seed, const BinaryImage& mask) { - const int w = seed.width(); - const int h = seed.height(); - - const int seed_wpl = seed.wordsPerLine(); - const int mask_wpl = mask.wordsPerLine(); - const int last_word_idx = (w - 1) >> 5; - const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); - - uint32_t* seed_line = seed.data(); - const uint32_t* mask_line = mask.data(); - const uint32_t* prev_line = seed_line; - - // Top to bottom. - for (int y = 0; y < h; ++y) { - uint32_t prev_word = 0; - - // Make sure offscreen bits are 0. - seed_line[last_word_idx] &= last_word_mask; - // Left to right (except the last word). - for (int i = 0; i <= last_word_idx; ++i) { - const uint32_t mask = mask_line[i]; - uint32_t word = prev_word << 31; - word |= seed_line[i] | prev_line[i]; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - prev_word = word; - } - - // If we don't do this, prev_line[last_word_idx] on the next - // iteration may contain garbage in the off-screen area. - // That garbage can easily leak back. - seed_line[last_word_idx] &= last_word_mask; - - prev_line = seed_line; - seed_line += seed_wpl; - mask_line += mask_wpl; + const int w = seed.width(); + const int h = seed.height(); + + const int seed_wpl = seed.wordsPerLine(); + const int mask_wpl = mask.wordsPerLine(); + const int last_word_idx = (w - 1) >> 5; + const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); + + uint32_t* seed_line = seed.data(); + const uint32_t* mask_line = mask.data(); + const uint32_t* prev_line = seed_line; + + // Top to bottom. + for (int y = 0; y < h; ++y) { + uint32_t prev_word = 0; + + // Make sure offscreen bits are 0. + seed_line[last_word_idx] &= last_word_mask; + // Left to right (except the last word). + for (int i = 0; i <= last_word_idx; ++i) { + const uint32_t mask = mask_line[i]; + uint32_t word = prev_word << 31; + word |= seed_line[i] | prev_line[i]; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; + prev_word = word; } - seed_line -= seed_wpl; - mask_line -= mask_wpl; - prev_line = seed_line; + // If we don't do this, prev_line[last_word_idx] on the next + // iteration may contain garbage in the off-screen area. + // That garbage can easily leak back. + seed_line[last_word_idx] &= last_word_mask; - // Bottom to top. - for (int y = h - 1; y >= 0; --y) { - uint32_t prev_word = 0; - - // Make sure offscreen bits area 0. - seed_line[last_word_idx] &= last_word_mask; - // Right to left. - for (int i = last_word_idx; i >= 0; --i) { - const uint32_t mask = mask_line[i]; - uint32_t word = prev_word >> 31; - word |= seed_line[i] | prev_line[i]; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - prev_word = word; - } - // If we don't do this, prev_line[last_word_idx] on the next - // iteration may contain garbage in the off-screen area. - // That garbage can easily leak back. - // Fortunately, garbage can't spread through prev_word, - // as only 1 bit is used from it, which can't be garbage. - seed_line[last_word_idx] &= last_word_mask; - - prev_line = seed_line; - seed_line -= seed_wpl; - mask_line -= mask_wpl; + prev_line = seed_line; + seed_line += seed_wpl; + mask_line += mask_wpl; + } + + seed_line -= seed_wpl; + mask_line -= mask_wpl; + prev_line = seed_line; + + // Bottom to top. + for (int y = h - 1; y >= 0; --y) { + uint32_t prev_word = 0; + + // Make sure offscreen bits area 0. + seed_line[last_word_idx] &= last_word_mask; + // Right to left. + for (int i = last_word_idx; i >= 0; --i) { + const uint32_t mask = mask_line[i]; + uint32_t word = prev_word >> 31; + word |= seed_line[i] | prev_line[i]; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; + prev_word = word; } + // If we don't do this, prev_line[last_word_idx] on the next + // iteration may contain garbage in the off-screen area. + // That garbage can easily leak back. + // Fortunately, garbage can't spread through prev_word, + // as only 1 bit is used from it, which can't be garbage. + seed_line[last_word_idx] &= last_word_mask; + + prev_line = seed_line; + seed_line -= seed_wpl; + mask_line -= mask_wpl; + } } // seedFill4Iteration void seedFill8Iteration(BinaryImage& seed, const BinaryImage& mask) { - const int w = seed.width(); - const int h = seed.height(); - - const int seed_wpl = seed.wordsPerLine(); - const int mask_wpl = mask.wordsPerLine(); - const int last_word_idx = (w - 1) >> 5; - const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); - - uint32_t* seed_line = seed.data(); - const uint32_t* mask_line = mask.data(); - const uint32_t* prev_line = seed_line; - - // Note: we start with prev_line == seed_line, but in this case - // prev_line[i + 1] won't be clipped by its mask when we use it to - // update seed_line[i]. The wrong value may propagate further from - // there, so clipping we do on the anti-raster pass won't help. - // That's why we clip the first line here. - for (int i = 0; i <= last_word_idx; ++i) { - seed_line[i] &= mask_line[i]; + const int w = seed.width(); + const int h = seed.height(); + + const int seed_wpl = seed.wordsPerLine(); + const int mask_wpl = mask.wordsPerLine(); + const int last_word_idx = (w - 1) >> 5; + const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); + + uint32_t* seed_line = seed.data(); + const uint32_t* mask_line = mask.data(); + const uint32_t* prev_line = seed_line; + + // Note: we start with prev_line == seed_line, but in this case + // prev_line[i + 1] won't be clipped by its mask when we use it to + // update seed_line[i]. The wrong value may propagate further from + // there, so clipping we do on the anti-raster pass won't help. + // That's why we clip the first line here. + for (int i = 0; i <= last_word_idx; ++i) { + seed_line[i] &= mask_line[i]; + } + + // Top to bottom. + for (int y = 0; y < h; ++y) { + uint32_t prev_word = 0; + + // Make sure offscreen bits area 0. + seed_line[last_word_idx] &= last_word_mask; + // Left to right (except the last word). + int i = 0; + for (; i < last_word_idx; ++i) { + const uint32_t mask = mask_line[i]; + uint32_t word = prev_line[i]; + word |= (word << 1) | (word >> 1); + word |= seed_line[i]; + word |= prev_line[i + 1] >> 31; + word |= prev_word << 31; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; + prev_word = word; } + // Last word. + const uint32_t mask = mask_line[i] & last_word_mask; + uint32_t word = prev_line[i]; + word |= (word << 1) | (word >> 1); + word |= seed_line[i]; + word |= prev_word << 31; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; - // Top to bottom. - for (int y = 0; y < h; ++y) { - uint32_t prev_word = 0; - - // Make sure offscreen bits area 0. - seed_line[last_word_idx] &= last_word_mask; - // Left to right (except the last word). - int i = 0; - for (; i < last_word_idx; ++i) { - const uint32_t mask = mask_line[i]; - uint32_t word = prev_line[i]; - word |= (word << 1) | (word >> 1); - word |= seed_line[i]; - word |= prev_line[i + 1] >> 31; - word |= prev_word << 31; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - prev_word = word; - } - // Last word. - const uint32_t mask = mask_line[i] & last_word_mask; - uint32_t word = prev_line[i]; - word |= (word << 1) | (word >> 1); - word |= seed_line[i]; - word |= prev_word << 31; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - - prev_line = seed_line; - seed_line += seed_wpl; - mask_line += mask_wpl; + prev_line = seed_line; + seed_line += seed_wpl; + mask_line += mask_wpl; + } + + seed_line -= seed_wpl; + mask_line -= mask_wpl; + prev_line = seed_line; + + // Bottom to top. + for (int y = h - 1; y >= 0; --y) { + uint32_t prev_word = 0; + + // Make sure offscreen bits area 0. + seed_line[last_word_idx] &= last_word_mask; + // Right to left (except the last word). + int i = last_word_idx; + for (; i > 0; --i) { + const uint32_t mask = mask_line[i]; + uint32_t word = prev_line[i]; + word |= (word << 1) | (word >> 1); + word |= seed_line[i]; + word |= prev_line[i - 1] << 31; + word |= prev_word >> 31; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; + prev_word = word; } + // Last word. + const uint32_t mask = mask_line[i]; + uint32_t word = prev_line[i]; + word |= (word << 1) | (word >> 1); + word |= seed_line[i]; + word |= prev_word >> 31; + word &= mask; + word = fillWordHorizontally(word, mask); + seed_line[i] = word; + // If we don't do this, prev_line[last_word_idx] on the next + // iteration may contain garbage in the off-screen area. + // That garbage can easily leak back. + // Fortunately, garbage can't spread through prev_word, + // as only 1 bit is used from it, which can't be garbage. + seed_line[last_word_idx] &= last_word_mask; + + prev_line = seed_line; seed_line -= seed_wpl; mask_line -= mask_wpl; - prev_line = seed_line; - - // Bottom to top. - for (int y = h - 1; y >= 0; --y) { - uint32_t prev_word = 0; - - // Make sure offscreen bits area 0. - seed_line[last_word_idx] &= last_word_mask; - // Right to left (except the last word). - int i = last_word_idx; - for (; i > 0; --i) { - const uint32_t mask = mask_line[i]; - uint32_t word = prev_line[i]; - word |= (word << 1) | (word >> 1); - word |= seed_line[i]; - word |= prev_line[i - 1] << 31; - word |= prev_word >> 31; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - prev_word = word; - } - - // Last word. - const uint32_t mask = mask_line[i]; - uint32_t word = prev_line[i]; - word |= (word << 1) | (word >> 1); - word |= seed_line[i]; - word |= prev_word >> 31; - word &= mask; - word = fillWordHorizontally(word, mask); - seed_line[i] = word; - // If we don't do this, prev_line[last_word_idx] on the next - // iteration may contain garbage in the off-screen area. - // That garbage can easily leak back. - // Fortunately, garbage can't spread through prev_word, - // as only 1 bit is used from it, which can't be garbage. - seed_line[last_word_idx] &= last_word_mask; - - prev_line = seed_line; - seed_line -= seed_wpl; - mask_line -= mask_wpl; - } + } } // seedFill8Iteration inline uint8_t lightest(uint8_t lhs, uint8_t rhs) { - return lhs > rhs ? lhs : rhs; + return lhs > rhs ? lhs : rhs; } inline uint8_t darkest(uint8_t lhs, uint8_t rhs) { - return lhs < rhs ? lhs : rhs; + return lhs < rhs ? lhs : rhs; } inline bool darker_than(uint8_t lhs, uint8_t rhs) { - return lhs < rhs; + return lhs < rhs; } void seedFillGrayHorLine(uint8_t* seed, const uint8_t* mask, const int line_len) { - assert(line_len > 0); + assert(line_len > 0); - *seed = lightest(*seed, *mask); + *seed = lightest(*seed, *mask); - for (int i = 1; i < line_len; ++i) { - ++seed; - ++mask; - *seed = lightest(*mask, darkest(*seed, seed[-1])); - } + for (int i = 1; i < line_len; ++i) { + ++seed; + ++mask; + *seed = lightest(*mask, darkest(*seed, seed[-1])); + } - for (int i = 1; i < line_len; ++i) { - --seed; - --mask; - *seed = lightest(*mask, darkest(*seed, seed[1])); - } + for (int i = 1; i < line_len; ++i) { + --seed; + --mask; + *seed = lightest(*mask, darkest(*seed, seed[1])); + } } void seedFillGrayVertLine(uint8_t* seed, @@ -247,226 +247,228 @@ void seedFillGrayVertLine(uint8_t* seed, const uint8_t* mask, const int mask_stride, const int line_len) { - assert(line_len > 0); + assert(line_len > 0); - *seed = lightest(*seed, *mask); + *seed = lightest(*seed, *mask); - for (int i = 1; i < line_len; ++i) { - seed += seed_stride; - mask += mask_stride; - *seed = lightest(*mask, darkest(*seed, seed[-seed_stride])); - } + for (int i = 1; i < line_len; ++i) { + seed += seed_stride; + mask += mask_stride; + *seed = lightest(*mask, darkest(*seed, seed[-seed_stride])); + } - for (int i = 1; i < line_len; ++i) { - seed -= seed_stride; - mask -= mask_stride; - *seed = lightest(*mask, darkest(*seed, seed[seed_stride])); - } + for (int i = 1; i < line_len; ++i) { + seed -= seed_stride; + mask -= mask_stride; + *seed = lightest(*mask, darkest(*seed, seed[seed_stride])); + } } /** * \return non-zero if more iterations are required, zero otherwise. */ uint8_t seedFillGray4SlowIteration(GrayImage& seed, const GrayImage& mask) { - const int w = seed.width(); - const int h = seed.height(); - - uint8_t* seed_line = seed.data(); - const uint8_t* mask_line = mask.data(); - const uint8_t* prev_line = seed_line; - - const int seed_stride = seed.stride(); - const int mask_stride = mask.stride(); - - uint8_t modified = 0; - - // Top to bottom. - for (int y = 0; y < h; ++y) { - uint8_t prev_pixel = 0xff; - // Left to right. - for (int x = 0; x < w; ++x) { - const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - prev_pixel = pixel; - } - - prev_line = seed_line; - seed_line += seed_stride; - mask_line += mask_stride; + const int w = seed.width(); + const int h = seed.height(); + + uint8_t* seed_line = seed.data(); + const uint8_t* mask_line = mask.data(); + const uint8_t* prev_line = seed_line; + + const int seed_stride = seed.stride(); + const int mask_stride = mask.stride(); + + uint8_t modified = 0; + + // Top to bottom. + for (int y = 0; y < h; ++y) { + uint8_t prev_pixel = 0xff; + // Left to right. + for (int x = 0; x < w; ++x) { + const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; + prev_pixel = pixel; } - seed_line -= seed_stride; - mask_line -= mask_stride; prev_line = seed_line; - - // Bottom to top. - for (int y = h - 1; y >= 0; --y) { - uint8_t prev_pixel = 0xff; - // Right to left. - for (int x = w - 1; x >= 0; --x) { - const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - prev_pixel = pixel; - } - - prev_line = seed_line; - seed_line -= seed_stride; - mask_line -= mask_stride; + seed_line += seed_stride; + mask_line += mask_stride; + } + + seed_line -= seed_stride; + mask_line -= mask_stride; + prev_line = seed_line; + + // Bottom to top. + for (int y = h - 1; y >= 0; --y) { + uint8_t prev_pixel = 0xff; + // Right to left. + for (int x = w - 1; x >= 0; --x) { + const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; + prev_pixel = pixel; } - return modified; + prev_line = seed_line; + seed_line -= seed_stride; + mask_line -= mask_stride; + } + + return modified; } // seedFillGray4SlowIteration /** * \return non-zero if more iterations are required, zero otherwise. */ uint8_t seedFillGray8SlowIteration(GrayImage& seed, const GrayImage& mask) { - const int w = seed.width(); - const int h = seed.height(); - - uint8_t* seed_line = seed.data(); - const uint8_t* mask_line = mask.data(); - const uint8_t* prev_line = seed_line; - - const int seed_stride = seed.stride(); - const int mask_stride = mask.stride(); - - uint8_t modified = 0; - - // Some code below doesn't handle such cases. - if (w == 1) { - seedFillGrayVertLine(seed_line, seed_stride, mask_line, mask_stride, h); - - return 0; - } else if (h == 1) { - seedFillGrayHorLine(seed_line, mask_line, w); - - return 0; + const int w = seed.width(); + const int h = seed.height(); + + uint8_t* seed_line = seed.data(); + const uint8_t* mask_line = mask.data(); + const uint8_t* prev_line = seed_line; + + const int seed_stride = seed.stride(); + const int mask_stride = mask.stride(); + + uint8_t modified = 0; + + // Some code below doesn't handle such cases. + if (w == 1) { + seedFillGrayVertLine(seed_line, seed_stride, mask_line, mask_stride, h); + + return 0; + } else if (h == 1) { + seedFillGrayHorLine(seed_line, mask_line, w); + + return 0; + } + + // The prev_line[x + 1] below actually refers to seed_line[x + 1] + // for the first line in raster order. When working with seed_line[x], + // seed_line[x + 1] would not yet be clipped by its mask. So, we + // have to do it now. + for (int x = 0; x < w; ++x) { + seed_line[x] = lightest(seed_line[x], mask_line[x]); + } + + // Top to bottom. + for (int y = 0; y < h; ++y) { + int x = 0; + // Leftmost pixel. + uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x + 1]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; + // Left to right. + while (++x < w - 1) { + pixel + = lightest(mask_line[x], + darkest(darkest(darkest(seed_line[x], seed_line[x - 1]), darkest(prev_line[x], prev_line[x - 1])), + prev_line[x + 1])); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; } + // Rightmost pixel. + pixel = lightest(mask_line[x], + darkest(darkest(seed_line[x], seed_line[x - 1]), darkest(prev_line[x], prev_line[x - 1]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; - // The prev_line[x + 1] below actually refers to seed_line[x + 1] - // for the first line in raster order. When working with seed_line[x], - // seed_line[x + 1] would not yet be clipped by its mask. So, we - // have to do it now. - for (int x = 0; x < w; ++x) { - seed_line[x] = lightest(seed_line[x], mask_line[x]); - } - - // Top to bottom. - for (int y = 0; y < h; ++y) { - int x = 0; - // Leftmost pixel. - uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x + 1]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - // Left to right. - while (++x < w - 1) { - pixel = lightest(mask_line[x], darkest(darkest(darkest(seed_line[x], seed_line[x - 1]), - darkest(prev_line[x], prev_line[x - 1])), - prev_line[x + 1])); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - } - // Rightmost pixel. - pixel = lightest(mask_line[x], - darkest(darkest(seed_line[x], seed_line[x - 1]), darkest(prev_line[x], prev_line[x - 1]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - - prev_line = seed_line; - seed_line += seed_stride; - mask_line += mask_stride; + prev_line = seed_line; + seed_line += seed_stride; + mask_line += mask_stride; + } + + seed_line -= seed_stride; + mask_line -= mask_stride; + prev_line = seed_line; + + // Bottom to top. + for (int y = h - 1; y >= 0; --y) { + int x = w - 1; + // Rightmost pixel. + uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x - 1]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; + // Right to left. + while (--x > 0) { + pixel + = lightest(mask_line[x], + darkest(darkest(darkest(seed_line[x], seed_line[x + 1]), darkest(prev_line[x], prev_line[x + 1])), + prev_line[x - 1])); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; } + // Leftmost pixel. + pixel = lightest(mask_line[x], + darkest(darkest(seed_line[x], seed_line[x + 1]), darkest(prev_line[x], prev_line[x + 1]))); + modified |= seed_line[x] ^ pixel; + seed_line[x] = pixel; + prev_line = seed_line; seed_line -= seed_stride; mask_line -= mask_stride; - prev_line = seed_line; + } - // Bottom to top. - for (int y = h - 1; y >= 0; --y) { - int x = w - 1; - // Rightmost pixel. - uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x - 1]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - // Right to left. - while (--x > 0) { - pixel = lightest(mask_line[x], darkest(darkest(darkest(seed_line[x], seed_line[x + 1]), - darkest(prev_line[x], prev_line[x + 1])), - prev_line[x - 1])); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - } - // Leftmost pixel. - pixel = lightest(mask_line[x], - darkest(darkest(seed_line[x], seed_line[x + 1]), darkest(prev_line[x], prev_line[x + 1]))); - modified |= seed_line[x] ^ pixel; - seed_line[x] = pixel; - - prev_line = seed_line; - seed_line -= seed_stride; - mask_line -= mask_stride; - } - - return modified; + return modified; } // seedFillGray8SlowIteration } // namespace BinaryImage seedFill(const BinaryImage& seed, const BinaryImage& mask, const Connectivity connectivity) { - if (seed.size() != mask.size()) { - throw std::invalid_argument("seedFill: seed and mask have different sizes"); - } + if (seed.size() != mask.size()) { + throw std::invalid_argument("seedFill: seed and mask have different sizes"); + } - BinaryImage prev; - BinaryImage img(seed); + BinaryImage prev; + BinaryImage img(seed); - do { - prev = img; - if (connectivity == CONN4) { - seedFill4Iteration(img, mask); - } else { - seedFill8Iteration(img, mask); - } - } while (img != prev); + do { + prev = img; + if (connectivity == CONN4) { + seedFill4Iteration(img, mask); + } else { + seedFill8Iteration(img, mask); + } + } while (img != prev); - return img; + return img; } GrayImage seedFillGray(const GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) { - GrayImage result(seed); - seedFillGrayInPlace(result, mask, connectivity); + GrayImage result(seed); + seedFillGrayInPlace(result, mask, connectivity); - return result; + return result; } void seedFillGrayInPlace(GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) { - if (seed.size() != mask.size()) { - throw std::invalid_argument("seedFillGrayInPlace: seed and mask have different sizes"); - } + if (seed.size() != mask.size()) { + throw std::invalid_argument("seedFillGrayInPlace: seed and mask have different sizes"); + } - if (seed.isNull()) { - return; - } + if (seed.isNull()) { + return; + } - seedFillGenericInPlace(&darkest, &lightest, connectivity, seed.data(), seed.stride(), seed.size(), mask.data(), - mask.stride()); + seedFillGenericInPlace(&darkest, &lightest, connectivity, seed.data(), seed.stride(), seed.size(), mask.data(), + mask.stride()); } GrayImage seedFillGraySlow(const GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) { - GrayImage img(seed); + GrayImage img(seed); - if (connectivity == CONN4) { - while (seedFillGray4SlowIteration(img, mask)) { - // Continue until done. - } - } else { - while (seedFillGray8SlowIteration(img, mask)) { - // Continue until done. - } + if (connectivity == CONN4) { + while (seedFillGray4SlowIteration(img, mask)) { + // Continue until done. + } + } else { + while (seedFillGray8SlowIteration(img, mask)) { + // Continue until done. } + } - return img; + return img; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SeedFillGeneric.cpp b/imageproc/SeedFillGeneric.cpp index 8cf246897..604318d39 100644 --- a/imageproc/SeedFillGeneric.cpp +++ b/imageproc/SeedFillGeneric.cpp @@ -22,47 +22,47 @@ namespace imageproc { namespace detail { namespace seed_fill_generic { void initHorTransitions(std::vector& transitions, const int width) { - transitions.reserve(width); + transitions.reserve(width); - if (width == 1) { - // No transitions allowed. - transitions.emplace_back(0, 0); + if (width == 1) { + // No transitions allowed. + transitions.emplace_back(0, 0); - return; - } + return; + } - // Only east transition is allowed. - transitions.emplace_back(0, 1); + // Only east transition is allowed. + transitions.emplace_back(0, 1); - for (int i = 1; i < width - 1; ++i) { - // Both transitions are allowed. - transitions.emplace_back(-1, 1); - } + for (int i = 1; i < width - 1; ++i) { + // Both transitions are allowed. + transitions.emplace_back(-1, 1); + } - // Only west transition is allowed. - transitions.emplace_back(-1, 0); + // Only west transition is allowed. + transitions.emplace_back(-1, 0); } void initVertTransitions(std::vector& transitions, const int height) { - transitions.reserve(height); + transitions.reserve(height); - if (height == 1) { - // No transitions allowed. - transitions.emplace_back(0, 0); + if (height == 1) { + // No transitions allowed. + transitions.emplace_back(0, 0); - return; - } + return; + } - // Only south transition is allowed. - transitions.emplace_back(0, ~0); + // Only south transition is allowed. + transitions.emplace_back(0, ~0); - for (int i = 1; i < height - 1; ++i) { - // Both transitions are allowed. - transitions.emplace_back(~0, ~0); - } + for (int i = 1; i < height - 1; ++i) { + // Both transitions are allowed. + transitions.emplace_back(~0, ~0); + } - // Only north transition is allowed. - transitions.emplace_back(~0, 0); + // Only north transition is allowed. + transitions.emplace_back(~0, 0); } } // namespace seed_fill_generic } // namespace detail diff --git a/imageproc/SeedFillGeneric.h b/imageproc/SeedFillGeneric.h index cf638a9b3..7e2ae1861 100644 --- a/imageproc/SeedFillGeneric.h +++ b/imageproc/SeedFillGeneric.h @@ -19,48 +19,45 @@ #ifndef IMAGEPROC_SEEDFILL_GENERIC_H_ #define IMAGEPROC_SEEDFILL_GENERIC_H_ -#include "Connectivity.h" -#include "FastQueue.h" -#include "BinaryImage.h" #include -#include #include +#include +#include "BinaryImage.h" +#include "Connectivity.h" +#include "FastQueue.h" namespace imageproc { namespace detail { namespace seed_fill_generic { struct HTransition { - int west_delta; // -1 or 0 - int east_delta; // 1 or 0 + int west_delta; // -1 or 0 + int east_delta; // 1 or 0 - HTransition(int west_delta_, int east_delta_) : west_delta(west_delta_), east_delta(east_delta_) { - } + HTransition(int west_delta_, int east_delta_) : west_delta(west_delta_), east_delta(east_delta_) {} }; struct VTransition { - int north_mask; // 0 or ~0 - int south_mask; // 0 or ~0 + int north_mask; // 0 or ~0 + int south_mask; // 0 or ~0 - VTransition(int north_mask_, int south_mask_) : north_mask(north_mask_), south_mask(south_mask_) { - } + VTransition(int north_mask_, int south_mask_) : north_mask(north_mask_), south_mask(south_mask_) {} }; -template +template struct Position { - T* seed; - const T* mask; - int x; - int y; + T* seed; + const T* mask; + int x; + int y; - Position(T* seed_, const T* mask_, int x_, int y_) : seed(seed_), mask(mask_), x(x_), y(y_) { - } + Position(T* seed_, const T* mask_, int x_, int y_) : seed(seed_), mask(mask_), x(x_), y(y_) {} }; void initHorTransitions(std::vector& transitions, int width); void initVertTransitions(std::vector& transitions, int height); -template +template void seedFillSingleLine(SpreadOp spread_op, MaskOp mask_op, const int line_len, @@ -68,26 +65,26 @@ void seedFillSingleLine(SpreadOp spread_op, const int seed_stride, const T* mask, const int mask_stride) { - if (line_len == 0) { - return; - } - - *seed = mask_op(*seed, *mask); - - for (int i = 1; i < line_len; ++i) { - seed += seed_stride; - mask += mask_stride; - *seed = mask_op(*mask, spread_op(*seed, seed[-seed_stride])); - } - - for (int i = 1; i < line_len; ++i) { - seed -= seed_stride; - mask -= mask_stride; - *seed = mask_op(*mask, spread_op(*seed, seed[seed_stride])); - } + if (line_len == 0) { + return; + } + + *seed = mask_op(*seed, *mask); + + for (int i = 1; i < line_len; ++i) { + seed += seed_stride; + mask += mask_stride; + *seed = mask_op(*mask, spread_op(*seed, seed[-seed_stride])); + } + + for (int i = 1; i < line_len; ++i) { + seed -= seed_stride; + mask -= mask_stride; + *seed = mask_op(*mask, spread_op(*seed, seed[seed_stride])); + } } -template +template inline void processNeighbor(SpreadOp spread_op, MaskOp mask_op, FastQueue>& queue, @@ -98,21 +95,21 @@ inline void processNeighbor(SpreadOp spread_op, const Position& base_pos, const int x_delta, const int y_delta) { - const T new_val(mask_op(*neighbor_mask, spread_op(this_val, *neighbor))); - if (new_val != *neighbor) { - *neighbor = new_val; - const int x = base_pos.x + x_delta; - const int y = base_pos.y + y_delta; - uint32_t& in_queue_word = in_queue_line[x >> 5]; - const uint32_t in_queue_mask = (uint32_t(1) << 31) >> (x & 31); - if (!(in_queue_word & in_queue_mask)) { // If not already in the queue. - queue.push(Position(neighbor, neighbor_mask, x, y)); - in_queue_word |= in_queue_mask; // Mark as already in the queue. - } + const T new_val(mask_op(*neighbor_mask, spread_op(this_val, *neighbor))); + if (new_val != *neighbor) { + *neighbor = new_val; + const int x = base_pos.x + x_delta; + const int y = base_pos.y + y_delta; + uint32_t& in_queue_word = in_queue_line[x >> 5]; + const uint32_t in_queue_mask = (uint32_t(1) << 31) >> (x & 31); + if (!(in_queue_word & in_queue_mask)) { // If not already in the queue. + queue.push(Position(neighbor, neighbor_mask, x, y)); + in_queue_word |= in_queue_mask; // Mark as already in the queue. } + } } -template +template void spread4(SpreadOp spread_op, MaskOp mask_op, FastQueue>& queue, @@ -122,42 +119,42 @@ void spread4(SpreadOp spread_op, const VTransition* v_transitions, const int seed_stride, const int mask_stride) { - while (!queue.empty()) { - const Position pos(queue.front()); - queue.pop(); - - const T this_val(*pos.seed); - const HTransition ht(h_transitions[pos.x]); - const VTransition vt(v_transitions[pos.y]); - uint32_t* const in_queue_line = in_queue_data + in_queue_stride * pos.y; - T* seed; - const T* mask; - - // Western neighbor. - seed = pos.seed + ht.west_delta; - mask = pos.mask + ht.west_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.west_delta, 0); - - // Eastern neighbor. - seed = pos.seed + ht.east_delta; - mask = pos.mask + ht.east_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.east_delta, 0); - - // Northern neighbor. - seed = pos.seed - (seed_stride & vt.north_mask); - mask = pos.mask - (mask_stride & vt.north_mask); - processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, - mask, pos, 0, -1 & vt.north_mask); - - // Southern neighbor. - seed = pos.seed + (seed_stride & vt.south_mask); - mask = pos.mask + (mask_stride & vt.south_mask); - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, - mask, pos, 0, 1 & vt.south_mask); - } + while (!queue.empty()) { + const Position pos(queue.front()); + queue.pop(); + + const T this_val(*pos.seed); + const HTransition ht(h_transitions[pos.x]); + const VTransition vt(v_transitions[pos.y]); + uint32_t* const in_queue_line = in_queue_data + in_queue_stride * pos.y; + T* seed; + const T* mask; + + // Western neighbor. + seed = pos.seed + ht.west_delta; + mask = pos.mask + ht.west_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.west_delta, 0); + + // Eastern neighbor. + seed = pos.seed + ht.east_delta; + mask = pos.mask + ht.east_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.east_delta, 0); + + // Northern neighbor. + seed = pos.seed - (seed_stride & vt.north_mask); + mask = pos.mask - (mask_stride & vt.north_mask); + processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, mask, + pos, 0, -1 & vt.north_mask); + + // Southern neighbor. + seed = pos.seed + (seed_stride & vt.south_mask); + mask = pos.mask + (mask_stride & vt.south_mask); + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, mask, + pos, 0, 1 & vt.south_mask); + } } // spread4 -template +template void spread8(SpreadOp spread_op, MaskOp mask_op, FastQueue>& queue, @@ -167,66 +164,66 @@ void spread8(SpreadOp spread_op, const VTransition* v_transitions, const int seed_stride, const int mask_stride) { - while (!queue.empty()) { - const Position pos(queue.front()); - queue.pop(); - - const T this_val(*pos.seed); - const HTransition ht(h_transitions[pos.x]); - const VTransition vt(v_transitions[pos.y]); - uint32_t* const in_queue_line = in_queue_data + in_queue_stride * pos.y; - T* seed; - const T* mask; - - // Northern neighbor. - seed = pos.seed - (seed_stride & vt.north_mask); - mask = pos.mask - (mask_stride & vt.north_mask); - processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, - mask, pos, 0, -1 & vt.north_mask); - - // North-Western neighbor. - seed = pos.seed - (seed_stride & vt.north_mask) + ht.west_delta; - mask = pos.mask - (mask_stride & vt.north_mask) + ht.west_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, - mask, pos, ht.west_delta, -1 & vt.north_mask); - - // North-Eastern neighbor. - seed = pos.seed - (seed_stride & vt.north_mask) + ht.east_delta; - mask = pos.mask - (mask_stride & vt.north_mask) + ht.east_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, - mask, pos, ht.east_delta, -1 & vt.north_mask); - - // Eastern neighbor. - seed = pos.seed + ht.east_delta; - mask = pos.mask + ht.east_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.east_delta, 0); - - // Western neighbor. - seed = pos.seed + ht.west_delta; - mask = pos.mask + ht.west_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.west_delta, 0); - - // Southern neighbor. - seed = pos.seed + (seed_stride & vt.south_mask); - mask = pos.mask + (mask_stride & vt.south_mask); - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, - mask, pos, 0, 1 & vt.south_mask); - - // South-Eastern neighbor. - seed = pos.seed + (seed_stride & vt.south_mask) + ht.east_delta; - mask = pos.mask + (mask_stride & vt.south_mask) + ht.east_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, - mask, pos, ht.east_delta, 1 & vt.south_mask); - - // South-Western neighbor. - seed = pos.seed + (seed_stride & vt.south_mask) + ht.west_delta; - mask = pos.mask + (seed_stride & vt.south_mask) + ht.west_delta; - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, - mask, pos, ht.west_delta, 1 & vt.south_mask); - } + while (!queue.empty()) { + const Position pos(queue.front()); + queue.pop(); + + const T this_val(*pos.seed); + const HTransition ht(h_transitions[pos.x]); + const VTransition vt(v_transitions[pos.y]); + uint32_t* const in_queue_line = in_queue_data + in_queue_stride * pos.y; + T* seed; + const T* mask; + + // Northern neighbor. + seed = pos.seed - (seed_stride & vt.north_mask); + mask = pos.mask - (mask_stride & vt.north_mask); + processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, mask, + pos, 0, -1 & vt.north_mask); + + // North-Western neighbor. + seed = pos.seed - (seed_stride & vt.north_mask) + ht.west_delta; + mask = pos.mask - (mask_stride & vt.north_mask) + ht.west_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, mask, + pos, ht.west_delta, -1 & vt.north_mask); + + // North-Eastern neighbor. + seed = pos.seed - (seed_stride & vt.north_mask) + ht.east_delta; + mask = pos.mask - (mask_stride & vt.north_mask) + ht.east_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line - (in_queue_stride & vt.north_mask), this_val, seed, mask, + pos, ht.east_delta, -1 & vt.north_mask); + + // Eastern neighbor. + seed = pos.seed + ht.east_delta; + mask = pos.mask + ht.east_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.east_delta, 0); + + // Western neighbor. + seed = pos.seed + ht.west_delta; + mask = pos.mask + ht.west_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line, this_val, seed, mask, pos, ht.west_delta, 0); + + // Southern neighbor. + seed = pos.seed + (seed_stride & vt.south_mask); + mask = pos.mask + (mask_stride & vt.south_mask); + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, mask, + pos, 0, 1 & vt.south_mask); + + // South-Eastern neighbor. + seed = pos.seed + (seed_stride & vt.south_mask) + ht.east_delta; + mask = pos.mask + (mask_stride & vt.south_mask) + ht.east_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, mask, + pos, ht.east_delta, 1 & vt.south_mask); + + // South-Western neighbor. + seed = pos.seed + (seed_stride & vt.south_mask) + ht.west_delta; + mask = pos.mask + (seed_stride & vt.south_mask) + ht.west_delta; + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), this_val, seed, mask, + pos, ht.west_delta, 1 & vt.south_mask); + } } // spread8 -template +template void seedFill4(SpreadOp spread_op, MaskOp mask_op, T* const seed, @@ -234,89 +231,89 @@ void seedFill4(SpreadOp spread_op, const QSize size, const T* const mask, const int mask_stride) { - const int w = size.width(); - const int h = size.height(); - - T* seed_line = seed; - const T* mask_line = mask; - T* prev_line = seed_line; - - // Top to bottom. - for (int y = 0; y < h; ++y) { - int x = 0; - - // First item in line. - T prev(mask_op(mask_line[x], spread_op(seed_line[x], prev_line[x]))); - seed_line[x] = prev; - - // Other items, left to right. - while (++x < w) { - prev = mask_op(mask_line[x], spread_op(prev, spread_op(seed_line[x], prev_line[x]))); - seed_line[x] = prev; - } - - prev_line = seed_line; - seed_line += seed_stride; - mask_line += mask_stride; + const int w = size.width(); + const int h = size.height(); + + T* seed_line = seed; + const T* mask_line = mask; + T* prev_line = seed_line; + + // Top to bottom. + for (int y = 0; y < h; ++y) { + int x = 0; + + // First item in line. + T prev(mask_op(mask_line[x], spread_op(seed_line[x], prev_line[x]))); + seed_line[x] = prev; + + // Other items, left to right. + while (++x < w) { + prev = mask_op(mask_line[x], spread_op(prev, spread_op(seed_line[x], prev_line[x]))); + seed_line[x] = prev; } - seed_line -= seed_stride; - mask_line -= mask_stride; + prev_line = seed_line; + seed_line += seed_stride; + mask_line += mask_stride; + } + + seed_line -= seed_stride; + mask_line -= mask_stride; + + FastQueue> queue; + BinaryImage in_queue(size, WHITE); + uint32_t* const in_queue_data = in_queue.data(); + const int in_queue_stride = in_queue.wordsPerLine(); + std::vector h_transitions; + std::vector v_transitions; + initHorTransitions(h_transitions, w); + initVertTransitions(v_transitions, h); + + // Bottom to top. + uint32_t* in_queue_line = in_queue_data + in_queue_stride * (h - 1); + for (int y = h - 1; y >= 0; --y) { + const VTransition vt(v_transitions[y]); + + // Right to left. + for (int x = w - 1; x >= 0; --x) { + const HTransition ht(h_transitions[x]); + + T* const p_base_seed = seed_line + x; + const T* const p_base_mask = mask_line + x; - FastQueue> queue; - BinaryImage in_queue(size, WHITE); - uint32_t* const in_queue_data = in_queue.data(); - const int in_queue_stride = in_queue.wordsPerLine(); - std::vector h_transitions; - std::vector v_transitions; - initHorTransitions(h_transitions, w); - initVertTransitions(v_transitions, h); - - // Bottom to top. - uint32_t* in_queue_line = in_queue_data + in_queue_stride * (h - 1); - for (int y = h - 1; y >= 0; --y) { - const VTransition vt(v_transitions[y]); - - // Right to left. - for (int x = w - 1; x >= 0; --x) { - const HTransition ht(h_transitions[x]); - - T* const p_base_seed = seed_line + x; - const T* const p_base_mask = mask_line + x; - - T* const p_east_seed = p_base_seed + ht.east_delta; - T* const p_south_seed = p_base_seed + (seed_stride & vt.south_mask); - - const T new_val(mask_op(*p_base_mask, spread_op(*p_base_seed, spread_op(*p_east_seed, *p_south_seed)))); - if (new_val == *p_base_seed) { - continue; - } - - *p_base_seed = new_val; - - const Position pos(p_base_seed, p_base_mask, x, y); - const T* p_east_mask = p_base_mask + ht.east_delta; - const T* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); - - // Eastern neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line, new_val, p_east_seed, p_east_mask, pos, - ht.east_delta, 0); - - // Southern neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, - p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask); - } - - seed_line -= seed_stride; - mask_line -= mask_stride; - in_queue_line -= in_queue_stride; + T* const p_east_seed = p_base_seed + ht.east_delta; + T* const p_south_seed = p_base_seed + (seed_stride & vt.south_mask); + + const T new_val(mask_op(*p_base_mask, spread_op(*p_base_seed, spread_op(*p_east_seed, *p_south_seed)))); + if (new_val == *p_base_seed) { + continue; + } + + *p_base_seed = new_val; + + const Position pos(p_base_seed, p_base_mask, x, y); + const T* p_east_mask = p_base_mask + ht.east_delta; + const T* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); + + // Eastern neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line, new_val, p_east_seed, p_east_mask, pos, ht.east_delta, + 0); + + // Southern neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, + p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask); } - spread4(spread_op, mask_op, queue, in_queue_data, in_queue_stride, &h_transitions[0], &v_transitions[0], - seed_stride, mask_stride); + seed_line -= seed_stride; + mask_line -= mask_stride; + in_queue_line -= in_queue_stride; + } + + spread4(spread_op, mask_op, queue, in_queue_data, in_queue_stride, &h_transitions[0], &v_transitions[0], seed_stride, + mask_stride); } // seedFill4 -template +template void seedFill8(SpreadOp spread_op, MaskOp mask_op, T* const seed, @@ -324,122 +321,122 @@ void seedFill8(SpreadOp spread_op, const QSize size, const T* const mask, const int mask_stride) { - const int w = size.width(); - const int h = size.height(); - - // Some code below doesn't handle such cases. - if (w == 1) { - seedFillSingleLine(spread_op, mask_op, h, seed, seed_stride, mask, mask_stride); - return; - } else if (h == 1) { - seedFillSingleLine(spread_op, mask_op, w, seed, 1, mask, 1); - return; + const int w = size.width(); + const int h = size.height(); + + // Some code below doesn't handle such cases. + if (w == 1) { + seedFillSingleLine(spread_op, mask_op, h, seed, seed_stride, mask, mask_stride); + return; + } else if (h == 1) { + seedFillSingleLine(spread_op, mask_op, w, seed, 1, mask, 1); + return; + } + + T* seed_line = seed; + const T* mask_line = mask; + + // Note: we usually process the first line by assigning + // prev_line = seed_line, but in this case prev_line[x + 1] + // won't be clipped by its mask when we use it to update seed_line[x]. + // The wrong value may propagate further from there, so clipping + // we do on the anti-raster pass won't help. + // That's why we process the first line separately. + seed_line[0] = mask_op(seed_line[0], mask_line[0]); + for (int x = 1; x < w; ++x) { + seed_line[x] = mask_op(mask_line[x], spread_op(seed_line[x], seed_line[x - 1])); + } + + T* prev_line = seed_line; + + // Top to bottom. + for (int y = 1; y < h; ++y) { + seed_line += seed_stride; + mask_line += mask_stride; + + int x = 0; + + // Leftmost pixel. + seed_line[x] = mask_op(mask_line[x], spread_op(seed_line[x], spread_op(prev_line[x], prev_line[x + 1]))); + + // Left to right. + while (++x < w - 1) { + seed_line[x] = mask_op(mask_line[x], spread_op(spread_op(spread_op(seed_line[x], seed_line[x - 1]), + spread_op(prev_line[x], prev_line[x - 1])), + prev_line[x + 1])); } - T* seed_line = seed; - const T* mask_line = mask; - - // Note: we usually process the first line by assigning - // prev_line = seed_line, but in this case prev_line[x + 1] - // won't be clipped by its mask when we use it to update seed_line[x]. - // The wrong value may propagate further from there, so clipping - // we do on the anti-raster pass won't help. - // That's why we process the first line separately. - seed_line[0] = mask_op(seed_line[0], mask_line[0]); - for (int x = 1; x < w; ++x) { - seed_line[x] = mask_op(mask_line[x], spread_op(seed_line[x], seed_line[x - 1])); + // Rightmost pixel. + seed_line[x] = mask_op( + mask_line[x], spread_op(spread_op(seed_line[x], seed_line[x - 1]), spread_op(prev_line[x], prev_line[x - 1]))); + + prev_line = seed_line; + } + + FastQueue> queue; + BinaryImage in_queue(size, WHITE); + uint32_t* const in_queue_data = in_queue.data(); + const int in_queue_stride = in_queue.wordsPerLine(); + std::vector h_transitions; + std::vector v_transitions; + initHorTransitions(h_transitions, w); + initVertTransitions(v_transitions, h); + + // Bottom to top. + uint32_t* in_queue_line = in_queue_data + in_queue_stride * (h - 1); + for (int y = h - 1; y >= 0; --y) { + const VTransition vt(v_transitions[y]); + + for (int x = w - 1; x >= 0; --x) { + const HTransition ht(h_transitions[x]); + + T* const p_base_seed = seed_line + x; + const T* const p_base_mask = mask_line + x; + + T* const p_east_seed = p_base_seed + ht.east_delta; + T* const p_south_seed = p_base_seed + (seed_stride & vt.south_mask); + T* const p_south_west_seed = p_south_seed + ht.west_delta; + T* const p_south_east_seed = p_south_seed + ht.east_delta; + + const T new_val + = mask_op(*p_base_mask, spread_op(*p_base_seed, spread_op(spread_op(*p_east_seed, *p_south_east_seed), + spread_op(*p_south_seed, *p_south_west_seed)))); + if (new_val == *p_base_seed) { + continue; + } + + *p_base_seed = new_val; + + const Position pos(p_base_seed, p_base_mask, x, y); + const T* p_east_mask = p_base_mask + ht.east_delta; + const T* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); + const T* p_south_west_mask = p_south_mask + ht.west_delta; + const T* p_south_east_mask = p_south_mask + ht.east_delta; + + // Eastern neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line, new_val, p_east_seed, p_east_mask, pos, ht.east_delta, + 0); + + // South-eastern neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, + p_south_east_seed, p_south_east_mask, pos, ht.east_delta, 1 & vt.south_mask); + + // Southern neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, + p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask); + + // South-western neighbor. + processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, + p_south_west_seed, p_south_west_mask, pos, ht.west_delta, 1 & vt.south_mask); } - T* prev_line = seed_line; - - // Top to bottom. - for (int y = 1; y < h; ++y) { - seed_line += seed_stride; - mask_line += mask_stride; - - int x = 0; - - // Leftmost pixel. - seed_line[x] = mask_op(mask_line[x], spread_op(seed_line[x], spread_op(prev_line[x], prev_line[x + 1]))); - - // Left to right. - while (++x < w - 1) { - seed_line[x] = mask_op(mask_line[x], spread_op(spread_op(spread_op(seed_line[x], seed_line[x - 1]), - spread_op(prev_line[x], prev_line[x - 1])), - prev_line[x + 1])); - } - - // Rightmost pixel. - seed_line[x] = mask_op(mask_line[x], spread_op(spread_op(seed_line[x], seed_line[x - 1]), - spread_op(prev_line[x], prev_line[x - 1]))); - - prev_line = seed_line; - } - - FastQueue> queue; - BinaryImage in_queue(size, WHITE); - uint32_t* const in_queue_data = in_queue.data(); - const int in_queue_stride = in_queue.wordsPerLine(); - std::vector h_transitions; - std::vector v_transitions; - initHorTransitions(h_transitions, w); - initVertTransitions(v_transitions, h); - - // Bottom to top. - uint32_t* in_queue_line = in_queue_data + in_queue_stride * (h - 1); - for (int y = h - 1; y >= 0; --y) { - const VTransition vt(v_transitions[y]); - - for (int x = w - 1; x >= 0; --x) { - const HTransition ht(h_transitions[x]); - - T* const p_base_seed = seed_line + x; - const T* const p_base_mask = mask_line + x; - - T* const p_east_seed = p_base_seed + ht.east_delta; - T* const p_south_seed = p_base_seed + (seed_stride & vt.south_mask); - T* const p_south_west_seed = p_south_seed + ht.west_delta; - T* const p_south_east_seed = p_south_seed + ht.east_delta; - - const T new_val = mask_op(*p_base_mask, - spread_op(*p_base_seed, spread_op(spread_op(*p_east_seed, *p_south_east_seed), - spread_op(*p_south_seed, *p_south_west_seed)))); - if (new_val == *p_base_seed) { - continue; - } - - *p_base_seed = new_val; - - const Position pos(p_base_seed, p_base_mask, x, y); - const T* p_east_mask = p_base_mask + ht.east_delta; - const T* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); - const T* p_south_west_mask = p_south_mask + ht.west_delta; - const T* p_south_east_mask = p_south_mask + ht.east_delta; - - // Eastern neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line, new_val, p_east_seed, p_east_mask, pos, - ht.east_delta, 0); - - // South-eastern neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, - p_south_east_seed, p_south_east_mask, pos, ht.east_delta, 1 & vt.south_mask); - - // Southern neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, - p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask); - - // South-western neighbor. - processNeighbor(spread_op, mask_op, queue, in_queue_line + (in_queue_stride & vt.south_mask), new_val, - p_south_west_seed, p_south_west_mask, pos, ht.west_delta, 1 & vt.south_mask); - } - - seed_line -= seed_stride; - mask_line -= mask_stride; - in_queue_line -= in_queue_stride; - } + seed_line -= seed_stride; + mask_line -= mask_stride; + in_queue_line -= in_queue_stride; + } - spread8(spread_op, mask_op, queue, in_queue_data, in_queue_stride, &h_transitions[0], &v_transitions[0], - seed_stride, mask_stride); + spread8(spread_op, mask_op, queue, in_queue_data, in_queue_stride, &h_transitions[0], &v_transitions[0], seed_stride, + mask_stride); } // seedFill8 } // namespace seed_fill_generic } // namespace detail @@ -473,7 +470,7 @@ void seedFill8(SpreadOp spread_op, * Applications and Efficient Algorithms, technical report 91-16, Harvard Robotics Laboratory, * November 1991, IEEE Transactions on Image Processing, Vol. 2, No. 2, pp. 176-201, April 1993.\n */ -template +template void seedFillGenericInPlace(SpreadOp spread_op, MaskOp mask_op, Connectivity conn, @@ -482,16 +479,16 @@ void seedFillGenericInPlace(SpreadOp spread_op, QSize size, const T* mask, int mask_stride) { - if (size.isEmpty()) { - return; - } - - if (conn == CONN4) { - detail::seed_fill_generic::seedFill4(spread_op, mask_op, seed, seed_stride, size, mask, mask_stride); - } else { - assert(conn == CONN8); - detail::seed_fill_generic::seedFill8(spread_op, mask_op, seed, seed_stride, size, mask, mask_stride); - } + if (size.isEmpty()) { + return; + } + + if (conn == CONN4) { + detail::seed_fill_generic::seedFill4(spread_op, mask_op, seed, seed_stride, size, mask, mask_stride); + } else { + assert(conn == CONN8); + detail::seed_fill_generic::seedFill8(spread_op, mask_op, seed, seed_stride, size, mask, mask_stride); + } } } // namespace imageproc diff --git a/imageproc/Shear.cpp b/imageproc/Shear.cpp index e0e0206aa..d46b0f10d 100644 --- a/imageproc/Shear.cpp +++ b/imageproc/Shear.cpp @@ -17,9 +17,9 @@ */ #include "Shear.h" -#include "RasterOp.h" -#include #include +#include +#include "RasterOp.h" namespace imageproc { void hShearFromTo(const BinaryImage& src, @@ -27,70 +27,70 @@ void hShearFromTo(const BinaryImage& src, const double shear, const double y_origin, const BWColor background_color) { - if (src.isNull() || dst.isNull()) { - throw std::invalid_argument("Can't shear a null image"); - } - if (src.size() != dst.size()) { - throw std::invalid_argument("Can't shear when dst.size() != src.size()"); - } - - const int width = src.width(); - const int height = src.height(); - - // shift = std::floor(0.5 + shear * (y + 0.5 - y_origin)); - double shift = 0.5 + shear * (0.5 - y_origin); - const double shift_end = 0.5 + shear * (height - 0.5 - y_origin); - auto shift1 = (int) std::floor(shift); - - if (shift1 == std::floor(shift_end)) { - assert(shift1 == 0); - dst = src; - - return; - } - - int shift2 = shift1; - int y1 = 0; - int y2 = 0; - for (;;) { - ++y2; - shift += shear; - shift2 = (int) std::floor(shift); - if ((shift1 != shift2) || (y2 == height)) { - const int block_height = y2 - y1; - if (std::abs(shift1) >= width) { - // The shifted block would be completely off the image. - const QRect fr(0, y1, width, block_height); - dst.fill(fr, background_color); - } else if (shift1 < 0) { - // Shift to the left. - const QRect dr(0, y1, width + shift1, block_height); - const QPoint sp(-shift1, y1); - rasterOp(dst, dr, src, sp); - const QRect fr(width + shift1, y1, -shift1, block_height); - dst.fill(fr, background_color); - } else if (shift1 > 0) { - // Shift to the right. - const QRect dr(shift1, y1, width - shift1, block_height); - const QPoint sp(0, y1); - rasterOp(dst, dr, src, sp); - const QRect fr(0, y1, shift1, block_height); - dst.fill(fr, background_color); - } else { - // No shift, just copy. - const QRect dr(0, y1, width, block_height); - const QPoint sp(0, y1); - rasterOp(dst, dr, src, sp); - } - - if (y2 == height) { - break; - } - - y1 = y2; - shift1 = shift2; - } + if (src.isNull() || dst.isNull()) { + throw std::invalid_argument("Can't shear a null image"); + } + if (src.size() != dst.size()) { + throw std::invalid_argument("Can't shear when dst.size() != src.size()"); + } + + const int width = src.width(); + const int height = src.height(); + + // shift = std::floor(0.5 + shear * (y + 0.5 - y_origin)); + double shift = 0.5 + shear * (0.5 - y_origin); + const double shift_end = 0.5 + shear * (height - 0.5 - y_origin); + auto shift1 = (int) std::floor(shift); + + if (shift1 == std::floor(shift_end)) { + assert(shift1 == 0); + dst = src; + + return; + } + + int shift2 = shift1; + int y1 = 0; + int y2 = 0; + while (true) { + ++y2; + shift += shear; + shift2 = (int) std::floor(shift); + if ((shift1 != shift2) || (y2 == height)) { + const int block_height = y2 - y1; + if (std::abs(shift1) >= width) { + // The shifted block would be completely off the image. + const QRect fr(0, y1, width, block_height); + dst.fill(fr, background_color); + } else if (shift1 < 0) { + // Shift to the left. + const QRect dr(0, y1, width + shift1, block_height); + const QPoint sp(-shift1, y1); + rasterOp(dst, dr, src, sp); + const QRect fr(width + shift1, y1, -shift1, block_height); + dst.fill(fr, background_color); + } else if (shift1 > 0) { + // Shift to the right. + const QRect dr(shift1, y1, width - shift1, block_height); + const QPoint sp(0, y1); + rasterOp(dst, dr, src, sp); + const QRect fr(0, y1, shift1, block_height); + dst.fill(fr, background_color); + } else { + // No shift, just copy. + const QRect dr(0, y1, width, block_height); + const QPoint sp(0, y1); + rasterOp(dst, dr, src, sp); + } + + if (y2 == height) { + break; + } + + y1 = y2; + shift1 = shift2; } + } } // hShearFromTo void vShearFromTo(const BinaryImage& src, @@ -98,91 +98,91 @@ void vShearFromTo(const BinaryImage& src, const double shear, const double x_origin, const BWColor background_color) { - if (src.isNull() || dst.isNull()) { - throw std::invalid_argument("Can't shear a null image"); - } - if (src.size() != dst.size()) { - throw std::invalid_argument("Can't shear when dst.size() != src.size()"); - } - - const int width = src.width(); - const int height = src.height(); - - // shift = std::floor(0.5 + shear * (x + 0.5 - x_origin)); - double shift = 0.5 + shear * (0.5 - x_origin); - const double shift_end = 0.5 + shear * (width - 0.5 - x_origin); - auto shift1 = (int) std::floor(shift); - - if (shift1 == std::floor(shift_end)) { - assert(shift1 == 0); - dst = src; - - return; - } - - int shift2 = shift1; - int x1 = 0; - int x2 = 0; - for (;;) { - ++x2; - shift += shear; - shift2 = (int) std::floor(shift); - if ((shift1 != shift2) || (x2 == width)) { - const int block_width = x2 - x1; - if (std::abs(shift1) >= height) { - // The shifted block would be completely off the image. - const QRect fr(x1, 0, block_width, height); - dst.fill(fr, background_color); - } else if (shift1 < 0) { - // Shift upwards. - const QRect dr(x1, 0, block_width, height + shift1); - const QPoint sp(x1, -shift1); - rasterOp(dst, dr, src, sp); - const QRect fr(x1, height + shift1, block_width, -shift1); - dst.fill(fr, background_color); - } else if (shift1 > 0) { - // Shift downwards. - const QRect dr(x1, shift1, block_width, height - shift1); - const QPoint sp(x1, 0); - rasterOp(dst, dr, src, sp); - const QRect fr(x1, 0, block_width, shift1); - dst.fill(fr, background_color); - } else { - // No shift, just copy. - const QRect dr(x1, 0, block_width, height); - const QPoint sp(x1, 0); - rasterOp(dst, dr, src, sp); - } - - if (x2 == width) { - break; - } - - x1 = x2; - shift1 = shift2; - } + if (src.isNull() || dst.isNull()) { + throw std::invalid_argument("Can't shear a null image"); + } + if (src.size() != dst.size()) { + throw std::invalid_argument("Can't shear when dst.size() != src.size()"); + } + + const int width = src.width(); + const int height = src.height(); + + // shift = std::floor(0.5 + shear * (x + 0.5 - x_origin)); + double shift = 0.5 + shear * (0.5 - x_origin); + const double shift_end = 0.5 + shear * (width - 0.5 - x_origin); + auto shift1 = (int) std::floor(shift); + + if (shift1 == std::floor(shift_end)) { + assert(shift1 == 0); + dst = src; + + return; + } + + int shift2 = shift1; + int x1 = 0; + int x2 = 0; + while (true) { + ++x2; + shift += shear; + shift2 = (int) std::floor(shift); + if ((shift1 != shift2) || (x2 == width)) { + const int block_width = x2 - x1; + if (std::abs(shift1) >= height) { + // The shifted block would be completely off the image. + const QRect fr(x1, 0, block_width, height); + dst.fill(fr, background_color); + } else if (shift1 < 0) { + // Shift upwards. + const QRect dr(x1, 0, block_width, height + shift1); + const QPoint sp(x1, -shift1); + rasterOp(dst, dr, src, sp); + const QRect fr(x1, height + shift1, block_width, -shift1); + dst.fill(fr, background_color); + } else if (shift1 > 0) { + // Shift downwards. + const QRect dr(x1, shift1, block_width, height - shift1); + const QPoint sp(x1, 0); + rasterOp(dst, dr, src, sp); + const QRect fr(x1, 0, block_width, shift1); + dst.fill(fr, background_color); + } else { + // No shift, just copy. + const QRect dr(x1, 0, block_width, height); + const QPoint sp(x1, 0); + rasterOp(dst, dr, src, sp); + } + + if (x2 == width) { + break; + } + + x1 = x2; + shift1 = shift2; } + } } // vShearFromTo BinaryImage hShear(const BinaryImage& src, const double shear, const double y_origin, const BWColor background_color) { - BinaryImage dst(src.width(), src.height()); - hShearFromTo(src, dst, shear, y_origin, background_color); + BinaryImage dst(src.width(), src.height()); + hShearFromTo(src, dst, shear, y_origin, background_color); - return dst; + return dst; } BinaryImage vShear(const BinaryImage& src, const double shear, const double x_origin, const BWColor background_color) { - BinaryImage dst(src.width(), src.height()); - vShearFromTo(src, dst, shear, x_origin, background_color); + BinaryImage dst(src.width(), src.height()); + vShearFromTo(src, dst, shear, x_origin, background_color); - return dst; + return dst; } void hShearInPlace(BinaryImage& image, const double shear, const double y_origin, const BWColor background_color) { - hShearFromTo(image, image, shear, y_origin, background_color); + hShearFromTo(image, image, shear, y_origin, background_color); } void vShearInPlace(BinaryImage& image, const double shear, const double x_origin, const BWColor background_color) { - vShearFromTo(image, image, shear, x_origin, background_color); + vShearFromTo(image, image, shear, x_origin, background_color); } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SkewFinder.cpp b/imageproc/SkewFinder.cpp index a95616aa0..4b7683b93 100644 --- a/imageproc/SkewFinder.cpp +++ b/imageproc/SkewFinder.cpp @@ -17,13 +17,13 @@ */ #include "SkewFinder.h" +#include +#include #include "BinaryImage.h" #include "BitOps.h" -#include "Shear.h" -#include "ReduceThreshold.h" #include "Constants.h" -#include -#include +#include "ReduceThreshold.h" +#include "Shear.h" namespace imageproc { const double Skew::GOOD_CONFIDENCE = 2.0; @@ -39,177 +39,176 @@ const int SkewFinder::DEFAULT_FINE_REDUCTION = 1; const double SkewFinder::LOW_SCORE = 1000.0; SkewFinder::SkewFinder() - : m_maxAngle(DEFAULT_MAX_ANGLE), - m_accuracy(DEFAULT_ACCURACY), - m_resolutionRatio(1.0), - m_coarseReduction(DEFAULT_COARSE_REDUCTION), - m_fineReduction(DEFAULT_FINE_REDUCTION) { -} + : m_maxAngle(DEFAULT_MAX_ANGLE), + m_accuracy(DEFAULT_ACCURACY), + m_resolutionRatio(1.0), + m_coarseReduction(DEFAULT_COARSE_REDUCTION), + m_fineReduction(DEFAULT_FINE_REDUCTION) {} void SkewFinder::setMaxAngle(const double max_angle) { - if ((max_angle < 0.0) || (max_angle > 45.0)) { - throw std::invalid_argument("SkewFinder: max skew angle is invalid"); - } - m_maxAngle = max_angle; + if ((max_angle < 0.0) || (max_angle > 45.0)) { + throw std::invalid_argument("SkewFinder: max skew angle is invalid"); + } + m_maxAngle = max_angle; } void SkewFinder::setDesiredAccuracy(const double accuracy) { - m_accuracy = accuracy; + m_accuracy = accuracy; } void SkewFinder::setCoarseReduction(const int reduction) { - if (reduction < 0) { - throw std::invalid_argument("SkewFinder: coarse reduction is invalid"); - } - m_coarseReduction = reduction; + if (reduction < 0) { + throw std::invalid_argument("SkewFinder: coarse reduction is invalid"); + } + m_coarseReduction = reduction; } void SkewFinder::setFineReduction(const int reduction) { - if (reduction < 0) { - throw std::invalid_argument("SkewFinder: fine reduction is invalid"); - } - m_fineReduction = reduction; + if (reduction < 0) { + throw std::invalid_argument("SkewFinder: fine reduction is invalid"); + } + m_fineReduction = reduction; } void SkewFinder::setResolutionRatio(const double ratio) { - if (ratio <= 0.0) { - throw std::invalid_argument("SkewFinder: resolution ratio is invalid"); - } - m_resolutionRatio = ratio; + if (ratio <= 0.0) { + throw std::invalid_argument("SkewFinder: resolution ratio is invalid"); + } + m_resolutionRatio = ratio; } Skew SkewFinder::findSkew(const BinaryImage& image) const { - if (image.isNull()) { - throw std::invalid_argument("SkewFinder: null image was provided"); - } - - ReduceThreshold coarse_reduced(image); - const int min_reduction = std::min(m_coarseReduction, m_fineReduction); - for (int i = 0; i < min_reduction; ++i) { - coarse_reduced.reduce(i == 0 ? 1 : 2); - } - - ReduceThreshold fine_reduced(coarse_reduced.image()); - - for (int i = min_reduction; i < m_coarseReduction; ++i) { - coarse_reduced.reduce(i == 0 ? 1 : 2); - } - - BinaryImage skewed(coarse_reduced.image().size()); - const double coarse_step = 1.0; // degrees - // Coarse linear search. - int num_coarse_scores = 0; - double sum_coarse_scores = 0.0; - double best_coarse_score = 0.0; - double best_coarse_angle = -m_maxAngle; - for (double angle = -m_maxAngle; angle <= m_maxAngle; angle += coarse_step) { - const double score = process(coarse_reduced, skewed, angle); - sum_coarse_scores += score; - ++num_coarse_scores; - if (score > best_coarse_score) { - best_coarse_angle = angle; - best_coarse_score = score; - } - } - - if (m_accuracy >= coarse_step) { - double confidence = 0.0; - if (num_coarse_scores > 1) { - confidence = best_coarse_score / sum_coarse_scores * num_coarse_scores; - } - - return Skew(-best_coarse_angle, confidence - 1.0); - } - - for (int i = min_reduction; i < m_fineReduction; ++i) { - fine_reduced.reduce(i == 0 ? 1 : 2); - } - - if (m_coarseReduction != m_fineReduction) { - skewed = BinaryImage(fine_reduced.image().size()); - } - // Fine binary search. - double angle_plus = best_coarse_angle + 0.5 * coarse_step; - double angle_minus = best_coarse_angle - 0.5 * coarse_step; - double score_plus = process(fine_reduced, skewed, angle_plus); - double score_minus = process(fine_reduced, skewed, angle_minus); - const double fine_score1 = score_plus; - const double fine_score2 = score_minus; - while (angle_plus - angle_minus > m_accuracy) { - if (score_plus > score_minus) { - angle_minus = 0.5 * (angle_plus + angle_minus); - score_minus = process(fine_reduced, skewed, angle_minus); - } else if (score_plus < score_minus) { - angle_plus = 0.5 * (angle_plus + angle_minus); - score_plus = process(fine_reduced, skewed, angle_plus); - } else { - // This protects us from unreasonably low m_accuracy. - break; - } - } - - double best_angle; - double best_score; - if (score_plus > score_minus) { - best_angle = angle_plus; - best_score = score_plus; - } else { - best_angle = angle_minus; - best_score = score_minus; - } - - if (best_score <= LOW_SCORE) { - return Skew(-best_angle, 0.0); // Zero confidence. - } - + if (image.isNull()) { + throw std::invalid_argument("SkewFinder: null image was provided"); + } + + ReduceThreshold coarse_reduced(image); + const int min_reduction = std::min(m_coarseReduction, m_fineReduction); + for (int i = 0; i < min_reduction; ++i) { + coarse_reduced.reduce(i == 0 ? 1 : 2); + } + + ReduceThreshold fine_reduced(coarse_reduced.image()); + + for (int i = min_reduction; i < m_coarseReduction; ++i) { + coarse_reduced.reduce(i == 0 ? 1 : 2); + } + + BinaryImage skewed(coarse_reduced.image().size()); + const double coarse_step = 1.0; // degrees + // Coarse linear search. + int num_coarse_scores = 0; + double sum_coarse_scores = 0.0; + double best_coarse_score = 0.0; + double best_coarse_angle = -m_maxAngle; + for (double angle = -m_maxAngle; angle <= m_maxAngle; angle += coarse_step) { + const double score = process(coarse_reduced, skewed, angle); + sum_coarse_scores += score; + ++num_coarse_scores; + if (score > best_coarse_score) { + best_coarse_angle = angle; + best_coarse_score = score; + } + } + + if (m_accuracy >= coarse_step) { double confidence = 0.0; if (num_coarse_scores > 1) { - confidence = best_score / sum_coarse_scores * num_coarse_scores; + confidence = best_coarse_score / sum_coarse_scores * num_coarse_scores; + } + + return Skew(-best_coarse_angle, confidence - 1.0); + } + + for (int i = min_reduction; i < m_fineReduction; ++i) { + fine_reduced.reduce(i == 0 ? 1 : 2); + } + + if (m_coarseReduction != m_fineReduction) { + skewed = BinaryImage(fine_reduced.image().size()); + } + // Fine binary search. + double angle_plus = best_coarse_angle + 0.5 * coarse_step; + double angle_minus = best_coarse_angle - 0.5 * coarse_step; + double score_plus = process(fine_reduced, skewed, angle_plus); + double score_minus = process(fine_reduced, skewed, angle_minus); + const double fine_score1 = score_plus; + const double fine_score2 = score_minus; + while (angle_plus - angle_minus > m_accuracy) { + if (score_plus > score_minus) { + angle_minus = 0.5 * (angle_plus + angle_minus); + score_minus = process(fine_reduced, skewed, angle_minus); + } else if (score_plus < score_minus) { + angle_plus = 0.5 * (angle_plus + angle_minus); + score_plus = process(fine_reduced, skewed, angle_plus); } else { - int num_scores = num_coarse_scores; - double sum_scores = sum_coarse_scores; - num_scores += 2; - sum_scores += fine_score1; - sum_scores += fine_score2; - confidence = best_score / sum_scores * num_scores; - } - - return Skew(-best_angle, confidence - 1.0); + // This protects us from unreasonably low m_accuracy. + break; + } + } + + double best_angle; + double best_score; + if (score_plus > score_minus) { + best_angle = angle_plus; + best_score = score_plus; + } else { + best_angle = angle_minus; + best_score = score_minus; + } + + if (best_score <= LOW_SCORE) { + return Skew(-best_angle, 0.0); // Zero confidence. + } + + double confidence = 0.0; + if (num_coarse_scores > 1) { + confidence = best_score / sum_coarse_scores * num_coarse_scores; + } else { + int num_scores = num_coarse_scores; + double sum_scores = sum_coarse_scores; + num_scores += 2; + sum_scores += fine_score1; + sum_scores += fine_score2; + confidence = best_score / sum_scores * num_scores; + } + + return Skew(-best_angle, confidence - 1.0); } // SkewFinder::findSkew double SkewFinder::process(const BinaryImage& src, BinaryImage& dst, const double angle) const { - const double tg = std::tan(angle * constants::DEG2RAD); - const double x_center = 0.5 * dst.width(); - vShearFromTo(src, dst, tg / m_resolutionRatio, x_center, WHITE); + const double tg = std::tan(angle * constants::DEG2RAD); + const double x_center = 0.5 * dst.width(); + vShearFromTo(src, dst, tg / m_resolutionRatio, x_center, WHITE); - return calcScore(dst); + return calcScore(dst); } double SkewFinder::calcScore(const BinaryImage& image) { - const int width = image.width(); - const int height = image.height(); - const uint32_t* line = image.data(); - const int wpl = image.wordsPerLine(); - const int last_word_idx = (width - 1) >> 5; - const uint32_t last_word_mask = ~uint32_t(0) << (31 - ((width - 1) & 31)); - - double score = 0.0; - int last_line_black_pixels = 0; - for (int y = 0; y < height; ++y, line += wpl) { - int num_black_pixels = 0; - int i = 0; - for (; i != last_word_idx; ++i) { - num_black_pixels += countNonZeroBits(line[i]); - } - num_black_pixels += countNonZeroBits(line[i] & last_word_mask); - - if (y != 0) { - const double diff = num_black_pixels - last_line_black_pixels; - score += diff * diff; - } - last_line_black_pixels = num_black_pixels; - } - - return score; + const int width = image.width(); + const int height = image.height(); + const uint32_t* line = image.data(); + const int wpl = image.wordsPerLine(); + const int last_word_idx = (width - 1) >> 5; + const uint32_t last_word_mask = ~uint32_t(0) << (31 - ((width - 1) & 31)); + + double score = 0.0; + int last_line_black_pixels = 0; + for (int y = 0; y < height; ++y, line += wpl) { + int num_black_pixels = 0; + int i = 0; + for (; i != last_word_idx; ++i) { + num_black_pixels += countNonZeroBits(line[i]); + } + num_black_pixels += countNonZeroBits(line[i] & last_word_mask); + + if (y != 0) { + const double diff = num_black_pixels - last_line_black_pixels; + score += diff * diff; + } + last_line_black_pixels = num_black_pixels; + } + + return score; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SkewFinder.h b/imageproc/SkewFinder.h index 72f0b98c6..bd4a3841f 100644 --- a/imageproc/SkewFinder.h +++ b/imageproc/SkewFinder.h @@ -29,131 +29,125 @@ class BinaryImage; * \see SkewFinder */ class Skew { -public: - /** - * \brief The threshold separating good and poor confidence values. - * \see confidence() - */ - static const double GOOD_CONFIDENCE; - - Skew() : m_angle(0.0), m_confidence(0.0) { - } - - Skew(double angle, double confidence) : m_angle(angle), m_confidence(confidence) { - } - - /** - * \brief Get the skew angle in degrees. - * - * Positive values indicate clockwise skews. - */ - double angle() const { - return m_angle; - } - - /** - * \brief Get the confidence value. - * - * The worst possible confidence is 0, while everything - * above or equal to GOOD_CONFIDENCE indicates high - * confidence level. - */ - double confidence() const { - return m_confidence; - } - -private: - double m_angle; - double m_confidence; + public: + /** + * \brief The threshold separating good and poor confidence values. + * \see confidence() + */ + static const double GOOD_CONFIDENCE; + + Skew() : m_angle(0.0), m_confidence(0.0) {} + + Skew(double angle, double confidence) : m_angle(angle), m_confidence(confidence) {} + + /** + * \brief Get the skew angle in degrees. + * + * Positive values indicate clockwise skews. + */ + double angle() const { return m_angle; } + + /** + * \brief Get the confidence value. + * + * The worst possible confidence is 0, while everything + * above or equal to GOOD_CONFIDENCE indicates high + * confidence level. + */ + double confidence() const { return m_confidence; } + + private: + double m_angle; + double m_confidence; }; class SkewFinder { - DECLARE_NON_COPYABLE(SkewFinder) - -public: - static const double DEFAULT_MAX_ANGLE; - - static const double DEFAULT_ACCURACY; - - static const int DEFAULT_COARSE_REDUCTION; - - static const int DEFAULT_FINE_REDUCTION; - - SkewFinder(); - - /** - * \brief Set the maximum skew angle, in degrees. - * - * The range between 0 and max_angle degrees both clockwise - * and counter-clockwise will be checked. - * \note The angle can't exceed 45 degrees. - */ - void setMaxAngle(double max_angle = DEFAULT_MAX_ANGLE); - - /** - * \brief Set the desired accuracy. - * - * Accuracy is the allowed deviation from the actual skew - * angle, in degrees. - */ - void setDesiredAccuracy(double accuracy = DEFAULT_ACCURACY); - - /** - * \brief Downscale the image before doing a coarse search. - * - * Downscaling the image before doing a coarse search will speed - * things up, but may reduce accuracy. Specifying a value - * that is too high will cause totally wrong results. - * \param reduction The number of times to apply a 2x downscaling - * to the image before doing a coarse search. - * The default value is recommended for 300 dpi - * scans of hight quality material. - */ - void setCoarseReduction(int reduction = DEFAULT_COARSE_REDUCTION); - - /** - * \brief Downscale the image before doing a fine search. - * - * Downscaling the image before doing a fine search will speed - * things up, but may reduce accuracy. Comared to a reduction - * before a coarse search, it won't give as much of a speed-up, - * but it won't cause completely wrong results. - * \param reduction The number of times to apply a 2x downscaling - * to the image before doing a fine search. - * The default value is recommended for 300 dpi - * scans of hight quality material. - */ - void setFineReduction(int reduction = DEFAULT_FINE_REDUCTION); - - /** - * \brief Set the horizontal to vertical optical resolution ratio. - * - * If horizontal and vertical optical resolutions (DPI) differ, - * it's necessary to provide their ratio. - * \param ratio Horizontal optical resolution divided by vertical one. - */ - void setResolutionRatio(double ratio); - - /** - * \brief Process the image and determine its skew. - * \note If the image contains text columns at (slightly) different - * angles, one of those angles will be found, with a lower confidence. - */ - Skew findSkew(const BinaryImage& image) const; - -private: - static const double LOW_SCORE; - - double process(const BinaryImage& src, BinaryImage& dst, double angle) const; - - static double calcScore(const BinaryImage& image); - - double m_maxAngle; - double m_accuracy; - double m_resolutionRatio; - int m_coarseReduction; - int m_fineReduction; + DECLARE_NON_COPYABLE(SkewFinder) + + public: + static const double DEFAULT_MAX_ANGLE; + + static const double DEFAULT_ACCURACY; + + static const int DEFAULT_COARSE_REDUCTION; + + static const int DEFAULT_FINE_REDUCTION; + + SkewFinder(); + + /** + * \brief Set the maximum skew angle, in degrees. + * + * The range between 0 and max_angle degrees both clockwise + * and counter-clockwise will be checked. + * \note The angle can't exceed 45 degrees. + */ + void setMaxAngle(double max_angle = DEFAULT_MAX_ANGLE); + + /** + * \brief Set the desired accuracy. + * + * Accuracy is the allowed deviation from the actual skew + * angle, in degrees. + */ + void setDesiredAccuracy(double accuracy = DEFAULT_ACCURACY); + + /** + * \brief Downscale the image before doing a coarse search. + * + * Downscaling the image before doing a coarse search will speed + * things up, but may reduce accuracy. Specifying a value + * that is too high will cause totally wrong results. + * \param reduction The number of times to apply a 2x downscaling + * to the image before doing a coarse search. + * The default value is recommended for 300 dpi + * scans of hight quality material. + */ + void setCoarseReduction(int reduction = DEFAULT_COARSE_REDUCTION); + + /** + * \brief Downscale the image before doing a fine search. + * + * Downscaling the image before doing a fine search will speed + * things up, but may reduce accuracy. Comared to a reduction + * before a coarse search, it won't give as much of a speed-up, + * but it won't cause completely wrong results. + * \param reduction The number of times to apply a 2x downscaling + * to the image before doing a fine search. + * The default value is recommended for 300 dpi + * scans of hight quality material. + */ + void setFineReduction(int reduction = DEFAULT_FINE_REDUCTION); + + /** + * \brief Set the horizontal to vertical optical resolution ratio. + * + * If horizontal and vertical optical resolutions (DPI) differ, + * it's necessary to provide their ratio. + * \param ratio Horizontal optical resolution divided by vertical one. + */ + void setResolutionRatio(double ratio); + + /** + * \brief Process the image and determine its skew. + * \note If the image contains text columns at (slightly) different + * angles, one of those angles will be found, with a lower confidence. + */ + Skew findSkew(const BinaryImage& image) const; + + private: + static const double LOW_SCORE; + + double process(const BinaryImage& src, BinaryImage& dst, double angle) const; + + static double calcScore(const BinaryImage& image); + + double m_maxAngle; + double m_accuracy; + double m_resolutionRatio; + int m_coarseReduction; + int m_fineReduction; }; } // namespace imageproc #endif // ifndef IMAGEPROC_SKEWFINDER_H_ diff --git a/imageproc/SlicedHistogram.cpp b/imageproc/SlicedHistogram.cpp index 72b01f8c0..3ec38adc6 100644 --- a/imageproc/SlicedHistogram.cpp +++ b/imageproc/SlicedHistogram.cpp @@ -24,79 +24,79 @@ namespace imageproc { SlicedHistogram::SlicedHistogram() = default; SlicedHistogram::SlicedHistogram(const BinaryImage& image, const Type type) { - switch (type) { - case ROWS: - processHorizontalLines(image, image.rect()); - break; - case COLS: - processVerticalLines(image, image.rect()); - break; - } + switch (type) { + case ROWS: + processHorizontalLines(image, image.rect()); + break; + case COLS: + processVerticalLines(image, image.rect()); + break; + } } SlicedHistogram::SlicedHistogram(const BinaryImage& image, const QRect& area, const Type type) { - if (!image.rect().contains(area)) { - throw std::invalid_argument("SlicedHistogram: area exceeds the image"); - } + if (!image.rect().contains(area)) { + throw std::invalid_argument("SlicedHistogram: area exceeds the image"); + } - switch (type) { - case ROWS: - processHorizontalLines(image, area); - break; - case COLS: - processVerticalLines(image, area); - break; - } + switch (type) { + case ROWS: + processHorizontalLines(image, area); + break; + case COLS: + processVerticalLines(image, area); + break; + } } void SlicedHistogram::processHorizontalLines(const BinaryImage& image, const QRect& area) { - m_data.reserve(area.height()); + m_data.reserve(area.height()); - const int top = area.top(); - const int bottom = area.bottom(); - const int wpl = image.wordsPerLine(); - const int first_word_idx = area.left() >> 5; - const int last_word_idx = area.right() >> 5; // area.right() is within area - const uint32_t first_word_mask = ~uint32_t(0) >> (area.left() & 31); - const int last_word_unused_bits = (last_word_idx << 5) + 31 - area.right(); - const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; - const uint32_t* line = image.data() + top * wpl; + const int top = area.top(); + const int bottom = area.bottom(); + const int wpl = image.wordsPerLine(); + const int first_word_idx = area.left() >> 5; + const int last_word_idx = area.right() >> 5; // area.right() is within area + const uint32_t first_word_mask = ~uint32_t(0) >> (area.left() & 31); + const int last_word_unused_bits = (last_word_idx << 5) + 31 - area.right(); + const uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; + const uint32_t* line = image.data() + top * wpl; - if (first_word_idx == last_word_idx) { - const uint32_t mask = first_word_mask & last_word_mask; - for (int y = top; y <= bottom; ++y, line += wpl) { - const int count = countNonZeroBits(line[first_word_idx] & mask); - m_data.push_back(count); - } - } else { - for (int y = top; y <= bottom; ++y, line += wpl) { - int idx = first_word_idx; - int count = countNonZeroBits(line[idx] & first_word_mask); - for (++idx; idx != last_word_idx; ++idx) { - count += countNonZeroBits(line[idx]); - } - count += countNonZeroBits(line[idx] & last_word_mask); - m_data.push_back(count); - } + if (first_word_idx == last_word_idx) { + const uint32_t mask = first_word_mask & last_word_mask; + for (int y = top; y <= bottom; ++y, line += wpl) { + const int count = countNonZeroBits(line[first_word_idx] & mask); + m_data.push_back(count); + } + } else { + for (int y = top; y <= bottom; ++y, line += wpl) { + int idx = first_word_idx; + int count = countNonZeroBits(line[idx] & first_word_mask); + for (++idx; idx != last_word_idx; ++idx) { + count += countNonZeroBits(line[idx]); + } + count += countNonZeroBits(line[idx] & last_word_mask); + m_data.push_back(count); } + } } // SlicedHistogram::processHorizontalLines void SlicedHistogram::processVerticalLines(const BinaryImage& image, const QRect& area) { - m_data.reserve(area.width()); + m_data.reserve(area.width()); - const int right = area.right(); - const int height = area.height(); - const int wpl = image.wordsPerLine(); - const uint32_t* const top_line = image.data() + area.top() * wpl; + const int right = area.right(); + const int height = area.height(); + const int wpl = image.wordsPerLine(); + const uint32_t* const top_line = image.data() + area.top() * wpl; - for (int x = area.left(); x <= right; ++x) { - const uint32_t* pword = top_line + (x >> 5); - const int least_significant_zeroes = 31 - (x & 31); - int count = 0; - for (int i = 0; i < height; ++i, pword += wpl) { - count += (*pword >> least_significant_zeroes) & 1; - } - m_data.push_back(count); + for (int x = area.left(); x <= right; ++x) { + const uint32_t* pword = top_line + (x >> 5); + const int least_significant_zeroes = 31 - (x & 31); + int count = 0; + for (int i = 0; i < height; ++i, pword += wpl) { + count += (*pword >> least_significant_zeroes) & 1; } + m_data.push_back(count); + } } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/SlicedHistogram.h b/imageproc/SlicedHistogram.h index a8f9e7e19..c4f8ac3cd 100644 --- a/imageproc/SlicedHistogram.h +++ b/imageproc/SlicedHistogram.h @@ -19,8 +19,8 @@ #ifndef IMAGEPROC_SLICEDHISTOGRAM_H_ #define IMAGEPROC_SLICEDHISTOGRAM_H_ -#include #include +#include class QRect; @@ -32,63 +32,55 @@ class BinaryImage; * in each horizontal or vertical line. */ class SlicedHistogram { - // Member-wise copying is OK. -public: - enum Type { - ROWS, /**< Process horizontal lines. */ - COLS /**< Process vertical lines. */ - }; - - /** - * \brief Constructs an empty histogram. - */ - SlicedHistogram(); - - /** - * \brief Calculates the histogram of the whole image. - * - * \param image The image to process. A null image will produce - * an empty histogram. - * \param type Specifies whether to process columns or rows. - */ - SlicedHistogram(const BinaryImage& image, Type type); - - /** - * \brief Calculates the histogram of a portion of the image. - * - * \param image The image to process. A null image will produce - * an empty histogram, provided that \p area is also null. - * \param area The area of the image to process. The first value - * in the histogram will correspond to the first line in this area. - * \param type Specifies whether to process columns or rows. - * - * \exception std::invalid_argument If \p area is not completely - * within image.rect(). - */ - SlicedHistogram(const BinaryImage& image, const QRect& area, Type type); - - size_t size() const { - return m_data.size(); - } - - void setSize(size_t size) { - m_data.resize(size); - } - - const int& operator[](size_t idx) const { - return m_data[idx]; - } - - int& operator[](size_t idx) { - return m_data[idx]; - } - -private: - void processHorizontalLines(const BinaryImage& image, const QRect& area); - - void processVerticalLines(const BinaryImage& image, const QRect& area); - - std::vector m_data; + // Member-wise copying is OK. + public: + enum Type { + ROWS, /**< Process horizontal lines. */ + COLS /**< Process vertical lines. */ + }; + + /** + * \brief Constructs an empty histogram. + */ + SlicedHistogram(); + + /** + * \brief Calculates the histogram of the whole image. + * + * \param image The image to process. A null image will produce + * an empty histogram. + * \param type Specifies whether to process columns or rows. + */ + SlicedHistogram(const BinaryImage& image, Type type); + + /** + * \brief Calculates the histogram of a portion of the image. + * + * \param image The image to process. A null image will produce + * an empty histogram, provided that \p area is also null. + * \param area The area of the image to process. The first value + * in the histogram will correspond to the first line in this area. + * \param type Specifies whether to process columns or rows. + * + * \exception std::invalid_argument If \p area is not completely + * within image.rect(). + */ + SlicedHistogram(const BinaryImage& image, const QRect& area, Type type); + + size_t size() const { return m_data.size(); } + + void setSize(size_t size) { m_data.resize(size); } + + const int& operator[](size_t idx) const { return m_data[idx]; } + + int& operator[](size_t idx) { return m_data[idx]; } + + private: + void processHorizontalLines(const BinaryImage& image, const QRect& area); + + void processVerticalLines(const BinaryImage& image, const QRect& area); + + std::vector m_data; }; } // namespace imageproc #endif // ifndef IMAGEPROC_SLICEDHISTOGRAM_H_ diff --git a/imageproc/Sobel.h b/imageproc/Sobel.h index aef4f8bc1..12bfafffd 100644 --- a/imageproc/Sobel.h +++ b/imageproc/Sobel.h @@ -76,14 +76,14 @@ namespace imageproc { * \param dst_writer A functor that writes a value to the destination grid. * See \p tmp_writer for more info. */ -template +template void horizontalSobel(int width, int height, SrcIt src, @@ -101,14 +101,14 @@ void horizontalSobel(int width, /** * \see horizontalSobel() */ -template +template void verticalSobel(int width, int height, SrcIt src, @@ -123,14 +123,14 @@ void verticalSobel(int width, DstWriter dst_writer); -template +template void horizontalSobel(const int width, const int height, SrcIt src, @@ -143,72 +143,72 @@ void horizontalSobel(const int width, DstIt dst, const int dst_stride, DstWriter dst_writer) { - if ((width <= 0) || (height <= 0)) { - return; - } - - // Vertical pre-accumulation pass: mid = top + mid*2 + bottom - for (int x = 0; x < width; ++x) { - SrcIt p_src(src + x); - TmpIt p_tmp(tmp + x); + if ((width <= 0) || (height <= 0)) { + return; + } - T top(src_reader(*p_src)); - if (height == 1) { - tmp_writer(*p_tmp, top + top + top + top); - continue; - } + // Vertical pre-accumulation pass: mid = top + mid*2 + bottom + for (int x = 0; x < width; ++x) { + SrcIt p_src(src + x); + TmpIt p_tmp(tmp + x); - T mid(src_reader(p_src[src_stride])); - tmp_writer(*p_tmp, top + top + top + mid); + T top(src_reader(*p_src)); + if (height == 1) { + tmp_writer(*p_tmp, top + top + top + top); + continue; + } - for (int y = 1; y < height - 1; ++y) { - p_src += src_stride; - p_tmp += tmp_stride; - const T bottom(src_reader(p_src[src_stride])); - tmp_writer(*p_tmp, top + mid + mid + bottom); - top = mid; - mid = bottom; - } + T mid(src_reader(p_src[src_stride])); + tmp_writer(*p_tmp, top + top + top + mid); - p_src += src_stride; - p_tmp += tmp_stride; - tmp_writer(*p_tmp, top + mid + mid + mid); + for (int y = 1; y < height - 1; ++y) { + p_src += src_stride; + p_tmp += tmp_stride; + const T bottom(src_reader(p_src[src_stride])); + tmp_writer(*p_tmp, top + mid + mid + bottom); + top = mid; + mid = bottom; } - // Horizontal pass: mid = right - left - for (int y = 0; y < height; ++y) { - T left(tmp_reader(*tmp)); + p_src += src_stride; + p_tmp += tmp_stride; + tmp_writer(*p_tmp, top + mid + mid + mid); + } - if (width == 1) { - dst_writer(*dst, left - left); - } else { - T mid(tmp_reader(tmp[1])); - dst_writer(dst[0], mid - left); + // Horizontal pass: mid = right - left + for (int y = 0; y < height; ++y) { + T left(tmp_reader(*tmp)); - int x = 1; - for (; x < width - 1; ++x) { - const T right(tmp_reader(tmp[x + 1])); - dst_writer(dst[x], right - left); - left = mid; - mid = right; - } + if (width == 1) { + dst_writer(*dst, left - left); + } else { + T mid(tmp_reader(tmp[1])); + dst_writer(dst[0], mid - left); - dst_writer(dst[x], mid - left); - } + int x = 1; + for (; x < width - 1; ++x) { + const T right(tmp_reader(tmp[x + 1])); + dst_writer(dst[x], right - left); + left = mid; + mid = right; + } - tmp += tmp_stride; - dst += dst_stride; + dst_writer(dst[x], mid - left); } + + tmp += tmp_stride; + dst += dst_stride; + } } // horizontalSobel -template +template void verticalSobel(const int width, const int height, SrcIt src, @@ -221,63 +221,63 @@ void verticalSobel(const int width, DstIt dst, const int dst_stride, DstWriter dst_writer) { - if ((width <= 0) || (height <= 0)) { - return; - } + if ((width <= 0) || (height <= 0)) { + return; + } - const TmpIt tmp_orig(tmp); + const TmpIt tmp_orig(tmp); - // Horizontal pre-accumulation pass: mid = left + mid*2 + right - for (int y = 0; y < height; ++y) { - T left(src_reader(*src)); + // Horizontal pre-accumulation pass: mid = left + mid*2 + right + for (int y = 0; y < height; ++y) { + T left(src_reader(*src)); - if (width == 1) { - tmp_writer(*tmp, left + left + left + left); - } else { - T mid(src_reader(src[1])); - tmp_writer(tmp[0], left + left + left + mid); + if (width == 1) { + tmp_writer(*tmp, left + left + left + left); + } else { + T mid(src_reader(src[1])); + tmp_writer(tmp[0], left + left + left + mid); - int x = 1; - for (; x < width - 1; ++x) { - const T right(src_reader(src[x + 1])); - tmp_writer(tmp[x], left + mid + mid + right); - left = mid; - mid = right; - } + int x = 1; + for (; x < width - 1; ++x) { + const T right(src_reader(src[x + 1])); + tmp_writer(tmp[x], left + mid + mid + right); + left = mid; + mid = right; + } - tmp_writer(tmp[x], left + mid + mid + mid); - } - src += src_stride; - tmp += tmp_stride; + tmp_writer(tmp[x], left + mid + mid + mid); } + src += src_stride; + tmp += tmp_stride; + } - // Vertical pass: mid = bottom - top - for (int x = 0; x < width; ++x) { - TmpIt p_tmp(tmp_orig + x); - TmpIt p_dst(dst + x); - - T top(tmp_reader(*p_tmp)); - if (height == 1) { - dst_writer(*p_dst, top - top); - continue; - } + // Vertical pass: mid = bottom - top + for (int x = 0; x < width; ++x) { + TmpIt p_tmp(tmp_orig + x); + TmpIt p_dst(dst + x); - T mid(tmp_reader(p_tmp[tmp_stride])); - dst_writer(*p_dst, mid - top); + T top(tmp_reader(*p_tmp)); + if (height == 1) { + dst_writer(*p_dst, top - top); + continue; + } - for (int y = 1; y < height - 1; ++y) { - p_tmp += tmp_stride; - p_dst += dst_stride; - const T bottom(tmp_reader(p_tmp[tmp_stride])); - dst_writer(*p_dst, bottom - top); - top = mid; - mid = bottom; - } + T mid(tmp_reader(p_tmp[tmp_stride])); + dst_writer(*p_dst, mid - top); - p_tmp += tmp_stride; - p_dst += dst_stride; - dst_writer(*p_dst, mid - top); + for (int y = 1; y < height - 1; ++y) { + p_tmp += tmp_stride; + p_dst += dst_stride; + const T bottom(tmp_reader(p_tmp[tmp_stride])); + dst_writer(*p_dst, bottom - top); + top = mid; + mid = bottom; } + + p_tmp += tmp_stride; + p_dst += dst_stride; + dst_writer(*p_dst, mid - top); + } } // verticalSobel } // namespace imageproc #endif // ifndef IMAGEPROC_SOBEL_H_ diff --git a/imageproc/Transform.cpp b/imageproc/Transform.cpp index d6081e517..ec7ff9918 100644 --- a/imageproc/Transform.cpp +++ b/imageproc/Transform.cpp @@ -16,48 +16,44 @@ along with this program. If not, see . */ -#include "BadAllocIfNull.h" -#include "ColorMixer.h" #include "Transform.h" -#include "Grayscale.h" #include #include +#include "BadAllocIfNull.h" +#include "ColorMixer.h" +#include "Grayscale.h" namespace imageproc { namespace { struct XLess { - bool operator()(const QPointF& lhs, const QPointF& rhs) const { - return lhs.x() < rhs.x(); - } + bool operator()(const QPointF& lhs, const QPointF& rhs) const { return lhs.x() < rhs.x(); } }; struct YLess { - bool operator()(const QPointF& lhs, const QPointF& rhs) const { - return lhs.y() < rhs.y(); - } + bool operator()(const QPointF& lhs, const QPointF& rhs) const { return lhs.y() < rhs.y(); } }; QSizeF calcSrcUnitSize(const QTransform& xform, const QSizeF& min) { - // Imagine a rectangle of (0, 0, 1, 1), except we take - // centers of its edges instead of its vertices. - QPolygonF dst_poly; - dst_poly.push_back(QPointF(0.5, 0.0)); - dst_poly.push_back(QPointF(1.0, 0.5)); - dst_poly.push_back(QPointF(0.5, 1.0)); - dst_poly.push_back(QPointF(0.0, 0.5)); - - QPolygonF src_poly(xform.map(dst_poly)); - std::sort(src_poly.begin(), src_poly.end(), XLess()); - const double width = src_poly.back().x() - src_poly.front().x(); - std::sort(src_poly.begin(), src_poly.end(), YLess()); - const double height = src_poly.back().y() - src_poly.front().y(); - - const QSizeF min32(min * 32.0); - - return QSizeF(std::max(min32.width(), width), std::max(min32.height(), height)); + // Imagine a rectangle of (0, 0, 1, 1), except we take + // centers of its edges instead of its vertices. + QPolygonF dst_poly; + dst_poly.push_back(QPointF(0.5, 0.0)); + dst_poly.push_back(QPointF(1.0, 0.5)); + dst_poly.push_back(QPointF(0.5, 1.0)); + dst_poly.push_back(QPointF(0.0, 0.5)); + + QPolygonF src_poly(xform.map(dst_poly)); + std::sort(src_poly.begin(), src_poly.end(), XLess()); + const double width = src_poly.back().x() - src_poly.front().x(); + std::sort(src_poly.begin(), src_poly.end(), YLess()); + const double height = src_poly.back().y() - src_poly.front().y(); + + const QSizeF min32(min * 32.0); + + return QSizeF(std::max(min32.width(), width), std::max(min32.height(), height)); } -template +template static void transformGeneric(const StorageUnit* const src_data, const int src_stride, const QSize src_size, @@ -68,252 +64,252 @@ static void transformGeneric(const StorageUnit* const src_data, const StorageUnit outside_color, const int outside_flags, const QSizeF& min_mapping_area) { - const int sw = src_size.width(); - const int sh = src_size.height(); - const int dw = dst_rect.width(); - const int dh = dst_rect.height(); - - StorageUnit* dst_line = dst_data; - - QTransform inv_xform; - inv_xform.translate(dst_rect.x(), dst_rect.y()); - inv_xform *= xform.inverted(); - inv_xform *= QTransform().scale(32.0, 32.0); - - // sx32 = dx*inv_xform.m11() + dy*inv_xform.m21() + inv_xform.dx(); - // sy32 = dy*inv_xform.m22() + dx*inv_xform.m12() + inv_xform.dy(); - - const QSizeF src32_unit_size(calcSrcUnitSize(inv_xform, min_mapping_area)); - const int src32_unit_w = std::max(1, qRound(src32_unit_size.width())); - const int src32_unit_h = std::max(1, qRound(src32_unit_size.height())); - - for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { - const double f_dy_center = dy + 0.5; - const double f_sx32_base = f_dy_center * inv_xform.m21() + inv_xform.dx(); - const double f_sy32_base = f_dy_center * inv_xform.m22() + inv_xform.dy(); - - for (int dx = 0; dx < dw; ++dx) { - const double f_dx_center = dx + 0.5; - const double f_sx32_center = f_sx32_base + f_dx_center * inv_xform.m11(); - const double f_sy32_center = f_sy32_base + f_dx_center * inv_xform.m12(); - int src32_left = (int) f_sx32_center - (src32_unit_w >> 1); - int src32_top = (int) f_sy32_center - (src32_unit_h >> 1); - int src32_right = src32_left + src32_unit_w; - int src32_bottom = src32_top + src32_unit_h; - int src_left = src32_left >> 5; - int src_right = (src32_right - 1) >> 5; // inclusive - int src_top = src32_top >> 5; - int src_bottom = (src32_bottom - 1) >> 5; // inclusive - assert(src_bottom >= src_top); - assert(src_right >= src_left); - - if ((src_bottom < 0) || (src_right < 0) || (src_left >= sw) || (src_top >= sh)) { - // Completely outside of src image. - if (outside_flags & OutsidePixels::COLOR) { - dst_line[dx] = outside_color; - } else { - const int src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); - const int src_y = qBound(0, (src_top + src_bottom) >> 1, sh - 1); - dst_line[dx] = src_data[src_y * src_stride + src_x]; - } - continue; - } - - /* - * Note that (intval / 32) is not the same as (intval >> 5). - * The former rounds towards zero, while the latter rounds towards - * negative infinity. - * Likewise, (intval % 32) is not the same as (intval & 31). - * The following expression: - * top_fraction = 32 - (src32_top & 31); - * works correctly with both positive and negative src32_top. - */ - - unsigned background_area = 0; - - if (src_top < 0) { - const unsigned top_fraction = 32 - (src32_top & 31); - const unsigned hor_fraction = src32_right - src32_left; - background_area += top_fraction * hor_fraction; - const unsigned full_pixels_ver = -1 - src_top; - background_area += hor_fraction * (full_pixels_ver << 5); - src_top = 0; - src32_top = 0; - } - if (src_bottom >= sh) { - const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); - const unsigned hor_fraction = src32_right - src32_left; - background_area += bottom_fraction * hor_fraction; - const unsigned full_pixels_ver = src_bottom - sh; - background_area += hor_fraction * (full_pixels_ver << 5); - src_bottom = sh - 1; // inclusive - src32_bottom = sh << 5; // exclusive - } - if (src_left < 0) { - const unsigned left_fraction = 32 - (src32_left & 31); - const unsigned vert_fraction = src32_bottom - src32_top; - background_area += left_fraction * vert_fraction; - const unsigned full_pixels_hor = -1 - src_left; - background_area += vert_fraction * (full_pixels_hor << 5); - src_left = 0; - src32_left = 0; - } - if (src_right >= sw) { - const unsigned right_fraction = src32_right - (src_right << 5); - const unsigned vert_fraction = src32_bottom - src32_top; - background_area += right_fraction * vert_fraction; - const unsigned full_pixels_hor = src_right - sw; - background_area += vert_fraction * (full_pixels_hor << 5); - src_right = sw - 1; // inclusive - src32_right = sw << 5; // exclusive - } - assert(src_bottom >= src_top); - assert(src_right >= src_left); - - Mixer mixer; - if (outside_flags & OutsidePixels::WEAK) { - background_area = 0; - } else { - assert(outside_flags & OutsidePixels::COLOR); - mixer.add(outside_color, background_area); - } - - const unsigned left_fraction = 32 - (src32_left & 31); - const unsigned top_fraction = 32 - (src32_top & 31); - const unsigned right_fraction = src32_right - (src_right << 5); - const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); - - assert(left_fraction + right_fraction + (src_right - src_left - 1) * 32 - == static_cast(src32_right - src32_left)); - assert(top_fraction + bottom_fraction + (src_bottom - src_top - 1) * 32 - == static_cast(src32_bottom - src32_top)); - - const unsigned src_area = (src32_bottom - src32_top) * (src32_right - src32_left); - if (src_area == 0) { - if ((outside_flags & OutsidePixels::COLOR)) { - dst_line[dx] = outside_color; - } else { - const int src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); - const int src_y = qBound(0, (src_top + src_bottom) >> 1, sh - 1); - dst_line[dx] = src_data[src_y * src_stride + src_x]; - } - continue; - } - - const StorageUnit* src_line = &src_data[src_top * src_stride]; - - if (src_top == src_bottom) { - if (src_left == src_right) { - // dst pixel maps to a single src pixel - const StorageUnit c = src_line[src_left]; - if (background_area == 0) { - // common case optimization - dst_line[dx] = c; - continue; - } - mixer.add(c, src_area); - } else { - // dst pixel maps to a horizontal line of src pixels - const unsigned vert_fraction = src32_bottom - src32_top; - const unsigned left_area = vert_fraction * left_fraction; - const unsigned middle_area = vert_fraction << 5; - const unsigned right_area = vert_fraction * right_fraction; - - mixer.add(src_line[src_left], left_area); - - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], middle_area); - } - - mixer.add(src_line[src_right], right_area); - } - } else if (src_left == src_right) { - // dst pixel maps to a vertical line of src pixels - const unsigned hor_fraction = src32_right - src32_left; - const unsigned top_area = hor_fraction * top_fraction; - const unsigned middle_area = hor_fraction << 5; - const unsigned bottom_area = hor_fraction * bottom_fraction; - - src_line += src_left; - mixer.add(*src_line, top_area); - - src_line += src_stride; - - for (int sy = src_top + 1; sy < src_bottom; ++sy) { - mixer.add(*src_line, middle_area); - src_line += src_stride; - } - - mixer.add(*src_line, bottom_area); - } else { - // dst pixel maps to a block of src pixels - const unsigned top_area = top_fraction << 5; - const unsigned bottom_area = bottom_fraction << 5; - const unsigned left_area = left_fraction << 5; - const unsigned right_area = right_fraction << 5; - const unsigned topleft_area = top_fraction * left_fraction; - const unsigned topright_area = top_fraction * right_fraction; - const unsigned bottomleft_area = bottom_fraction * left_fraction; - const unsigned bottomright_area = bottom_fraction * right_fraction; - - // process the top-left corner - mixer.add(src_line[src_left], topleft_area); - - // process the top line (without corners) - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], top_area); - } - - // process the top-right corner - mixer.add(src_line[src_right], topright_area); - - src_line += src_stride; - // process middle lines - for (int sy = src_top + 1; sy < src_bottom; ++sy) { - mixer.add(src_line[src_left], left_area); - - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], 32 * 32); - } - - mixer.add(src_line[src_right], right_area); - - src_line += src_stride; - } - - // process bottom-left corner - mixer.add(src_line[src_left], bottomleft_area); - - // process the bottom line (without corners) - for (int sx = src_left + 1; sx < src_right; ++sx) { - mixer.add(src_line[sx], bottom_area); - } - - // process the bottom-right corner - mixer.add(src_line[src_right], bottomright_area); - } - - dst_line[dx] = mixer.mix(src_area + background_area); + const int sw = src_size.width(); + const int sh = src_size.height(); + const int dw = dst_rect.width(); + const int dh = dst_rect.height(); + + StorageUnit* dst_line = dst_data; + + QTransform inv_xform; + inv_xform.translate(dst_rect.x(), dst_rect.y()); + inv_xform *= xform.inverted(); + inv_xform *= QTransform().scale(32.0, 32.0); + + // sx32 = dx*inv_xform.m11() + dy*inv_xform.m21() + inv_xform.dx(); + // sy32 = dy*inv_xform.m22() + dx*inv_xform.m12() + inv_xform.dy(); + + const QSizeF src32_unit_size(calcSrcUnitSize(inv_xform, min_mapping_area)); + const int src32_unit_w = std::max(1, qRound(src32_unit_size.width())); + const int src32_unit_h = std::max(1, qRound(src32_unit_size.height())); + + for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { + const double f_dy_center = dy + 0.5; + const double f_sx32_base = f_dy_center * inv_xform.m21() + inv_xform.dx(); + const double f_sy32_base = f_dy_center * inv_xform.m22() + inv_xform.dy(); + + for (int dx = 0; dx < dw; ++dx) { + const double f_dx_center = dx + 0.5; + const double f_sx32_center = f_sx32_base + f_dx_center * inv_xform.m11(); + const double f_sy32_center = f_sy32_base + f_dx_center * inv_xform.m12(); + int src32_left = (int) f_sx32_center - (src32_unit_w >> 1); + int src32_top = (int) f_sy32_center - (src32_unit_h >> 1); + int src32_right = src32_left + src32_unit_w; + int src32_bottom = src32_top + src32_unit_h; + int src_left = src32_left >> 5; + int src_right = (src32_right - 1) >> 5; // inclusive + int src_top = src32_top >> 5; + int src_bottom = (src32_bottom - 1) >> 5; // inclusive + assert(src_bottom >= src_top); + assert(src_right >= src_left); + + if ((src_bottom < 0) || (src_right < 0) || (src_left >= sw) || (src_top >= sh)) { + // Completely outside of src image. + if (outside_flags & OutsidePixels::COLOR) { + dst_line[dx] = outside_color; + } else { + const int src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); + const int src_y = qBound(0, (src_top + src_bottom) >> 1, sh - 1); + dst_line[dx] = src_data[src_y * src_stride + src_x]; + } + continue; + } + + /* + * Note that (intval / 32) is not the same as (intval >> 5). + * The former rounds towards zero, while the latter rounds towards + * negative infinity. + * Likewise, (intval % 32) is not the same as (intval & 31). + * The following expression: + * top_fraction = 32 - (src32_top & 31); + * works correctly with both positive and negative src32_top. + */ + + unsigned background_area = 0; + + if (src_top < 0) { + const unsigned top_fraction = 32 - (src32_top & 31); + const unsigned hor_fraction = src32_right - src32_left; + background_area += top_fraction * hor_fraction; + const unsigned full_pixels_ver = -1 - src_top; + background_area += hor_fraction * (full_pixels_ver << 5); + src_top = 0; + src32_top = 0; + } + if (src_bottom >= sh) { + const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); + const unsigned hor_fraction = src32_right - src32_left; + background_area += bottom_fraction * hor_fraction; + const unsigned full_pixels_ver = src_bottom - sh; + background_area += hor_fraction * (full_pixels_ver << 5); + src_bottom = sh - 1; // inclusive + src32_bottom = sh << 5; // exclusive + } + if (src_left < 0) { + const unsigned left_fraction = 32 - (src32_left & 31); + const unsigned vert_fraction = src32_bottom - src32_top; + background_area += left_fraction * vert_fraction; + const unsigned full_pixels_hor = -1 - src_left; + background_area += vert_fraction * (full_pixels_hor << 5); + src_left = 0; + src32_left = 0; + } + if (src_right >= sw) { + const unsigned right_fraction = src32_right - (src_right << 5); + const unsigned vert_fraction = src32_bottom - src32_top; + background_area += right_fraction * vert_fraction; + const unsigned full_pixels_hor = src_right - sw; + background_area += vert_fraction * (full_pixels_hor << 5); + src_right = sw - 1; // inclusive + src32_right = sw << 5; // exclusive + } + assert(src_bottom >= src_top); + assert(src_right >= src_left); + + Mixer mixer; + if (outside_flags & OutsidePixels::WEAK) { + background_area = 0; + } else { + assert(outside_flags & OutsidePixels::COLOR); + mixer.add(outside_color, background_area); + } + + const unsigned left_fraction = 32 - (src32_left & 31); + const unsigned top_fraction = 32 - (src32_top & 31); + const unsigned right_fraction = src32_right - (src_right << 5); + const unsigned bottom_fraction = src32_bottom - (src_bottom << 5); + + assert(left_fraction + right_fraction + (src_right - src_left - 1) * 32 + == static_cast(src32_right - src32_left)); + assert(top_fraction + bottom_fraction + (src_bottom - src_top - 1) * 32 + == static_cast(src32_bottom - src32_top)); + + const unsigned src_area = (src32_bottom - src32_top) * (src32_right - src32_left); + if (src_area == 0) { + if ((outside_flags & OutsidePixels::COLOR)) { + dst_line[dx] = outside_color; + } else { + const int src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); + const int src_y = qBound(0, (src_top + src_bottom) >> 1, sh - 1); + dst_line[dx] = src_data[src_y * src_stride + src_x]; + } + continue; + } + + const StorageUnit* src_line = &src_data[src_top * src_stride]; + + if (src_top == src_bottom) { + if (src_left == src_right) { + // dst pixel maps to a single src pixel + const StorageUnit c = src_line[src_left]; + if (background_area == 0) { + // common case optimization + dst_line[dx] = c; + continue; + } + mixer.add(c, src_area); + } else { + // dst pixel maps to a horizontal line of src pixels + const unsigned vert_fraction = src32_bottom - src32_top; + const unsigned left_area = vert_fraction * left_fraction; + const unsigned middle_area = vert_fraction << 5; + const unsigned right_area = vert_fraction * right_fraction; + + mixer.add(src_line[src_left], left_area); + + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], middle_area); + } + + mixer.add(src_line[src_right], right_area); + } + } else if (src_left == src_right) { + // dst pixel maps to a vertical line of src pixels + const unsigned hor_fraction = src32_right - src32_left; + const unsigned top_area = hor_fraction * top_fraction; + const unsigned middle_area = hor_fraction << 5; + const unsigned bottom_area = hor_fraction * bottom_fraction; + + src_line += src_left; + mixer.add(*src_line, top_area); + + src_line += src_stride; + + for (int sy = src_top + 1; sy < src_bottom; ++sy) { + mixer.add(*src_line, middle_area); + src_line += src_stride; + } + + mixer.add(*src_line, bottom_area); + } else { + // dst pixel maps to a block of src pixels + const unsigned top_area = top_fraction << 5; + const unsigned bottom_area = bottom_fraction << 5; + const unsigned left_area = left_fraction << 5; + const unsigned right_area = right_fraction << 5; + const unsigned topleft_area = top_fraction * left_fraction; + const unsigned topright_area = top_fraction * right_fraction; + const unsigned bottomleft_area = bottom_fraction * left_fraction; + const unsigned bottomright_area = bottom_fraction * right_fraction; + + // process the top-left corner + mixer.add(src_line[src_left], topleft_area); + + // process the top line (without corners) + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], top_area); + } + + // process the top-right corner + mixer.add(src_line[src_right], topright_area); + + src_line += src_stride; + // process middle lines + for (int sy = src_top + 1; sy < src_bottom; ++sy) { + mixer.add(src_line[src_left], left_area); + + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], 32 * 32); + } + + mixer.add(src_line[src_right], right_area); + + src_line += src_stride; + } + + // process bottom-left corner + mixer.add(src_line[src_left], bottomleft_area); + + // process the bottom line (without corners) + for (int sx = src_left + 1; sx < src_right; ++sx) { + mixer.add(src_line[sx], bottom_area); } + + // process the bottom-right corner + mixer.add(src_line[src_right], bottomright_area); + } + + dst_line[dx] = mixer.mix(src_area + background_area); } + } } // transformGeneric void fixDpiInPlace(QImage& image, const QTransform& xform) { - if (xform.isScaling()) { - QRect dpi_rect(QPoint(0, 0), QSize(image.dotsPerMeterX(), image.dotsPerMeterY())); - xform.mapRect(dpi_rect); - image.setDotsPerMeterX(dpi_rect.width()); - image.setDotsPerMeterX(dpi_rect.width()); - } + if (xform.isScaling()) { + QRect dpi_rect(QPoint(0, 0), QSize(image.dotsPerMeterX(), image.dotsPerMeterY())); + xform.mapRect(dpi_rect); + image.setDotsPerMeterX(dpi_rect.width()); + image.setDotsPerMeterX(dpi_rect.width()); + } } void fixDpiInPlace(GrayImage& image, const QTransform& xform) { - if (xform.isScaling()) { - QRect dpi_rect(QPoint(0, 0), QSize(image.dotsPerMeterX(), image.dotsPerMeterY())); - xform.mapRect(dpi_rect); - image.setDotsPerMeterX(dpi_rect.width()); - image.setDotsPerMeterX(dpi_rect.width()); - } + if (xform.isScaling()) { + QRect dpi_rect(QPoint(0, 0), QSize(image.dotsPerMeterX(), image.dotsPerMeterY())); + xform.mapRect(dpi_rect); + image.setDotsPerMeterX(dpi_rect.width()); + image.setDotsPerMeterX(dpi_rect.width()); + } } } // namespace @@ -322,73 +318,72 @@ QImage transform(const QImage& src, const QRect& dst_rect, const OutsidePixels outside_pixels, const QSizeF& min_mapping_area) { - if (src.isNull() || dst_rect.isEmpty()) { - return QImage(); - } - - if (!xform.isAffine()) { - throw std::invalid_argument("transform: only affine transformations are supported"); - } - - if (!dst_rect.isValid()) { - throw std::invalid_argument("transform: dst_rect is invalid"); - } + if (src.isNull() || dst_rect.isEmpty()) { + return QImage(); + } + + if (!xform.isAffine()) { + throw std::invalid_argument("transform: only affine transformations are supported"); + } + + if (!dst_rect.isValid()) { + throw std::invalid_argument("transform: dst_rect is invalid"); + } + + auto is_opaque_gray + = [](QRgb rgba) { return qAlpha(rgba) == 0xff && qRed(rgba) == qBlue(rgba) && qRed(rgba) == qGreen(rgba); }; + switch (src.format()) { + case QImage::Format_Invalid: + return QImage(); + case QImage::Format_Indexed8: + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + if (src.allGray() && is_opaque_gray(outside_pixels.rgba())) { + // The palette of src may be non-standard, so we create a GrayImage, + // which is guaranteed to have a standard palette. + GrayImage gray_src(src); + GrayImage gray_dst(dst_rect.size()); + typedef uint32_t AccumType; + transformGeneric>( + gray_src.data(), gray_src.stride(), src.size(), gray_dst.data(), gray_dst.stride(), xform, dst_rect, + outside_pixels.grayLevel(), outside_pixels.flags(), min_mapping_area); - auto is_opaque_gray - = [](QRgb rgba) { return qAlpha(rgba) == 0xff && qRed(rgba) == qBlue(rgba) && qRed(rgba) == qGreen(rgba); }; - switch (src.format()) { - case QImage::Format_Invalid: - return QImage(); - case QImage::Format_Indexed8: - case QImage::Format_Mono: - case QImage::Format_MonoLSB: - if (src.allGray() && is_opaque_gray(outside_pixels.rgba())) { - // The palette of src may be non-standard, so we create a GrayImage, - // which is guaranteed to have a standard palette. - GrayImage gray_src(src); - GrayImage gray_dst(dst_rect.size()); - typedef uint32_t AccumType; - transformGeneric>( - gray_src.data(), gray_src.stride(), src.size(), gray_dst.data(), gray_dst.stride(), xform, - dst_rect, outside_pixels.grayLevel(), outside_pixels.flags(), min_mapping_area); - - fixDpiInPlace(gray_dst, xform); - - return gray_dst; - } - default: - if (!src.hasAlphaChannel() && (qAlpha(outside_pixels.rgba()) == 0xff)) { - const QImage src_rgb32(src.convertToFormat(QImage::Format_RGB32)); - badAllocIfNull(src_rgb32); - QImage dst(dst_rect.size(), QImage::Format_RGB32); - badAllocIfNull(dst); - - typedef uint32_t AccumType; - transformGeneric>( - (const uint32_t*) src_rgb32.bits(), src_rgb32.bytesPerLine() / 4, src_rgb32.size(), - (uint32_t*) dst.bits(), dst.bytesPerLine() / 4, xform, dst_rect, outside_pixels.rgb(), - outside_pixels.flags(), min_mapping_area); - - fixDpiInPlace(dst, xform); - - return dst; - } else { - const QImage src_argb32(src.convertToFormat(QImage::Format_ARGB32)); - badAllocIfNull(src_argb32); - QImage dst(dst_rect.size(), QImage::Format_ARGB32); - badAllocIfNull(dst); - - typedef float AccumType; - transformGeneric>( - (const uint32_t*) src_argb32.bits(), src_argb32.bytesPerLine() / 4, src_argb32.size(), - (uint32_t*) dst.bits(), dst.bytesPerLine() / 4, xform, dst_rect, outside_pixels.rgba(), - outside_pixels.flags(), min_mapping_area); - - fixDpiInPlace(dst, xform); - - return dst; - } - } + fixDpiInPlace(gray_dst, xform); + + return gray_dst; + } + default: + if (!src.hasAlphaChannel() && (qAlpha(outside_pixels.rgba()) == 0xff)) { + const QImage src_rgb32(src.convertToFormat(QImage::Format_RGB32)); + badAllocIfNull(src_rgb32); + QImage dst(dst_rect.size(), QImage::Format_RGB32); + badAllocIfNull(dst); + + typedef uint32_t AccumType; + transformGeneric>( + (const uint32_t*) src_rgb32.bits(), src_rgb32.bytesPerLine() / 4, src_rgb32.size(), (uint32_t*) dst.bits(), + dst.bytesPerLine() / 4, xform, dst_rect, outside_pixels.rgb(), outside_pixels.flags(), min_mapping_area); + + fixDpiInPlace(dst, xform); + + return dst; + } else { + const QImage src_argb32(src.convertToFormat(QImage::Format_ARGB32)); + badAllocIfNull(src_argb32); + QImage dst(dst_rect.size(), QImage::Format_ARGB32); + badAllocIfNull(dst); + + typedef float AccumType; + transformGeneric>( + (const uint32_t*) src_argb32.bits(), src_argb32.bytesPerLine() / 4, src_argb32.size(), + (uint32_t*) dst.bits(), dst.bytesPerLine() / 4, xform, dst_rect, outside_pixels.rgba(), + outside_pixels.flags(), min_mapping_area); + + fixDpiInPlace(dst, xform); + + return dst; + } + } } // transform GrayImage transformToGray(const QImage& src, @@ -396,28 +391,28 @@ GrayImage transformToGray(const QImage& src, const QRect& dst_rect, const OutsidePixels outside_pixels, const QSizeF& min_mapping_area) { - if (src.isNull() || dst_rect.isEmpty()) { - return GrayImage(); - } + if (src.isNull() || dst_rect.isEmpty()) { + return GrayImage(); + } - if (!xform.isAffine()) { - throw std::invalid_argument("transformToGray: only affine transformations are supported"); - } + if (!xform.isAffine()) { + throw std::invalid_argument("transformToGray: only affine transformations are supported"); + } - if (!dst_rect.isValid()) { - throw std::invalid_argument("transformToGray: dst_rect is invalid"); - } + if (!dst_rect.isValid()) { + throw std::invalid_argument("transformToGray: dst_rect is invalid"); + } - const GrayImage gray_src(src); - GrayImage dst(dst_rect.size()); + const GrayImage gray_src(src); + GrayImage dst(dst_rect.size()); - typedef unsigned AccumType; - transformGeneric>( - gray_src.data(), gray_src.stride(), gray_src.size(), dst.data(), dst.stride(), xform, dst_rect, - outside_pixels.grayLevel(), outside_pixels.flags(), min_mapping_area); + typedef unsigned AccumType; + transformGeneric>(gray_src.data(), gray_src.stride(), gray_src.size(), dst.data(), + dst.stride(), xform, dst_rect, outside_pixels.grayLevel(), + outside_pixels.flags(), min_mapping_area); - fixDpiInPlace(dst, xform); + fixDpiInPlace(dst, xform); - return dst; + return dst; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/Transform.h b/imageproc/Transform.h index 1d9c71f08..120a223e0 100644 --- a/imageproc/Transform.h +++ b/imageproc/Transform.h @@ -19,8 +19,8 @@ #ifndef IMAGEPROC_TRANSFORM_H_ #define IMAGEPROC_TRANSFORM_H_ -#include #include +#include #include class QImage; @@ -31,59 +31,44 @@ namespace imageproc { class GrayImage; class OutsidePixels { - // Member-wise copying is OK. -public: - enum Flags { COLOR = 1 << 0, NEAREST = 1 << 1, WEAK = 1 << 2 }; - - /** - * \brief Outside pixels are assumed to be of particular color. - * - * Outside pixels may be blended with inside pixels near the edges. - */ - static OutsidePixels assumeColor(const QColor& color) { - return OutsidePixels(COLOR, color.rgba()); - } - - /** - * \brief Outside pixels are assumed to be of particular color. - * - * Outside pixels won't participate in blending operations. - */ - static OutsidePixels assumeWeakColor(const QColor& color) { - return OutsidePixels(WEAK | COLOR, color.rgba()); - } - - /** - * \brief An outside pixel is assumed to be the same as the nearest inside pixel. - * - * Outside pixels won't participate in blending operations. - */ - static OutsidePixels assumeWeakNearest() { - return OutsidePixels(WEAK | NEAREST, 0xff000000); - } - - int flags() const { - return m_flags; - } - - QRgb rgba() const { - return m_rgba; - } - - QRgb rgb() const { - return m_rgba | 0xff000000; - } - - uint8_t grayLevel() const { - return static_cast(qGray(m_rgba)); - } - -private: - OutsidePixels(int flags, QRgb rgba) : m_flags(flags), m_rgba(rgba) { - } - - int m_flags; - QRgb m_rgba; + // Member-wise copying is OK. + public: + enum Flags { COLOR = 1 << 0, NEAREST = 1 << 1, WEAK = 1 << 2 }; + + /** + * \brief Outside pixels are assumed to be of particular color. + * + * Outside pixels may be blended with inside pixels near the edges. + */ + static OutsidePixels assumeColor(const QColor& color) { return OutsidePixels(COLOR, color.rgba()); } + + /** + * \brief Outside pixels are assumed to be of particular color. + * + * Outside pixels won't participate in blending operations. + */ + static OutsidePixels assumeWeakColor(const QColor& color) { return OutsidePixels(WEAK | COLOR, color.rgba()); } + + /** + * \brief An outside pixel is assumed to be the same as the nearest inside pixel. + * + * Outside pixels won't participate in blending operations. + */ + static OutsidePixels assumeWeakNearest() { return OutsidePixels(WEAK | NEAREST, 0xff000000); } + + int flags() const { return m_flags; } + + QRgb rgba() const { return m_rgba; } + + QRgb rgb() const { return m_rgba | 0xff000000; } + + uint8_t grayLevel() const { return static_cast(qGray(m_rgba)); } + + private: + OutsidePixels(int flags, QRgb rgba) : m_flags(flags), m_rgba(rgba) {} + + int m_flags; + QRgb m_rgba; }; diff --git a/imageproc/UpscaleIntegerTimes.cpp b/imageproc/UpscaleIntegerTimes.cpp index 74faa3303..328ccfe32 100644 --- a/imageproc/UpscaleIntegerTimes.cpp +++ b/imageproc/UpscaleIntegerTimes.cpp @@ -22,90 +22,90 @@ namespace imageproc { namespace { inline uint32_t multiplyBit(uint32_t bit, int times) { - return (uint32_t(0) - bit) >> (32 - times); + return (uint32_t(0) - bit) >> (32 - times); } void expandImpl(BinaryImage& dst, const BinaryImage& src, const int xscale, const int yscale) { - const int sw = src.width(); - const int sh = src.height(); - - const int src_wpl = src.wordsPerLine(); - const int dst_wpl = dst.wordsPerLine(); - - const uint32_t* src_line = src.data(); - uint32_t* dst_line = dst.data(); - - for (int sy = 0; sy < sh; ++sy, src_line += src_wpl) { - uint32_t dst_word = 0; - int dst_bits_remaining = 32; - int di = 0; - - for (int sx = 0; sx < sw; ++sx) { - const uint32_t src_word = src_line[sx >> 5]; - const int src_bit = 31 - (sx & 31); - const uint32_t bit = (src_word >> src_bit) & uint32_t(1); - int todo = xscale; - - while (dst_bits_remaining <= todo) { - dst_word |= multiplyBit(bit, dst_bits_remaining); - dst_line[di++] = dst_word; - todo -= dst_bits_remaining; - dst_bits_remaining = 32; - dst_word = 0; - } - if (todo > 0) { - dst_bits_remaining -= todo; - dst_word |= multiplyBit(bit, todo) << dst_bits_remaining; - } - } - - if (dst_bits_remaining != 32) { - dst_line[di] = dst_word; - } - - const uint32_t* first_dst_line = dst_line; - dst_line += dst_wpl; - for (int line = 1; line < yscale; ++line, dst_line += dst_wpl) { - memcpy(dst_line, first_dst_line, dst_wpl * 4); - } + const int sw = src.width(); + const int sh = src.height(); + + const int src_wpl = src.wordsPerLine(); + const int dst_wpl = dst.wordsPerLine(); + + const uint32_t* src_line = src.data(); + uint32_t* dst_line = dst.data(); + + for (int sy = 0; sy < sh; ++sy, src_line += src_wpl) { + uint32_t dst_word = 0; + int dst_bits_remaining = 32; + int di = 0; + + for (int sx = 0; sx < sw; ++sx) { + const uint32_t src_word = src_line[sx >> 5]; + const int src_bit = 31 - (sx & 31); + const uint32_t bit = (src_word >> src_bit) & uint32_t(1); + int todo = xscale; + + while (dst_bits_remaining <= todo) { + dst_word |= multiplyBit(bit, dst_bits_remaining); + dst_line[di++] = dst_word; + todo -= dst_bits_remaining; + dst_bits_remaining = 32; + dst_word = 0; + } + if (todo > 0) { + dst_bits_remaining -= todo; + dst_word |= multiplyBit(bit, todo) << dst_bits_remaining; + } } + + if (dst_bits_remaining != 32) { + dst_line[di] = dst_word; + } + + const uint32_t* first_dst_line = dst_line; + dst_line += dst_wpl; + for (int line = 1; line < yscale; ++line, dst_line += dst_wpl) { + memcpy(dst_line, first_dst_line, dst_wpl * 4); + } + } } // expandImpl } // namespace BinaryImage upscaleIntegerTimes(const BinaryImage& src, const int xscale, const int yscale) { - if (src.isNull() || ((xscale == 1) && (yscale == 1))) { - return src; - } + if (src.isNull() || ((xscale == 1) && (yscale == 1))) { + return src; + } - if ((xscale < 0) || (yscale < 0)) { - throw std::runtime_error("upscaleIntegerTimes: scaling factors can't be negative"); - } + if ((xscale < 0) || (yscale < 0)) { + throw std::runtime_error("upscaleIntegerTimes: scaling factors can't be negative"); + } - BinaryImage dst(src.width() * xscale, src.height() * yscale); - expandImpl(dst, src, xscale, yscale); + BinaryImage dst(src.width() * xscale, src.height() * yscale); + expandImpl(dst, src, xscale, yscale); - return dst; + return dst; } BinaryImage upscaleIntegerTimes(const BinaryImage& src, const QSize& dst_size, const BWColor padding) { - if (src.isNull()) { - BinaryImage dst(dst_size); - dst.fill(padding); + if (src.isNull()) { + BinaryImage dst(dst_size); + dst.fill(padding); - return dst; - } + return dst; + } - const int xscale = dst_size.width() / src.width(); - const int yscale = dst_size.height() / src.height(); - if ((xscale < 1) || (yscale < 1)) { - throw std::invalid_argument("upscaleIntegerTimes: bad dst_size"); - } + const int xscale = dst_size.width() / src.width(); + const int yscale = dst_size.height() / src.height(); + if ((xscale < 1) || (yscale < 1)) { + throw std::invalid_argument("upscaleIntegerTimes: bad dst_size"); + } - BinaryImage dst(dst_size); - expandImpl(dst, src, xscale, yscale); - const QRect rect(0, 0, src.width() * xscale, src.height() * yscale); - dst.fillExcept(rect, padding); + BinaryImage dst(dst_size); + expandImpl(dst, src, xscale, yscale); + const QRect rect(0, 0, src.width() * xscale, src.height() * yscale); + dst.fillExcept(rect, padding); - return dst; + return dst; } } // namespace imageproc \ No newline at end of file diff --git a/imageproc/tests/CMakeLists.txt b/imageproc/tests/CMakeLists.txt index be4a71f0f..e467e09b0 100644 --- a/imageproc/tests/CMakeLists.txt +++ b/imageproc/tests/CMakeLists.txt @@ -1,43 +1,43 @@ -INCLUDE_DIRECTORIES(BEFORE ..) +include_directories(BEFORE ..) -SET( - sources - main.cpp - TestBinaryImage.cpp TestReduceThreshold.cpp - TestSlicedHistogram.cpp - TestConnCompEraser.cpp TestConnCompEraserExt.cpp - TestGrayscale.cpp - TestRasterOp.cpp TestShear.cpp - TestOrthogonalRotation.cpp - TestSkewFinder.cpp - TestScale.cpp - TestTransform.cpp - TestMorphology.cpp - TestBinarize.cpp - TestPolygonRasterizer.cpp - TestSeedFill.cpp - TestSEDM.cpp - TestRastLineFinder.cpp - Utils.cpp Utils.h +set( + sources + main.cpp + TestBinaryImage.cpp TestReduceThreshold.cpp + TestSlicedHistogram.cpp + TestConnCompEraser.cpp TestConnCompEraserExt.cpp + TestGrayscale.cpp + TestRasterOp.cpp TestShear.cpp + TestOrthogonalRotation.cpp + TestSkewFinder.cpp + TestScale.cpp + TestTransform.cpp + TestMorphology.cpp + TestBinarize.cpp + TestPolygonRasterizer.cpp + TestSeedFill.cpp + TestSEDM.cpp + TestRastLineFinder.cpp + Utils.cpp Utils.h ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) -SET( - libs - imageproc math foundation Qt5::Widgets Qt5::Xml - ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} - ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} +set( + libs + imageproc math foundation Qt5::Widgets Qt5::Xml + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} ) -REMOVE_DEFINITIONS(-DBUILDING_IMAGEPROC) -ADD_EXECUTABLE(imageproc_tests ${sources}) -TARGET_LINK_LIBRARIES(imageproc_tests ${libs}) +remove_definitions(-DBUILDING_IMAGEPROC) +add_executable(imageproc_tests ${sources}) +target_link_libraries(imageproc_tests ${libs}) # We want the executable located where we copy all the DLLs. -SET_TARGET_PROPERTIES( - imageproc_tests PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +set_target_properties( + imageproc_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -ADD_TEST(NAME imageproc_tests COMMAND imageproc_tests --log_level=message) +add_test(NAME imageproc_tests COMMAND imageproc_tests --log_level=message) diff --git a/imageproc/tests/TestBinarize.cpp b/imageproc/tests/TestBinarize.cpp index 6e64b6a8a..ecbb6e34b 100644 --- a/imageproc/tests/TestBinarize.cpp +++ b/imageproc/tests/TestBinarize.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ -#include "Binarize.h" -#include "BinaryImage.h" -#include "Utils.h" #include #include #include +#include "Binarize.h" +#include "BinaryImage.h" +#include "Utils.h" namespace imageproc { namespace tests { diff --git a/imageproc/tests/TestBinaryImage.cpp b/imageproc/tests/TestBinaryImage.cpp index 71b63d67d..99a2b2bd0 100644 --- a/imageproc/tests/TestBinaryImage.cpp +++ b/imageproc/tests/TestBinaryImage.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ -#include "BinaryImage.h" -#include "BWColor.h" -#include "Utils.h" #include #include #include +#include "BWColor.h" +#include "BinaryImage.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -30,115 +30,115 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(BinaryImageTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - BOOST_CHECK(BinaryImage().toQImage() == QImage()); - BOOST_CHECK(BinaryImage(QImage()).toQImage() == QImage()); + BOOST_CHECK(BinaryImage().toQImage() == QImage()); + BOOST_CHECK(BinaryImage(QImage()).toQImage() == QImage()); } BOOST_AUTO_TEST_CASE(test_from_to_qimage) { - const int w = 50; - const int h = 64; - QImage qimg_argb32(w, h, QImage::Format_ARGB32); - QImage qimg_mono(w, h, QImage::Format_Mono); - qimg_mono.setColorCount(2); - qimg_mono.setColor(0, 0xffffffff); - qimg_mono.setColor(1, 0xff000000); - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - const int rnd = rand() & 1; - qimg_argb32.setPixel(x, y, rnd ? 0x66888888 : 0x66777777); - qimg_mono.setPixel(x, y, rnd ? 0 : 1); - } + const int w = 50; + const int h = 64; + QImage qimg_argb32(w, h, QImage::Format_ARGB32); + QImage qimg_mono(w, h, QImage::Format_Mono); + qimg_mono.setColorCount(2); + qimg_mono.setColor(0, 0xffffffff); + qimg_mono.setColor(1, 0xff000000); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int rnd = rand() & 1; + qimg_argb32.setPixel(x, y, rnd ? 0x66888888 : 0x66777777); + qimg_mono.setPixel(x, y, rnd ? 0 : 1); } - - QImage qimg_mono_lsb(qimg_mono.convertToFormat(QImage::Format_MonoLSB)); - QImage qimg_rgb32(qimg_argb32.convertToFormat(QImage::Format_RGB32)); - QImage qimg_argb32_pm(qimg_argb32.convertToFormat(QImage::Format_ARGB32_Premultiplied)); - QImage qimg_rgb16(qimg_rgb32.convertToFormat(QImage::Format_RGB16)); - QImage qimg_indexed8(qimg_rgb32.convertToFormat(QImage::Format_Indexed8)); - - BOOST_REQUIRE(BinaryImage(qimg_mono).toQImage() == qimg_mono); - BOOST_CHECK(BinaryImage(qimg_mono_lsb).toQImage() == qimg_mono); - BOOST_CHECK(BinaryImage(qimg_argb32).toQImage() == qimg_mono); - BOOST_CHECK(BinaryImage(qimg_rgb32).toQImage() == qimg_mono); - BOOST_CHECK(BinaryImage(qimg_argb32_pm).toQImage() == qimg_mono); - BOOST_CHECK(BinaryImage(qimg_indexed8).toQImage() == qimg_mono); - - // A bug in Qt prevents this from working. - // BOOST_CHECK(BinaryImage(qimg_rgb16, 0x80).toQImage() == qimg_mono); + } + + QImage qimg_mono_lsb(qimg_mono.convertToFormat(QImage::Format_MonoLSB)); + QImage qimg_rgb32(qimg_argb32.convertToFormat(QImage::Format_RGB32)); + QImage qimg_argb32_pm(qimg_argb32.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + QImage qimg_rgb16(qimg_rgb32.convertToFormat(QImage::Format_RGB16)); + QImage qimg_indexed8(qimg_rgb32.convertToFormat(QImage::Format_Indexed8)); + + BOOST_REQUIRE(BinaryImage(qimg_mono).toQImage() == qimg_mono); + BOOST_CHECK(BinaryImage(qimg_mono_lsb).toQImage() == qimg_mono); + BOOST_CHECK(BinaryImage(qimg_argb32).toQImage() == qimg_mono); + BOOST_CHECK(BinaryImage(qimg_rgb32).toQImage() == qimg_mono); + BOOST_CHECK(BinaryImage(qimg_argb32_pm).toQImage() == qimg_mono); + BOOST_CHECK(BinaryImage(qimg_indexed8).toQImage() == qimg_mono); + + // A bug in Qt prevents this from working. + // BOOST_CHECK(BinaryImage(qimg_rgb16, 0x80).toQImage() == qimg_mono); } BOOST_AUTO_TEST_CASE(test_full_fill) { - BinaryImage white(100, 100); - white.fill(WHITE); + BinaryImage white(100, 100); + white.fill(WHITE); - QImage q_white(100, 100, QImage::Format_Mono); - q_white.fill(1); + QImage q_white(100, 100, QImage::Format_Mono); + q_white.fill(1); - BOOST_REQUIRE(BinaryImage(q_white) == white); + BOOST_REQUIRE(BinaryImage(q_white) == white); - BinaryImage black(30, 30); - black.fill(BLACK); + BinaryImage black(30, 30); + black.fill(BLACK); - QImage q_black(30, 30, QImage::Format_Mono); - q_black.fill(0); + QImage q_black(30, 30, QImage::Format_Mono); + q_black.fill(0); - BOOST_REQUIRE(BinaryImage(q_black) == black); + BOOST_REQUIRE(BinaryImage(q_black) == black); } BOOST_AUTO_TEST_CASE(test_partial_fill_small) { - QImage q_image(randomMonoQImage(100, 100)); - - const QRect rect(80, 80, 20, 20); - BinaryImage image(q_image); - image.fill(rect, WHITE); - QImage white_rect(rect.width(), rect.height(), QImage::Format_Mono); - white_rect.setColorCount(2); - white_rect.setColor(0, 0xffffffff); - white_rect.setColor(1, 0xff000000); - white_rect.fill(0); - BOOST_REQUIRE(image.toQImage().copy(rect) == white_rect); - BOOST_CHECK(surroundingsIntact(image.toQImage(), q_image, rect)); + QImage q_image(randomMonoQImage(100, 100)); + + const QRect rect(80, 80, 20, 20); + BinaryImage image(q_image); + image.fill(rect, WHITE); + QImage white_rect(rect.width(), rect.height(), QImage::Format_Mono); + white_rect.setColorCount(2); + white_rect.setColor(0, 0xffffffff); + white_rect.setColor(1, 0xff000000); + white_rect.fill(0); + BOOST_REQUIRE(image.toQImage().copy(rect) == white_rect); + BOOST_CHECK(surroundingsIntact(image.toQImage(), q_image, rect)); } BOOST_AUTO_TEST_CASE(test_partial_fill_large) { - QImage q_image(randomMonoQImage(100, 100)); - - const QRect rect(20, 20, 79, 79); - BinaryImage image(q_image); - image.fill(rect, WHITE); - QImage white_rect(rect.width(), rect.height(), QImage::Format_Mono); - white_rect.setColorCount(2); - white_rect.setColor(0, 0xffffffff); - white_rect.setColor(1, 0xff000000); - white_rect.fill(0); - BOOST_REQUIRE(image.toQImage().copy(rect) == white_rect); - BOOST_CHECK(surroundingsIntact(image.toQImage(), q_image, rect)); + QImage q_image(randomMonoQImage(100, 100)); + + const QRect rect(20, 20, 79, 79); + BinaryImage image(q_image); + image.fill(rect, WHITE); + QImage white_rect(rect.width(), rect.height(), QImage::Format_Mono); + white_rect.setColorCount(2); + white_rect.setColor(0, 0xffffffff); + white_rect.setColor(1, 0xff000000); + white_rect.fill(0); + BOOST_REQUIRE(image.toQImage().copy(rect) == white_rect); + BOOST_CHECK(surroundingsIntact(image.toQImage(), q_image, rect)); } BOOST_AUTO_TEST_CASE(test_fill_except) { - QImage q_image(randomMonoQImage(100, 100)); + QImage q_image(randomMonoQImage(100, 100)); - const QRect rect(20, 20, 79, 79); - BinaryImage image(q_image); - image.fillExcept(rect, BLACK); + const QRect rect(20, 20, 79, 79); + BinaryImage image(q_image); + image.fillExcept(rect, BLACK); - QImage black_image(q_image.width(), q_image.height(), QImage::Format_Mono); - black_image.setColorCount(2); - black_image.setColor(0, 0xffffffff); - black_image.setColor(1, 0xff000000); - black_image.fill(1); + QImage black_image(q_image.width(), q_image.height(), QImage::Format_Mono); + black_image.setColorCount(2); + black_image.setColor(0, 0xffffffff); + black_image.setColor(1, 0xff000000); + black_image.fill(1); - BOOST_REQUIRE(image.toQImage().copy(rect) == q_image.copy(rect)); - BOOST_CHECK(surroundingsIntact(image.toQImage(), black_image, rect)); + BOOST_REQUIRE(image.toQImage().copy(rect) == q_image.copy(rect)); + BOOST_CHECK(surroundingsIntact(image.toQImage(), black_image, rect)); } BOOST_AUTO_TEST_CASE(test_content_bounding_box4) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] + = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 8)); - BOOST_CHECK(img.contentBoundingBox() == QRect(1, 1, 6, 6)); + const BinaryImage img(makeBinaryImage(inp, 9, 8)); + BOOST_CHECK(img.contentBoundingBox() == QRect(1, 1, 6, 6)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestConnCompEraser.cpp b/imageproc/tests/TestConnCompEraser.cpp index 2b663c171..324449436 100644 --- a/imageproc/tests/TestConnCompEraser.cpp +++ b/imageproc/tests/TestConnCompEraser.cpp @@ -16,14 +16,14 @@ along with this program. If not, see . */ -#include "ConnCompEraser.h" -#include "ConnComp.h" -#include "BinaryImage.h" -#include "Utils.h" #include -#include #include #include +#include +#include "BinaryImage.h" +#include "ConnComp.h" +#include "ConnCompEraser.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -32,51 +32,51 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(ConnCompEraserTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - ConnCompEraser eraser(BinaryImage(), CONN4); - BOOST_CHECK(eraser.nextConnComp().isNull()); + ConnCompEraser eraser(BinaryImage(), CONN4); + BOOST_CHECK(eraser.nextConnComp().isNull()); } BOOST_AUTO_TEST_CASE(test_small_image) { - static const int inp[] = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, - 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; + static const int inp[] + = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; - std::list c4r; - c4r.emplace_back(2, 0, 3, 6); - c4r.emplace_back(0, 3, 2, 1); - c4r.emplace_back(1, 5, 1, 1); - c4r.emplace_back(5, 2, 4, 3); - c4r.emplace_back(0, 6, 7, 2); - c4r.emplace_back(7, 6, 1, 1); + std::list c4r; + c4r.emplace_back(2, 0, 3, 6); + c4r.emplace_back(0, 3, 2, 1); + c4r.emplace_back(1, 5, 1, 1); + c4r.emplace_back(5, 2, 4, 3); + c4r.emplace_back(0, 6, 7, 2); + c4r.emplace_back(7, 6, 1, 1); - std::list c8r; - c8r.emplace_back(0, 0, 9, 6); - c8r.emplace_back(0, 6, 8, 2); + std::list c8r; + c8r.emplace_back(0, 0, 9, 6); + c8r.emplace_back(0, 6, 8, 2); - BinaryImage img(makeBinaryImage(inp, 9, 8)); + BinaryImage img(makeBinaryImage(inp, 9, 8)); - ConnComp cc; - ConnCompEraser eraser4(img, CONN4); - while (!(cc = eraser4.nextConnComp()).isNull()) { - const auto it(std::find(c4r.begin(), c4r.end(), cc.rect())); - if (it != c4r.end()) { - c4r.erase(it); - } else { - BOOST_ERROR("Incorrect 4-connected block found."); - } + ConnComp cc; + ConnCompEraser eraser4(img, CONN4); + while (!(cc = eraser4.nextConnComp()).isNull()) { + const auto it(std::find(c4r.begin(), c4r.end(), cc.rect())); + if (it != c4r.end()) { + c4r.erase(it); + } else { + BOOST_ERROR("Incorrect 4-connected block found."); } - BOOST_CHECK_MESSAGE(c4r.empty(), "Not all 4-connected blocks were found."); + } + BOOST_CHECK_MESSAGE(c4r.empty(), "Not all 4-connected blocks were found."); - ConnCompEraser eraser8(img, CONN8); - while (!(cc = eraser8.nextConnComp()).isNull()) { - const auto it(std::find(c8r.begin(), c8r.end(), cc.rect())); - if (it != c8r.end()) { - c8r.erase(it); - } else { - BOOST_ERROR("Incorrect 8-connected block found."); - } + ConnCompEraser eraser8(img, CONN8); + while (!(cc = eraser8.nextConnComp()).isNull()) { + const auto it(std::find(c8r.begin(), c8r.end(), cc.rect())); + if (it != c8r.end()) { + c8r.erase(it); + } else { + BOOST_ERROR("Incorrect 8-connected block found."); } - BOOST_CHECK_MESSAGE(c8r.empty(), "Not all 8-connected blocks were found."); + } + BOOST_CHECK_MESSAGE(c8r.empty(), "Not all 8-connected blocks were found."); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestConnCompEraserExt.cpp b/imageproc/tests/TestConnCompEraserExt.cpp index 65394676c..82923e8f9 100644 --- a/imageproc/tests/TestConnCompEraserExt.cpp +++ b/imageproc/tests/TestConnCompEraserExt.cpp @@ -16,16 +16,16 @@ along with this program. If not, see . */ -#include "ConnCompEraserExt.h" -#include "ConnComp.h" -#include "BinaryImage.h" -#include "BWColor.h" -#include "RasterOp.h" -#include "Utils.h" #include -#include #include #include +#include +#include "BWColor.h" +#include "BinaryImage.h" +#include "ConnComp.h" +#include "ConnCompEraserExt.h" +#include "RasterOp.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -34,107 +34,107 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(ConnCompEraserExtTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - ConnCompEraser eraser(BinaryImage(), CONN4); - BOOST_CHECK(eraser.nextConnComp().isNull()); + ConnCompEraser eraser(BinaryImage(), CONN4); + BOOST_CHECK(eraser.nextConnComp().isNull()); } static bool checkAlignedImage(const ConnCompEraserExt& eraser, const BinaryImage& nonaligned) { - const BinaryImage aligned(eraser.computeConnCompImageAligned()); - const int pad = aligned.width() - nonaligned.width(); - if (pad < 0) { - return false; - } - - BinaryImage test1(nonaligned); - BinaryImage empty1(test1.size()); - empty1.fill(WHITE); - rasterOp>(test1, test1.rect(), aligned, QPoint(pad, 0)); - if (test1 != empty1) { - return false; - } - - if (pad > 0) { - // Check that padding is white. - BinaryImage test2(pad, nonaligned.height()); - BinaryImage empty2(test2.size()); - empty2.fill(WHITE); - rasterOp(test2, test2.rect(), aligned, QPoint(0, 0)); - if (test2 != empty2) { - return false; - } + const BinaryImage aligned(eraser.computeConnCompImageAligned()); + const int pad = aligned.width() - nonaligned.width(); + if (pad < 0) { + return false; + } + + BinaryImage test1(nonaligned); + BinaryImage empty1(test1.size()); + empty1.fill(WHITE); + rasterOp>(test1, test1.rect(), aligned, QPoint(pad, 0)); + if (test1 != empty1) { + return false; + } + + if (pad > 0) { + // Check that padding is white. + BinaryImage test2(pad, nonaligned.height()); + BinaryImage empty2(test2.size()); + empty2.fill(WHITE); + rasterOp(test2, test2.rect(), aligned, QPoint(0, 0)); + if (test2 != empty2) { + return false; } + } - return true; + return true; } BOOST_AUTO_TEST_CASE(test_small_image) { - static const int inp[] = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, - 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; - - std::list c4i; - - static const int out4_1[] = {1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0}; - c4i.push_back(makeBinaryImage(out4_1, 3, 6)); - - static const int out4_2[] = {1, 1}; - c4i.push_back(makeBinaryImage(out4_2, 2, 1)); - - static const int out4_3[] = {0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1}; - c4i.push_back(makeBinaryImage(out4_3, 7, 2)); - - static const int out4_4[] = { - 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, - }; - c4i.push_back(makeBinaryImage(out4_4, 4, 3)); - - static const int out4_5[] = {1}; - c4i.push_back(makeBinaryImage(out4_5, 1, 1)); - - static const int out4_6[] = {1}; - c4i.push_back(makeBinaryImage(out4_6, 1, 1)); - - std::list c8i; - - static const int out8_1[] = { - 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, - 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, - }; - c8i.push_back(makeBinaryImage(out8_1, 9, 6)); - - static const int out8_2[] = { - 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, - }; - c8i.push_back(makeBinaryImage(out8_2, 8, 2)); - - BinaryImage img(makeBinaryImage(inp, 9, 8)); - - ConnComp cc; - ConnCompEraserExt eraser4(img, CONN4); - while (!(cc = eraser4.nextConnComp()).isNull()) { - const BinaryImage cc_img(eraser4.computeConnCompImage()); - const auto it(std::find(c4i.begin(), c4i.end(), cc_img)); - if (it != c4i.end()) { - BOOST_CHECK(checkAlignedImage(eraser4, cc_img)); - c4i.erase(it); - } else { - BOOST_ERROR("Incorrect 4-connected block found."); - } + static const int inp[] + = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; + + std::list c4i; + + static const int out4_1[] = {1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0}; + c4i.push_back(makeBinaryImage(out4_1, 3, 6)); + + static const int out4_2[] = {1, 1}; + c4i.push_back(makeBinaryImage(out4_2, 2, 1)); + + static const int out4_3[] = {0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1}; + c4i.push_back(makeBinaryImage(out4_3, 7, 2)); + + static const int out4_4[] = { + 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, + }; + c4i.push_back(makeBinaryImage(out4_4, 4, 3)); + + static const int out4_5[] = {1}; + c4i.push_back(makeBinaryImage(out4_5, 1, 1)); + + static const int out4_6[] = {1}; + c4i.push_back(makeBinaryImage(out4_6, 1, 1)); + + std::list c8i; + + static const int out8_1[] = { + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, + }; + c8i.push_back(makeBinaryImage(out8_1, 9, 6)); + + static const int out8_2[] = { + 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + }; + c8i.push_back(makeBinaryImage(out8_2, 8, 2)); + + BinaryImage img(makeBinaryImage(inp, 9, 8)); + + ConnComp cc; + ConnCompEraserExt eraser4(img, CONN4); + while (!(cc = eraser4.nextConnComp()).isNull()) { + const BinaryImage cc_img(eraser4.computeConnCompImage()); + const auto it(std::find(c4i.begin(), c4i.end(), cc_img)); + if (it != c4i.end()) { + BOOST_CHECK(checkAlignedImage(eraser4, cc_img)); + c4i.erase(it); + } else { + BOOST_ERROR("Incorrect 4-connected block found."); } - BOOST_CHECK_MESSAGE(c4i.empty(), "Not all 4-connected blocks were found."); - - ConnCompEraserExt eraser8(img, CONN8); - while (!(cc = eraser8.nextConnComp()).isNull()) { - const BinaryImage cc_img(eraser8.computeConnCompImage()); - const auto it(std::find(c8i.begin(), c8i.end(), cc_img)); - if (it != c8i.end()) { - BOOST_CHECK(checkAlignedImage(eraser8, cc_img)); - c8i.erase(it); - } else { - BOOST_ERROR("Incorrect 8-connected block found."); - } + } + BOOST_CHECK_MESSAGE(c4i.empty(), "Not all 4-connected blocks were found."); + + ConnCompEraserExt eraser8(img, CONN8); + while (!(cc = eraser8.nextConnComp()).isNull()) { + const BinaryImage cc_img(eraser8.computeConnCompImage()); + const auto it(std::find(c8i.begin(), c8i.end(), cc_img)); + if (it != c8i.end()) { + BOOST_CHECK(checkAlignedImage(eraser8, cc_img)); + c8i.erase(it); + } else { + BOOST_ERROR("Incorrect 8-connected block found."); } - BOOST_CHECK_MESSAGE(c8i.empty(), "Not all 8-connected blocks were found."); + } + BOOST_CHECK_MESSAGE(c8i.empty(), "Not all 8-connected blocks were found."); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestGrayscale.cpp b/imageproc/tests/TestGrayscale.cpp index 921837f4f..cabef6409 100644 --- a/imageproc/tests/TestGrayscale.cpp +++ b/imageproc/tests/TestGrayscale.cpp @@ -16,11 +16,11 @@ along with this program. If not, see . */ -#include "Grayscale.h" -#include "Utils.h" #include #include #include +#include "Grayscale.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -29,47 +29,47 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(GrayscaleTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - BOOST_CHECK(toGrayscale(QImage()).isNull()); + BOOST_CHECK(toGrayscale(QImage()).isNull()); } BOOST_AUTO_TEST_CASE(test_mono_to_grayscale) { - const int w = 50; - const int h = 64; - - QImage mono(w, h, QImage::Format_Mono); - QImage gray(w, h, QImage::Format_Indexed8); - gray.setColorTable(createGrayscalePalette()); - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - const int rnd = rand() & 1; - mono.setPixel(x, y, rnd ? 0 : 1); - gray.setPixel(x, y, rnd ? 0 : 255); - } + const int w = 50; + const int h = 64; + + QImage mono(w, h, QImage::Format_Mono); + QImage gray(w, h, QImage::Format_Indexed8); + gray.setColorTable(createGrayscalePalette()); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int rnd = rand() & 1; + mono.setPixel(x, y, rnd ? 0 : 1); + gray.setPixel(x, y, rnd ? 0 : 255); } + } - const QImage mono_lsb(mono.convertToFormat(QImage::Format_MonoLSB)); + const QImage mono_lsb(mono.convertToFormat(QImage::Format_MonoLSB)); - BOOST_REQUIRE(toGrayscale(mono) == gray); - BOOST_CHECK(toGrayscale(mono_lsb) == gray); + BOOST_REQUIRE(toGrayscale(mono) == gray); + BOOST_CHECK(toGrayscale(mono_lsb) == gray); } BOOST_AUTO_TEST_CASE(test_argb32_to_grayscale) { - const int w = 50; - const int h = 64; - QImage argb32(w, h, QImage::Format_ARGB32); - QImage gray(w, h, QImage::Format_Indexed8); - gray.setColorTable(createGrayscalePalette()); - - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - const int rnd = rand() & 1; - argb32.setPixel(x, y, rnd ? 0x80303030 : 0x80606060); - gray.setPixel(x, y, rnd ? 0x30 : 0x60); - } + const int w = 50; + const int h = 64; + QImage argb32(w, h, QImage::Format_ARGB32); + QImage gray(w, h, QImage::Format_Indexed8); + gray.setColorTable(createGrayscalePalette()); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int rnd = rand() & 1; + argb32.setPixel(x, y, rnd ? 0x80303030 : 0x80606060); + gray.setPixel(x, y, rnd ? 0x30 : 0x60); } + } - BOOST_CHECK(toGrayscale(argb32) == gray); + BOOST_CHECK(toGrayscale(argb32) == gray); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestMorphology.cpp b/imageproc/tests/TestMorphology.cpp index e29b0fe6e..01b50b7f1 100644 --- a/imageproc/tests/TestMorphology.cpp +++ b/imageproc/tests/TestMorphology.cpp @@ -16,15 +16,15 @@ along with this program. If not, see . */ -#include "Morphology.h" -#include "GrayImage.h" -#include "BinaryImage.h" -#include "BWColor.h" -#include "Utils.h" #include -#include #include +#include #include +#include "BWColor.h" +#include "BinaryImage.h" +#include "GrayImage.h" +#include "Morphology.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -33,650 +33,656 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(MorphologyTestSuite); BOOST_AUTO_TEST_CASE(test_dilate_1x1) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect()) == img); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect()) == img); } BOOST_AUTO_TEST_CASE(test_dilate_1x1_gray) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, - 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(1, 1), img.rect()) == img); + const GrayImage img(makeGrayImage(inp, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(1, 1), img.rect()) == img); } BOOST_AUTO_TEST_CASE(test_dilate_1x1_shift_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect().translated(2, 2), BLACK) == control); + BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect().translated(2, 2), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x1_shift_gray) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, - 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 5, 5, 6, 5, 0, 0, 0, 0, 0, 5, 5, - 0, 0, 0, 0, 0, 3, 0, 5, 5, 0, 4, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 5, 5, - 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 5, 5, 6, 5, 0, 0, 0, 0, 0, 5, 5, + 0, 0, 0, 0, 0, 3, 0, 5, 5, 0, 4, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(1, 1), img.rect().translated(2, 2), 5) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(1, 1), img.rect().translated(2, 2), 5) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x1_gray) { - static const int inp[] = {9, 4, 2, 3, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 2, 9, - 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int inp[] = {9, 4, 2, 3, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 2, 9, + 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - static const int out[] = {4, 2, 2, 2, 3, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 5, 2, 2, 2, - 9, 9, 9, 9, 9, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int out[] = {4, 2, 2, 2, 3, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 5, 2, 2, 2, + 9, 9, 9, 9, 9, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(3, 1), img.rect()) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(3, 1), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x3_gray) { - static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - static const int out[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 2, 9, 9, 9, 9, 9, 9, 1, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 2, 9, 2, 9, 9, 9, 9, 9, 9, 3, 9, 2, 9, 9, 5, 2, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 5, 2, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int out[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 2, 9, 9, 9, 9, 9, 9, 1, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 2, 9, 2, 9, 9, 9, 9, 9, 9, 3, 9, 2, 9, 9, 5, 2, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 5, 2, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(1, 3), img.rect()) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(1, 3), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x20_gray) { - static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - static const int out[] = {9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, - 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, - 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1}; + static const int out[] = {9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, + 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, + 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1, 9, 2, 9, 2, 9, 9, 5, 2, 1}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(1, 20), img.rect()) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(1, 20), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, - 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, + 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(dilateBrick(img, QSize(3, 3), img.rect(), WHITE) == control); + BOOST_CHECK(dilateBrick(img, QSize(3, 3), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_gray) { - static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - static const int out[] = {4, 4, 4, 9, 9, 9, 9, 1, 1, 2, 2, 2, 9, 9, 9, 9, 1, 1, 2, 2, 2, 9, 9, 9, 9, 9, 9, - 2, 2, 2, 2, 2, 9, 9, 9, 9, 3, 3, 2, 2, 2, 5, 2, 2, 2, 9, 9, 2, 2, 2, 5, 2, 2, 2, - 9, 9, 2, 2, 2, 5, 2, 2, 2, 9, 9, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int out[] = {4, 4, 4, 9, 9, 9, 9, 1, 1, 2, 2, 2, 9, 9, 9, 9, 1, 1, 2, 2, 2, 9, 9, 9, 9, 9, 9, + 2, 2, 2, 2, 2, 9, 9, 9, 9, 3, 3, 2, 2, 2, 5, 2, 2, 2, 9, 9, 2, 2, 2, 5, 2, 2, 2, + 9, 9, 2, 2, 2, 5, 2, 2, 2, 9, 9, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(dilateGray(img, QSize(3, 3), img.rect()) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(dilateGray(img, QSize(3, 3), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_gray_shrinked) { - static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; - static const int out[] = {2, 2, 9, 9, 9, 9, 1, 2, 2, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 9, 9, 3, 2, 2, 2, - 5, 2, 2, 9, 2, 2, 2, 5, 2, 2, 9, 2, 2, 2, 5, 2, 2, 9, 2, 2, 2, 9, 9, 9}; + static const int out[] = {2, 2, 9, 9, 9, 9, 1, 2, 2, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 9, 9, 3, 2, 2, 2, + 5, 2, 2, 9, 2, 2, 2, 5, 2, 2, 9, 2, 2, 2, 5, 2, 2, 9, 2, 2, 2, 9, 9, 9}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 7, 7)); - const QRect dst_area(img.rect().adjusted(1, 1, -1, -1)); - BOOST_CHECK(dilateGray(img, QSize(3, 3), dst_area) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 7, 7)); + const QRect dst_area(img.rect().adjusted(1, 1, -1, -1)); + BOOST_CHECK(dilateGray(img, QSize(3, 3), dst_area) == control); } BOOST_AUTO_TEST_CASE(test_open_1x2_gray) { - static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + static const int inp[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 5, 2, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - static const int out[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 9, 9, 9, 9, 9, 9, 9, - 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, - 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + static const int out[] = {9, 4, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 9, 9, 9, 9, 9, 9, 9, + 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, + 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - const GrayImage img(makeGrayImage(inp, 9, 9)); - const GrayImage control(makeGrayImage(out, 9, 9)); - BOOST_CHECK(openGray(img, QSize(1, 2), 0x00) == control); + const GrayImage img(makeGrayImage(inp, 9, 9)); + const GrayImage control(makeGrayImage(out, 9, 9)); + BOOST_CHECK(openGray(img, QSize(1, 2), 0x00) == control); } BOOST_AUTO_TEST_CASE(test_dilate_5x5_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1}; + static const int out[] = {1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(dilateBrick(img, QSize(5, 5), img.rect(), WHITE) == control); + BOOST_CHECK(dilateBrick(img, QSize(5, 5), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_narrowing_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] + = {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 4, 9)); - const QRect dst_rect(5, 0, 4, 9); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 4, 9)); + const QRect dst_rect(5, 0, 4, 9); - BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); + BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_5x5_narrowing_white) { - static const int inp[] - = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] + = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = { - // 1, 1, 0, 0, 1, 1, - 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 - // 0, 0, 0, 1, 1, 1, - // 0, 0, 0, 1, 1, 1, - // 0, 0, 0, 1, 1, 1 - }; + static const int out[] = { + // 1, 1, 0, 0, 1, 1, + 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 + // 0, 0, 0, 1, 1, 1, + // 0, 0, 0, 1, 1, 1, + // 0, 0, 0, 1, 1, 1 + }; - const BinaryImage img(makeBinaryImage(inp, 11, 9)); - const BinaryImage control(makeBinaryImage(out, 6, 5)); - const QRect dst_rect(4, 1, 6, 5); + const BinaryImage img(makeBinaryImage(inp, 11, 9)); + const BinaryImage control(makeBinaryImage(out, 6, 5)); + const QRect dst_rect(4, 1, 6, 5); - BOOST_CHECK(dilateBrick(img, QSize(5, 5), dst_rect, WHITE) == control); + BOOST_CHECK(dilateBrick(img, QSize(5, 5), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_narrowing_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, - 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1}; + static const int out[] + = {1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 4, 9)); - const QRect dst_rect(QRect(5, 0, 4, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 4, 9)); + const QRect dst_rect(QRect(5, 0, 4, 9)); - BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); + BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_widening_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, - 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, - 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; + static const int out[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 11, 11)); - const QRect dst_rect(img.rect().adjusted(-1, -1, 1, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 11, 11)); + const QRect dst_rect(img.rect().adjusted(-1, -1, 1, 1)); - BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); + BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_widening_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - }; + static const int out[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 11, 11)); - const QRect dst_rect(img.rect().adjusted(-1, -1, 1, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 11, 11)); + const QRect dst_rect(img.rect().adjusted(-1, -1, 1, 1)); - BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); + BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x1_out_of_brick_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const Brick brick(QSize(3, 1), QPoint(-1, 0)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const Brick brick(QSize(3, 1), QPoint(-1, 0)); - BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); + BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x3_out_of_brick_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; + static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const Brick brick(QSize(1, 3), QPoint(0, -1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const Brick brick(QSize(1, 3), QPoint(0, -1)); - BOOST_CHECK(dilateBrick(img, brick, img.rect(), BLACK) == control); + BOOST_CHECK(dilateBrick(img, brick, img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_large_dilate) { - BinaryImage img(110, 110); - img.fill(WHITE); - const QRect initial_rect(img.rect().center(), QSize(1, 1)); - img.fill(initial_rect, BLACK); + BinaryImage img(110, 110); + img.fill(WHITE); + const QRect initial_rect(img.rect().center(), QSize(1, 1)); + img.fill(initial_rect, BLACK); - const Brick brick(QSize(80, 80)); - const QRect extended_rect(initial_rect.adjusted(brick.minX(), brick.minY(), brick.maxX(), brick.maxY())); + const Brick brick(QSize(80, 80)); + const QRect extended_rect(initial_rect.adjusted(brick.minX(), brick.minY(), brick.maxX(), brick.maxY())); - BinaryImage control(img); - control.fill(extended_rect, BLACK); + BinaryImage control(img); + control.fill(extended_rect, BLACK); - BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); + BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_erode_1x1) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - BOOST_CHECK(erodeBrick(img, QSize(1, 1), img.rect()) == img); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + BOOST_CHECK(erodeBrick(img, QSize(1, 1), img.rect()) == img); } BOOST_AUTO_TEST_CASE(test_erode_3x3_assymmetric_black) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; - static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const Brick brick(QSize(3, 3), QPoint(0, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const Brick brick(QSize(3, 3), QPoint(0, 1)); - BOOST_CHECK(erodeBrick(img, brick, img.rect(), BLACK) == control); + BOOST_CHECK(erodeBrick(img, brick, img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_erode_3x3_assymmetric_white) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, - 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, + 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const Brick brick(QSize(3, 3), QPoint(0, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const Brick brick(QSize(3, 3), QPoint(0, 1)); - BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); + BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_erode_11x11_white) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const Brick brick(QSize(11, 11)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const Brick brick(QSize(11, 11)); - BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); + BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), WHITE) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), BLACK) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_shifted_white) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - static const int out[] = {// 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {// 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const QRect dst_rect(img.rect().translated(2, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const QRect dst_rect(img.rect().translated(2, 1)); - BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, WHITE) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_shifted_black) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - static const int out[] = {// 0, 0, 0, 0, 0, 0, 1, 1, 1 - 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, - 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int out[] = {// 0, 0, 0, 0, 0, 0, 1, 1, 1 + 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const QRect dst_rect(img.rect().translated(2, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const QRect dst_rect(img.rect().translated(2, 1)); - BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, BLACK) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_narrowing) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - static const int out[] = {// 0, 0, 0, 0, - // 0, 0, 0, 0 - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}; + static const int out[] = {// 0, 0, 0, 0, + // 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 4, 4)); - const QRect dst_rect(img.rect().adjusted(2, 2, -3, -3)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 4, 4)); + const QRect dst_rect(img.rect().adjusted(2, 2, -3, -3)); - BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, WHITE) == control); - BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, BLACK) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, WHITE) == control); + BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_white) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), WHITE) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_black) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, - 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1}; + static const int out[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), BLACK) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_shifted_white) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - static const int out[] = {// 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {// 1, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const QRect dst_rect(img.rect().translated(2, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const QRect dst_rect(img.rect().translated(2, 1)); - BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, WHITE) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_shifted_black) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - static const int out[] = {// 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static const int out[] = {// 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); - const QRect dst_rect(img.rect().translated(2, 1)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); + const QRect dst_rect(img.rect().translated(2, 1)); - BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, BLACK) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_narrowing) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0}; - static const int out[] = {// 0, 0, 0, 0, - // 0, 0, 0, 0 - 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1}; + static const int out[] = {// 0, 0, 0, 0, + // 0, 0, 0, 0 + 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 4, 4)); - const QRect dst_rect(img.rect().adjusted(2, 2, -3, -3)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 4, 4)); + const QRect dst_rect(img.rect().adjusted(2, 2, -3, -3)); - BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, WHITE) == control); - BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, BLACK) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, WHITE) == control); + BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_hmm_1) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, - 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const char pattern[] = "?X?" - "X X" - "?X?"; - const QPoint origin(1, 1); + static const char pattern[] + = "?X?" + "X X" + "?X?"; + const QPoint origin(1, 1); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); - BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); } BOOST_AUTO_TEST_CASE(test_hmm_surroundings_1) { - static const int inp[] = {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out_white[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out_white[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out_black[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out_black[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const char pattern[] = "?X?" - "X X" - "?X?"; - const QPoint origin(1, 1); + static const char pattern[] + = "?X?" + "X X" + "?X?"; + const QPoint origin(1, 1); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control_w(makeBinaryImage(out_white, 9, 9)); - const BinaryImage control_b(makeBinaryImage(out_black, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control_w(makeBinaryImage(out_white, 9, 9)); + const BinaryImage control_b(makeBinaryImage(out_black, 9, 9)); - BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control_w); - BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control_b); + BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control_w); + BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control_b); } BOOST_AUTO_TEST_CASE(test_hmm_surroundings_2) { - static const int inp[] = {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const char pattern[] = "?X?" - "X X" - "?X?"; - const QPoint origin(1, 0); + static const char pattern[] + = "?X?" + "X X" + "?X?"; + const QPoint origin(1, 0); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); - BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); } BOOST_AUTO_TEST_CASE(test_hmm_cornercase_1) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, - 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const char pattern[] = "?X?" - "X X" - "?X?"; - const QPoint origin(10, 0); + static const char pattern[] + = "?X?" + "X X" + "?X?"; + const QPoint origin(10, 0); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); - BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); } BOOST_AUTO_TEST_CASE(test_hmm_cornercase_2) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, - 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const char pattern[] = "?X?" - "X X" - "?X?"; - const QPoint origin(0, 9); + static const char pattern[] + = "?X?" + "X X" + "?X?"; + const QPoint origin(0, 9); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); - BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, WHITE, pattern, 3, 3, origin) == control); + BOOST_CHECK(hitMissMatch(img, BLACK, pattern, 3, 3, origin) == control); } BOOST_AUTO_TEST_CASE(test_hmr_1) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; - static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; + static const int out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; - static const char pattern[] = " - " - "X+X" - "XXX"; + static const char pattern[] + = " - " + "X+X" + "XXX"; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage control(makeBinaryImage(out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage control(makeBinaryImage(out, 9, 9)); - BOOST_CHECK(hitMissReplace(img, WHITE, pattern, 3, 3) == control); - BOOST_CHECK(hitMissReplace(img, BLACK, pattern, 3, 3) == control); + BOOST_CHECK(hitMissReplace(img, WHITE, pattern, 3, 3) == control); + BOOST_CHECK(hitMissReplace(img, BLACK, pattern, 3, 3) == control); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestOrthogonalRotation.cpp b/imageproc/tests/TestOrthogonalRotation.cpp index 196e58c4b..6d89eeacf 100644 --- a/imageproc/tests/TestOrthogonalRotation.cpp +++ b/imageproc/tests/TestOrthogonalRotation.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ -#include "OrthogonalRotation.h" -#include "BinaryImage.h" -#include "Utils.h" #include #include #include +#include "BinaryImage.h" +#include "OrthogonalRotation.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -30,74 +30,74 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(OrthogonalRotationTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - const BinaryImage null_img; - BOOST_CHECK(orthogonalRotation(null_img, 90).isNull()); + const BinaryImage null_img; + BOOST_CHECK(orthogonalRotation(null_img, 90).isNull()); } BOOST_AUTO_TEST_CASE(test_full_image) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, - 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, - 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - static const int out1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - static const int out2[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, - 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, - 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - static const int out3[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage out1_img(makeBinaryImage(out1, 9, 9)); - const BinaryImage out2_img(makeBinaryImage(out2, 9, 9)); - const BinaryImage out3_img(makeBinaryImage(out3, 9, 9)); - - BOOST_REQUIRE(orthogonalRotation(img, 0) == img); - BOOST_REQUIRE(orthogonalRotation(img, 360) == img); - BOOST_REQUIRE(orthogonalRotation(img, 90) == out1_img); - BOOST_REQUIRE(orthogonalRotation(img, -270) == out1_img); - BOOST_REQUIRE(orthogonalRotation(img, 180) == out2_img); - BOOST_REQUIRE(orthogonalRotation(img, -180) == out2_img); - BOOST_REQUIRE(orthogonalRotation(img, 270) == out3_img); - BOOST_REQUIRE(orthogonalRotation(img, -90) == out3_img); + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, + 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + static const int out1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + static const int out2[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, + 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + static const int out3[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage out1_img(makeBinaryImage(out1, 9, 9)); + const BinaryImage out2_img(makeBinaryImage(out2, 9, 9)); + const BinaryImage out3_img(makeBinaryImage(out3, 9, 9)); + + BOOST_REQUIRE(orthogonalRotation(img, 0) == img); + BOOST_REQUIRE(orthogonalRotation(img, 360) == img); + BOOST_REQUIRE(orthogonalRotation(img, 90) == out1_img); + BOOST_REQUIRE(orthogonalRotation(img, -270) == out1_img); + BOOST_REQUIRE(orthogonalRotation(img, 180) == out2_img); + BOOST_REQUIRE(orthogonalRotation(img, -180) == out2_img); + BOOST_REQUIRE(orthogonalRotation(img, 270) == out3_img); + BOOST_REQUIRE(orthogonalRotation(img, -90) == out3_img); } BOOST_AUTO_TEST_CASE(test_sub_image) { - static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, - 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1}; - - static const int out1[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; - - static const int out2[] = {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}; - - static const int out3[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; - - static const int out4[] = {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}; - - const QRect rect(1, 2, 7, 7); - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage out1_img(makeBinaryImage(out1, 7, 7)); - const BinaryImage out2_img(makeBinaryImage(out2, 7, 7)); - const BinaryImage out3_img(makeBinaryImage(out3, 7, 7)); - const BinaryImage out4_img(makeBinaryImage(out4, 7, 7)); - - BOOST_REQUIRE(orthogonalRotation(img, rect, 0) == out1_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, 360) == out1_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, 90) == out2_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, -270) == out2_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, 180) == out3_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, -180) == out3_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, 270) == out4_img); - BOOST_REQUIRE(orthogonalRotation(img, rect, -90) == out4_img); + static const int inp[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1}; + + static const int out1[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; + + static const int out2[] = {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}; + + static const int out3[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; + + static const int out4[] = {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}; + + const QRect rect(1, 2, 7, 7); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage out1_img(makeBinaryImage(out1, 7, 7)); + const BinaryImage out2_img(makeBinaryImage(out2, 7, 7)); + const BinaryImage out3_img(makeBinaryImage(out3, 7, 7)); + const BinaryImage out4_img(makeBinaryImage(out4, 7, 7)); + + BOOST_REQUIRE(orthogonalRotation(img, rect, 0) == out1_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, 360) == out1_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, 90) == out2_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, -270) == out2_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, 180) == out3_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, -180) == out3_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, 270) == out4_img); + BOOST_REQUIRE(orthogonalRotation(img, rect, -90) == out4_img); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestPolygonRasterizer.cpp b/imageproc/tests/TestPolygonRasterizer.cpp index 12c3763b3..7d15f2da7 100644 --- a/imageproc/tests/TestPolygonRasterizer.cpp +++ b/imageproc/tests/TestPolygonRasterizer.cpp @@ -16,23 +16,23 @@ along with this program. If not, see . */ -#include "PolygonRasterizer.h" -#include "BinaryImage.h" -#include "BinaryThreshold.h" -#include "RasterOp.h" -#include "BWColor.h" -#include "Utils.h" -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include -#include #include +#include +#include "BWColor.h" +#include "BinaryImage.h" +#include "BinaryThreshold.h" +#include "PolygonRasterizer.h" +#include "RasterOp.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -41,127 +41,127 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(PolygonRasterizerTestSuite); static QPolygonF createShape(const QSize& image_size, double radius) { - const QPointF center(0.5 * image_size.width(), 0.5 * image_size.height()); - const double PI = 3.14159265; - double angle = PI / 2.0; - const int num_steps = 5; - const double step = PI * 2.0 / num_steps; + const QPointF center(0.5 * image_size.width(), 0.5 * image_size.height()); + const double PI = 3.14159265; + double angle = PI / 2.0; + const int num_steps = 5; + const double step = PI * 2.0 / num_steps; - QPolygonF poly; + QPolygonF poly; + poly.push_back(center + QPointF(std::cos(angle), std::sin(angle)) * radius); + for (int i = 1; i < num_steps; ++i) { + angle += step * 2; poly.push_back(center + QPointF(std::cos(angle), std::sin(angle)) * radius); - for (int i = 1; i < num_steps; ++i) { - angle += step * 2; - poly.push_back(center + QPointF(std::cos(angle), std::sin(angle)) * radius); - } + } - return poly; + return poly; } static bool fuzzyCompare(const BinaryImage& img, const QImage& control) { - // Make two binary images from the QImage with slightly different thresholds. - BinaryImage control1(control, BinaryThreshold(128 - 30)); - BinaryImage control2(control, BinaryThreshold(128 + 30)); + // Make two binary images from the QImage with slightly different thresholds. + BinaryImage control1(control, BinaryThreshold(128 - 30)); + BinaryImage control2(control, BinaryThreshold(128 + 30)); - // Take the difference with each control image. - rasterOp>(control1, img); - rasterOp>(control2, img); + // Take the difference with each control image. + rasterOp>(control1, img); + rasterOp>(control2, img); - // Are there pixels different in both cases? - rasterOp>(control1, control2); + // Are there pixels different in both cases? + rasterOp>(control1, control2); - return control1.countBlackPixels() == 0; + return control1.countBlackPixels() == 0; } static bool testFillShape(const QSize& image_size, const QPolygonF& shape, Qt::FillRule fill_rule) { - BinaryImage b_image(image_size, WHITE); - PolygonRasterizer::fill(b_image, BLACK, shape, fill_rule); + BinaryImage b_image(image_size, WHITE); + PolygonRasterizer::fill(b_image, BLACK, shape, fill_rule); - QImage q_image(image_size, QImage::Format_RGB32); - q_image.fill(0xffffffff); + QImage q_image(image_size, QImage::Format_RGB32); + q_image.fill(0xffffffff); - { - QPainter painter(&q_image); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setBrush(QColor(0x00, 0x00, 0x00)); - painter.setPen(Qt::NoPen); - painter.drawPolygon(shape, fill_rule); - } + { + QPainter painter(&q_image); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QColor(0x00, 0x00, 0x00)); + painter.setPen(Qt::NoPen); + painter.drawPolygon(shape, fill_rule); + } - return fuzzyCompare(b_image, q_image); + return fuzzyCompare(b_image, q_image); } static bool testFillExceptShape(const QSize& image_size, const QPolygonF& shape, Qt::FillRule fill_rule) { - BinaryImage b_image(image_size, WHITE); - PolygonRasterizer::fillExcept(b_image, BLACK, shape, fill_rule); + BinaryImage b_image(image_size, WHITE); + PolygonRasterizer::fillExcept(b_image, BLACK, shape, fill_rule); - QImage q_image(image_size, QImage::Format_RGB32); - q_image.fill(0x00000000); + QImage q_image(image_size, QImage::Format_RGB32); + q_image.fill(0x00000000); - { - QPainter painter(&q_image); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setBrush(QColor(0xff, 0xff, 0xff)); - painter.setPen(Qt::NoPen); - painter.drawPolygon(shape, fill_rule); - } + { + QPainter painter(&q_image); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QColor(0xff, 0xff, 0xff)); + painter.setPen(Qt::NoPen); + painter.drawPolygon(shape, fill_rule); + } - return fuzzyCompare(b_image, q_image); + return fuzzyCompare(b_image, q_image); } BOOST_AUTO_TEST_CASE(test_complex_shape) { - const QSize image_size(500, 500); - - // This one fits the image. - const QPolygonF smaller_shape(createShape(image_size, 230)); - - // This one doesn't fit the image and will be clipped. - const QPolygonF bigger_shape(createShape(image_size, 300)); - - BOOST_CHECK(testFillShape(image_size, smaller_shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillShape(image_size, smaller_shape, Qt::WindingFill)); - BOOST_CHECK(testFillShape(image_size, bigger_shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillShape(image_size, bigger_shape, Qt::WindingFill)); - BOOST_CHECK(testFillExceptShape(image_size, smaller_shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillExceptShape(image_size, smaller_shape, Qt::WindingFill)); - BOOST_CHECK(testFillExceptShape(image_size, bigger_shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillExceptShape(image_size, bigger_shape, Qt::WindingFill)); + const QSize image_size(500, 500); + + // This one fits the image. + const QPolygonF smaller_shape(createShape(image_size, 230)); + + // This one doesn't fit the image and will be clipped. + const QPolygonF bigger_shape(createShape(image_size, 300)); + + BOOST_CHECK(testFillShape(image_size, smaller_shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillShape(image_size, smaller_shape, Qt::WindingFill)); + BOOST_CHECK(testFillShape(image_size, bigger_shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillShape(image_size, bigger_shape, Qt::WindingFill)); + BOOST_CHECK(testFillExceptShape(image_size, smaller_shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillExceptShape(image_size, smaller_shape, Qt::WindingFill)); + BOOST_CHECK(testFillExceptShape(image_size, bigger_shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillExceptShape(image_size, bigger_shape, Qt::WindingFill)); } BOOST_AUTO_TEST_CASE(test_corner_cases) { - const QSize image_size(500, 500); - const QPolygonF shape(QRectF(QPointF(0, 0), image_size)); - const QPolygonF shape2(QRectF(QPointF(-1, -1), image_size)); - - // This one touches clip rectangle's corners. - QPolygonF shape3; - shape3.push_back(QPointF(-250.0, 250.0)); - shape3.push_back(QPointF(250.0, -250.0)); - shape3.push_back(QPointF(750.0, -250.0)); - shape3.push_back(QPointF(-250.0, 750.0)); - - BOOST_CHECK(testFillShape(image_size, shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillShape(image_size, shape, Qt::WindingFill)); - BOOST_CHECK(testFillShape(image_size, shape2, Qt::OddEvenFill)); - BOOST_CHECK(testFillShape(image_size, shape2, Qt::WindingFill)); - BOOST_CHECK(testFillShape(image_size, shape3, Qt::OddEvenFill)); - BOOST_CHECK(testFillShape(image_size, shape3, Qt::WindingFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape, Qt::OddEvenFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape, Qt::WindingFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape2, Qt::OddEvenFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape2, Qt::WindingFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape3, Qt::OddEvenFill)); - BOOST_CHECK(testFillExceptShape(image_size, shape3, Qt::WindingFill)); + const QSize image_size(500, 500); + const QPolygonF shape(QRectF(QPointF(0, 0), image_size)); + const QPolygonF shape2(QRectF(QPointF(-1, -1), image_size)); + + // This one touches clip rectangle's corners. + QPolygonF shape3; + shape3.push_back(QPointF(-250.0, 250.0)); + shape3.push_back(QPointF(250.0, -250.0)); + shape3.push_back(QPointF(750.0, -250.0)); + shape3.push_back(QPointF(-250.0, 750.0)); + + BOOST_CHECK(testFillShape(image_size, shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillShape(image_size, shape, Qt::WindingFill)); + BOOST_CHECK(testFillShape(image_size, shape2, Qt::OddEvenFill)); + BOOST_CHECK(testFillShape(image_size, shape2, Qt::WindingFill)); + BOOST_CHECK(testFillShape(image_size, shape3, Qt::OddEvenFill)); + BOOST_CHECK(testFillShape(image_size, shape3, Qt::WindingFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape, Qt::OddEvenFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape, Qt::WindingFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape2, Qt::OddEvenFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape2, Qt::WindingFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape3, Qt::OddEvenFill)); + BOOST_CHECK(testFillExceptShape(image_size, shape3, Qt::WindingFill)); } BOOST_AUTO_TEST_CASE(regression_test_1) { - QPolygonF shape; - shape.push_back(QPointF(937.872, 24.559)); - shape.push_back(QPointF(-1.23235e-14, -1.70697e-15)); - shape.push_back(QPointF(2.73578e-11, 1275.44)); - shape.push_back(QPointF(904.496, 1299.12)); - shape.push_back(QPointF(937.872, 24.559)); - BOOST_CHECK(testFillExceptShape(QSize(938, 1299), shape, Qt::WindingFill)); + QPolygonF shape; + shape.push_back(QPointF(937.872, 24.559)); + shape.push_back(QPointF(-1.23235e-14, -1.70697e-15)); + shape.push_back(QPointF(2.73578e-11, 1275.44)); + shape.push_back(QPointF(904.496, 1299.12)); + shape.push_back(QPointF(937.872, 24.559)); + BOOST_CHECK(testFillExceptShape(QSize(938, 1299), shape, Qt::WindingFill)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestRastLineFinder.cpp b/imageproc/tests/TestRastLineFinder.cpp index 99607a571..dfaab5720 100644 --- a/imageproc/tests/TestRastLineFinder.cpp +++ b/imageproc/tests/TestRastLineFinder.cpp @@ -17,66 +17,66 @@ */ -#include "RastLineFinder.h" -#include #include -#include -#include +#include #include #include +#include +#include +#include "RastLineFinder.h" namespace imageproc { namespace tests { BOOST_AUTO_TEST_SUITE(RastLineFinderTestSuite); static bool matchSupportPoints(const std::vector& idxs1, const std::set& idxs2) { - return std::set(idxs1.begin(), idxs1.end()) == idxs2; + return std::set(idxs1.begin(), idxs1.end()) == idxs2; } BOOST_AUTO_TEST_CASE(test1) { - // 4- and 3-point lines with min_support_points == 3 - // -------------------------------------------------- - // x x - // x x - // x x - // x - // -------------------------------------------------- - std::vector pts; - pts.emplace_back(-100, -100); - pts.emplace_back(0, 0); - pts.emplace_back(100, 100); - pts.emplace_back(200, 200); - pts.emplace_back(0, 100); - pts.emplace_back(100, 0); - pts.emplace_back(-100, 200); - - std::set line1_idxs; - line1_idxs.insert(0); - line1_idxs.insert(1); - line1_idxs.insert(2); - line1_idxs.insert(3); - - std::set line2_idxs; - line2_idxs.insert(4); - line2_idxs.insert(5); - line2_idxs.insert(6); - - RastLineFinderParams params; - params.setMinSupportPoints(3); - RastLineFinder finder(pts, params); - - std::vector support_idxs; - - // line 1 - BOOST_REQUIRE(!finder.findNext(&support_idxs).isNull()); - BOOST_REQUIRE(matchSupportPoints(support_idxs, line1_idxs)); - - // line2 - BOOST_REQUIRE(!finder.findNext(&support_idxs).isNull()); - BOOST_REQUIRE(matchSupportPoints(support_idxs, line2_idxs)); - - // no more lines - BOOST_REQUIRE(finder.findNext().isNull()); + // 4- and 3-point lines with min_support_points == 3 + // -------------------------------------------------- + // x x + // x x + // x x + // x + // -------------------------------------------------- + std::vector pts; + pts.emplace_back(-100, -100); + pts.emplace_back(0, 0); + pts.emplace_back(100, 100); + pts.emplace_back(200, 200); + pts.emplace_back(0, 100); + pts.emplace_back(100, 0); + pts.emplace_back(-100, 200); + + std::set line1_idxs; + line1_idxs.insert(0); + line1_idxs.insert(1); + line1_idxs.insert(2); + line1_idxs.insert(3); + + std::set line2_idxs; + line2_idxs.insert(4); + line2_idxs.insert(5); + line2_idxs.insert(6); + + RastLineFinderParams params; + params.setMinSupportPoints(3); + RastLineFinder finder(pts, params); + + std::vector support_idxs; + + // line 1 + BOOST_REQUIRE(!finder.findNext(&support_idxs).isNull()); + BOOST_REQUIRE(matchSupportPoints(support_idxs, line1_idxs)); + + // line2 + BOOST_REQUIRE(!finder.findNext(&support_idxs).isNull()); + BOOST_REQUIRE(matchSupportPoints(support_idxs, line2_idxs)); + + // no more lines + BOOST_REQUIRE(finder.findNext().isNull()); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestRasterOp.cpp b/imageproc/tests/TestRasterOp.cpp index 4c6d4ad41..c9a212263 100644 --- a/imageproc/tests/TestRasterOp.cpp +++ b/imageproc/tests/TestRasterOp.cpp @@ -16,15 +16,15 @@ along with this program. If not, see . */ -#include "RasterOp.h" -#include "BinaryImage.h" -#include "Utils.h" #include #include -#include +#include #include #include -#include +#include +#include "BinaryImage.h" +#include "RasterOp.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -32,147 +32,147 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(RasterOpTestSuite); -template +template static bool check_subimage_rop(const QImage& dst, const QRect& dst_rect, const QImage& src, const QPoint& src_pt) { - BinaryImage dst_bi(dst); - const BinaryImage src_bi(src); - rasterOp(dst_bi, dst_rect, src_bi, src_pt); - // Here we assume that full-image raster operations work correctly. - BinaryImage dst_subimage(dst.copy(dst_rect)); - const QRect src_rect(src_pt, dst_rect.size()); - const BinaryImage src_subimage(src.copy(src_rect)); - rasterOp(dst_subimage, dst_subimage.rect(), src_subimage, QPoint(0, 0)); - - return dst_subimage.toQImage() == dst_bi.toQImage().copy(dst_rect); + BinaryImage dst_bi(dst); + const BinaryImage src_bi(src); + rasterOp(dst_bi, dst_rect, src_bi, src_pt); + // Here we assume that full-image raster operations work correctly. + BinaryImage dst_subimage(dst.copy(dst_rect)); + const QRect src_rect(src_pt, dst_rect.size()); + const BinaryImage src_subimage(src.copy(src_rect)); + rasterOp(dst_subimage, dst_subimage.rect(), src_subimage, QPoint(0, 0)); + + return dst_subimage.toQImage() == dst_bi.toQImage().copy(dst_rect); } BOOST_AUTO_TEST_CASE(test_small_image) { - static const int inp[] = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, - 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; + static const int inp[] + = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}; - static const int mask[] = {0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}; + static const int mask[] + = {0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}; - static const int out[] = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}; + static const int out[] + = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}; - BinaryImage img(makeBinaryImage(inp, 9, 8)); - const BinaryImage mask_img(makeBinaryImage(mask, 9, 8)); + BinaryImage img(makeBinaryImage(inp, 9, 8)); + const BinaryImage mask_img(makeBinaryImage(mask, 9, 8)); - typedef RopAnd Rop; + typedef RopAnd Rop; - rasterOp(img, img.rect(), mask_img, QPoint(0, 0)); - BOOST_CHECK(img == makeBinaryImage(out, 9, 8)); + rasterOp(img, img.rect(), mask_img, QPoint(0, 0)); + BOOST_CHECK(img == makeBinaryImage(out, 9, 8)); } namespace { class Tester1 { -public: - Tester1(); + public: + Tester1(); - bool testFullImage() const; + bool testFullImage() const; - bool testSubImage(const QRect& dst_rect, const QPoint& src_pt) const; + bool testSubImage(const QRect& dst_rect, const QPoint& src_pt) const; -private: - typedef RopXor Rop; + private: + typedef RopXor Rop; - BinaryImage m_src; - BinaryImage m_dstBefore; - BinaryImage m_dstAfter; + BinaryImage m_src; + BinaryImage m_dstBefore; + BinaryImage m_dstAfter; }; Tester1::Tester1() { - const int w = 400; - const int h = 300; - - std::vector src(w * h); - for (int& i : src) { - i = rand() & 1; - } - - std::vector dst(w * h); - for (int& i : dst) { - i = rand() & 1; - } - - std::vector res(w * h); - for (size_t i = 0; i < res.size(); ++i) { - res[i] = Rop::transform(src[i], dst[i]) & 1; - } - - m_src = makeBinaryImage(&src[0], w, h); - m_dstBefore = makeBinaryImage(&dst[0], w, h); - m_dstAfter = makeBinaryImage(&res[0], w, h); + const int w = 400; + const int h = 300; + + std::vector src(w * h); + for (int& i : src) { + i = rand() & 1; + } + + std::vector dst(w * h); + for (int& i : dst) { + i = rand() & 1; + } + + std::vector res(w * h); + for (size_t i = 0; i < res.size(); ++i) { + res[i] = Rop::transform(src[i], dst[i]) & 1; + } + + m_src = makeBinaryImage(&src[0], w, h); + m_dstBefore = makeBinaryImage(&dst[0], w, h); + m_dstAfter = makeBinaryImage(&res[0], w, h); } bool Tester1::testFullImage() const { - BinaryImage dst(m_dstBefore); - rasterOp(dst, dst.rect(), m_src, QPoint(0, 0)); + BinaryImage dst(m_dstBefore); + rasterOp(dst, dst.rect(), m_src, QPoint(0, 0)); - return dst == m_dstAfter; + return dst == m_dstAfter; } bool Tester1::testSubImage(const QRect& dst_rect, const QPoint& src_pt) const { - const QImage dst_before(m_dstBefore.toQImage()); - const QImage& dst(dst_before); - const QImage src(m_src.toQImage()); + const QImage dst_before(m_dstBefore.toQImage()); + const QImage& dst(dst_before); + const QImage src(m_src.toQImage()); - if (!check_subimage_rop(dst, dst_rect, src, src_pt)) { - return false; - } + if (!check_subimage_rop(dst, dst_rect, src, src_pt)) { + return false; + } - return surroundingsIntact(dst, dst_before, dst_rect); + return surroundingsIntact(dst, dst_before, dst_rect); } } // namespace BOOST_AUTO_TEST_CASE(test_large_image) { - Tester1 tester; - BOOST_REQUIRE(tester.testFullImage()); - BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(101, 41))); - BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(99, 99))); - BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(104, 64))); + Tester1 tester; + BOOST_REQUIRE(tester.testFullImage()); + BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(101, 41))); + BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(99, 99))); + BOOST_REQUIRE(tester.testSubImage(QRect(101, 32, 211, 151), QPoint(104, 64))); } namespace { class Tester2 { -public: - Tester2(); + public: + Tester2(); - bool testBlockMove(const QRect& rect, int dx, int dy); + bool testBlockMove(const QRect& rect, int dx, int dy); -private: - BinaryImage m_image; + private: + BinaryImage m_image; }; Tester2::Tester2() { - m_image = randomBinaryImage(400, 300); + m_image = randomBinaryImage(400, 300); } bool Tester2::testBlockMove(const QRect& rect, const int dx, const int dy) { - BinaryImage dst(m_image); - const QRect dst_rect(rect.translated(dx, dy)); - rasterOp(dst, dst_rect, dst, rect.topLeft()); - QImage q_src(m_image.toQImage()); - QImage q_dst(dst.toQImage()); - if (q_src.copy(rect) != q_dst.copy(dst_rect)) { - return false; - } - - return surroundingsIntact(q_dst, q_src, dst_rect); + BinaryImage dst(m_image); + const QRect dst_rect(rect.translated(dx, dy)); + rasterOp(dst, dst_rect, dst, rect.topLeft()); + QImage q_src(m_image.toQImage()); + QImage q_dst(dst.toQImage()); + if (q_src.copy(rect) != q_dst.copy(dst_rect)) { + return false; + } + + return surroundingsIntact(q_dst, q_src, dst_rect); } } // namespace BOOST_AUTO_TEST_CASE(test_move_blocks) { - Tester2 tester; - BOOST_REQUIRE(tester.testBlockMove(QRect(0, 0, 97, 150), 1, 0)); - BOOST_REQUIRE(tester.testBlockMove(QRect(100, 50, 15, 100), -1, 0)); - BOOST_REQUIRE(tester.testBlockMove(QRect(200, 200, 200, 100), -1, -1)); - BOOST_REQUIRE(tester.testBlockMove(QRect(51, 35, 199, 200), 0, 1)); - BOOST_REQUIRE(tester.testBlockMove(QRect(51, 35, 199, 200), 1, 1)); + Tester2 tester; + BOOST_REQUIRE(tester.testBlockMove(QRect(0, 0, 97, 150), 1, 0)); + BOOST_REQUIRE(tester.testBlockMove(QRect(100, 50, 15, 100), -1, 0)); + BOOST_REQUIRE(tester.testBlockMove(QRect(200, 200, 200, 100), -1, -1)); + BOOST_REQUIRE(tester.testBlockMove(QRect(51, 35, 199, 200), 0, 1)); + BOOST_REQUIRE(tester.testBlockMove(QRect(51, 35, 199, 200), 1, 1)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestReduceThreshold.cpp b/imageproc/tests/TestReduceThreshold.cpp index 930b127b5..75a0a47a8 100644 --- a/imageproc/tests/TestReduceThreshold.cpp +++ b/imageproc/tests/TestReduceThreshold.cpp @@ -16,11 +16,11 @@ along with this program. If not, see . */ -#include "ReduceThreshold.h" -#include "BinaryImage.h" -#include "Utils.h" #include #include +#include "BinaryImage.h" +#include "ReduceThreshold.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -29,50 +29,50 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(ReduceThresholdTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - BOOST_CHECK(ReduceThreshold(BinaryImage())(2).image().isNull()); + BOOST_CHECK(ReduceThreshold(BinaryImage())(2).image().isNull()); } BOOST_AUTO_TEST_CASE(test_small_image) { - const int inp[] = {0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + const int inp[] = {0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, - 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, - 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const int out1[] = {0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const int out2[] = {0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - const int out3[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1}; - const int out4[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}; + 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + const int out1[] = {0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + const int out2[] = {0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + const int out3[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1}; + const int out4[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}; - const BinaryImage img(makeBinaryImage(inp, 10, 8)); + const BinaryImage img(makeBinaryImage(inp, 10, 8)); - BOOST_CHECK(makeBinaryImage(out1, 5, 4) == ReduceThreshold(img)(1)); - BOOST_CHECK(makeBinaryImage(out2, 5, 4) == ReduceThreshold(img)(2)); - BOOST_CHECK(makeBinaryImage(out3, 5, 4) == ReduceThreshold(img)(3)); - BOOST_CHECK(makeBinaryImage(out4, 5, 4) == ReduceThreshold(img)(4)); + BOOST_CHECK(makeBinaryImage(out1, 5, 4) == ReduceThreshold(img)(1)); + BOOST_CHECK(makeBinaryImage(out2, 5, 4) == ReduceThreshold(img)(2)); + BOOST_CHECK(makeBinaryImage(out3, 5, 4) == ReduceThreshold(img)(3)); + BOOST_CHECK(makeBinaryImage(out4, 5, 4) == ReduceThreshold(img)(4)); } BOOST_AUTO_TEST_CASE(test_lines) { - static const int inp[] = {0, 0, 0, 1, 1, 0, 1, 1, 0}; - static const int out1[] = {0, 1, 1, 1}; - static const int out2[] = {0, 1, 1, 1}; - static const int out3[] = {0, 0, 0, 1}; - static const int out4[] = {0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 1, 1, 0, 1, 1, 0}; + static const int out1[] = {0, 1, 1, 1}; + static const int out2[] = {0, 1, 1, 1}; + static const int out3[] = {0, 0, 0, 1}; + static const int out4[] = {0, 0, 0, 1}; - BinaryImage img(makeBinaryImage(inp, 9, 1)); + BinaryImage img(makeBinaryImage(inp, 9, 1)); - BOOST_CHECK(makeBinaryImage(out1, 4, 1) == ReduceThreshold(img)(1)); - BOOST_CHECK(makeBinaryImage(out2, 4, 1) == ReduceThreshold(img)(2)); - BOOST_CHECK(makeBinaryImage(out3, 4, 1) == ReduceThreshold(img)(3)); - BOOST_CHECK(makeBinaryImage(out4, 4, 1) == ReduceThreshold(img)(4)); + BOOST_CHECK(makeBinaryImage(out1, 4, 1) == ReduceThreshold(img)(1)); + BOOST_CHECK(makeBinaryImage(out2, 4, 1) == ReduceThreshold(img)(2)); + BOOST_CHECK(makeBinaryImage(out3, 4, 1) == ReduceThreshold(img)(3)); + BOOST_CHECK(makeBinaryImage(out4, 4, 1) == ReduceThreshold(img)(4)); - img = makeBinaryImage(inp, 1, 9); + img = makeBinaryImage(inp, 1, 9); - BOOST_CHECK(makeBinaryImage(out1, 1, 4) == ReduceThreshold(img)(1)); - BOOST_CHECK(makeBinaryImage(out2, 1, 4) == ReduceThreshold(img)(2)); - BOOST_CHECK(makeBinaryImage(out3, 1, 4) == ReduceThreshold(img)(3)); - BOOST_CHECK(makeBinaryImage(out4, 1, 4) == ReduceThreshold(img)(4)); + BOOST_CHECK(makeBinaryImage(out1, 1, 4) == ReduceThreshold(img)(1)); + BOOST_CHECK(makeBinaryImage(out2, 1, 4) == ReduceThreshold(img)(2)); + BOOST_CHECK(makeBinaryImage(out3, 1, 4) == ReduceThreshold(img)(3)); + BOOST_CHECK(makeBinaryImage(out4, 1, 4) == ReduceThreshold(img)(4)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestSEDM.cpp b/imageproc/tests/TestSEDM.cpp index d0bad7584..d88671606 100644 --- a/imageproc/tests/TestSEDM.cpp +++ b/imageproc/tests/TestSEDM.cpp @@ -16,14 +16,14 @@ along with this program. If not, see . */ -#include "SEDM.h" -#include "BinaryImage.h" -#include "BWColor.h" -#include "Utils.h" -#include #include #include #include +#include +#include "BWColor.h" +#include "BinaryImage.h" +#include "SEDM.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -32,43 +32,43 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(SEDMTestSuite); bool verifySEDM(const SEDM& sedm, const uint32_t* control) { - const uint32_t* line = sedm.data(); - for (int y = 0; y < sedm.size().height(); ++y) { - for (int x = 0; x < sedm.size().width(); ++x) { - if (line[x] != *control) { - return false; - } - ++control; - } - line += sedm.stride(); + const uint32_t* line = sedm.data(); + for (int y = 0; y < sedm.size().height(); ++y) { + for (int x = 0; x < sedm.size().width(); ++x) { + if (line[x] != *control) { + return false; + } + ++control; } + line += sedm.stride(); + } - return true; + return true; } void dumpMatrix(const uint32_t* data, QSize size) { - const int width = size.width(); - const int height = size.height(); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x, ++data) { - std::cout << *data << ' '; - } - std::cout << std::endl; + const int width = size.width(); + const int height = size.height(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x, ++data) { + std::cout << *data << ' '; } + std::cout << std::endl; + } } BOOST_AUTO_TEST_CASE(test1) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const uint32_t out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 4, 4, 4, 1, 0, 0, 0, 0, 1, 4, 9, 4, 1, 0, 0, 0, 0, 1, 4, 4, 4, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const uint32_t out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 4, 4, 4, 1, 0, 0, 0, 0, 1, 4, 9, 4, 1, 0, 0, 0, 0, 1, 4, 4, 4, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const SEDM sedm(img, SEDM::DIST_TO_WHITE, SEDM::DIST_TO_NO_BORDERS); - BOOST_CHECK(verifySEDM(sedm, out)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const SEDM sedm(img, SEDM::DIST_TO_WHITE, SEDM::DIST_TO_NO_BORDERS); + BOOST_CHECK(verifySEDM(sedm, out)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestScale.cpp b/imageproc/tests/TestScale.cpp index 9c398e9b1..32ec512f8 100644 --- a/imageproc/tests/TestScale.cpp +++ b/imageproc/tests/TestScale.cpp @@ -16,15 +16,15 @@ along with this program. If not, see . */ -#include "Scale.h" -#include "GrayImage.h" -#include "Utils.h" #include #include #include +#include #include #include -#include +#include "GrayImage.h" +#include "Scale.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -33,59 +33,59 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(ScaleTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - const GrayImage null_img; - BOOST_CHECK(scaleToGray(null_img, QSize(1, 1)).isNull()); + const GrayImage null_img; + BOOST_CHECK(scaleToGray(null_img, QSize(1, 1)).isNull()); } static bool fuzzyCompare(const QImage& img1, const QImage& img2) { - BOOST_REQUIRE(img1.size() == img2.size()); - - const int width = img1.width(); - const int height = img1.height(); - const uint8_t* line1 = img1.bits(); - const uint8_t* line2 = img2.bits(); - const int line1_bpl = img1.bytesPerLine(); - const int line2_bpl = img2.bytesPerLine(); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - if (std::abs(int(line1[x]) - int(line2[x])) > 1) { - return false; - } - } - line1 += line1_bpl; - line2 += line2_bpl; + BOOST_REQUIRE(img1.size() == img2.size()); + + const int width = img1.width(); + const int height = img1.height(); + const uint8_t* line1 = img1.bits(); + const uint8_t* line2 = img2.bits(); + const int line1_bpl = img1.bytesPerLine(); + const int line2_bpl = img2.bytesPerLine(); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (std::abs(int(line1[x]) - int(line2[x])) > 1) { + return false; + } } + line1 += line1_bpl; + line2 += line2_bpl; + } - return true; + return true; } static bool checkScale(const GrayImage& img, const QSize& new_size) { - const GrayImage scaled1(scaleToGray(img, new_size)); - const GrayImage scaled2(img.toQImage().scaled(new_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + const GrayImage scaled1(scaleToGray(img, new_size)); + const GrayImage scaled2(img.toQImage().scaled(new_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - return fuzzyCompare(scaled1, scaled2); + return fuzzyCompare(scaled1, scaled2); } BOOST_AUTO_TEST_CASE(test_random_image) { - GrayImage img(QSize(100, 100)); - uint8_t* line = img.data(); - for (int y = 0; y < img.height(); ++y) { - for (int x = 0; x < img.width(); ++x) { - line[x] = static_cast(rand() % 256); - } - line += img.stride(); + GrayImage img(QSize(100, 100)); + uint8_t* line = img.data(); + for (int y = 0; y < img.height(); ++y) { + for (int x = 0; x < img.width(); ++x) { + line[x] = static_cast(rand() % 256); } - - // Unfortunately scaleToGray() and QImage::scaled() - // produce too different results when upscaling. - - BOOST_CHECK(checkScale(img, QSize(50, 50))); - // BOOST_CHECK(checkScale(img, QSize(200, 200))); - BOOST_CHECK(checkScale(img, QSize(80, 80))); - // BOOST_CHECK(checkScale(img, QSize(140, 140))); - // BOOST_CHECK(checkScale(img, QSize(55, 145))); - // BOOST_CHECK(checkScale(img, QSize(145, 55))); + line += img.stride(); + } + + // Unfortunately scaleToGray() and QImage::scaled() + // produce too different results when upscaling. + + BOOST_CHECK(checkScale(img, QSize(50, 50))); + // BOOST_CHECK(checkScale(img, QSize(200, 200))); + BOOST_CHECK(checkScale(img, QSize(80, 80))); + // BOOST_CHECK(checkScale(img, QSize(140, 140))); + // BOOST_CHECK(checkScale(img, QSize(55, 145))); + // BOOST_CHECK(checkScale(img, QSize(145, 55))); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestSeedFill.cpp b/imageproc/tests/TestSeedFill.cpp index fdd096697..a5c2aca4a 100644 --- a/imageproc/tests/TestSeedFill.cpp +++ b/imageproc/tests/TestSeedFill.cpp @@ -16,16 +16,16 @@ along with this program. If not, see . */ -#include "SeedFill.h" -#include "Connectivity.h" -#include "BinaryImage.h" -#include "BWColor.h" -#include "Grayscale.h" -#include "Utils.h" #include -#include #include +#include #include +#include "BWColor.h" +#include "BinaryImage.h" +#include "Connectivity.h" +#include "Grayscale.h" +#include "SeedFill.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -34,129 +34,129 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(SeedFillTestSuite); BOOST_AUTO_TEST_CASE(test_regression_1) { - int seed_data[70 * 2] = {0}; - int mask_data[70 * 2] = {0}; + int seed_data[70 * 2] = {0}; + int mask_data[70 * 2] = {0}; - seed_data[32] = 1; - seed_data[64] = 1; + seed_data[32] = 1; + seed_data[64] = 1; - mask_data[32] = 1; - mask_data[64] = 1; - mask_data[70 + 31] = 1; - mask_data[70 + 63] = 1; + mask_data[32] = 1; + mask_data[64] = 1; + mask_data[70 + 31] = 1; + mask_data[70 + 63] = 1; - const BinaryImage seed(makeBinaryImage(seed_data, 70, 2)); - const BinaryImage mask(makeBinaryImage(mask_data, 70, 2)); - BOOST_CHECK(seedFill(seed, mask, CONN8) == mask); + const BinaryImage seed(makeBinaryImage(seed_data, 70, 2)); + const BinaryImage mask(makeBinaryImage(mask_data, 70, 2)); + BOOST_CHECK(seedFill(seed, mask, CONN8) == mask); } BOOST_AUTO_TEST_CASE(test_regression_2) { - int seed_data[70 * 2] = {0}; - int mask_data[70 * 2] = {0}; + int seed_data[70 * 2] = {0}; + int mask_data[70 * 2] = {0}; - seed_data[32] = 1; - seed_data[64] = 1; + seed_data[32] = 1; + seed_data[64] = 1; - mask_data[31] = 1; - mask_data[63] = 1; - mask_data[70 + 31] = 1; - mask_data[70 + 63] = 1; + mask_data[31] = 1; + mask_data[63] = 1; + mask_data[70 + 31] = 1; + mask_data[70 + 63] = 1; - const BinaryImage seed(makeBinaryImage(seed_data, 70, 2)); - const BinaryImage mask(makeBinaryImage(mask_data, 70, 2)); - BOOST_CHECK(seedFill(seed, mask, CONN8) == BinaryImage(70, 2, WHITE)); + const BinaryImage seed(makeBinaryImage(seed_data, 70, 2)); + const BinaryImage mask(makeBinaryImage(mask_data, 70, 2)); + BOOST_CHECK(seedFill(seed, mask, CONN8) == BinaryImage(70, 2, WHITE)); } BOOST_AUTO_TEST_CASE(test_regression_3) { - static const int seed_data[] = {1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0}; + static const int seed_data[] = {1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0}; - static const int mask_data[] = {0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1}; + static const int mask_data[] = {0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1}; - static const int fill_data[] = {0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}; + static const int fill_data[] = {0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}; - const BinaryImage seed(makeBinaryImage(seed_data, 5, 5)); - const BinaryImage mask(makeBinaryImage(mask_data, 5, 5)); - const BinaryImage fill(makeBinaryImage(fill_data, 5, 5)); + const BinaryImage seed(makeBinaryImage(seed_data, 5, 5)); + const BinaryImage mask(makeBinaryImage(mask_data, 5, 5)); + const BinaryImage fill(makeBinaryImage(fill_data, 5, 5)); - BOOST_REQUIRE(seedFill(seed, mask, CONN8) == fill); + BOOST_REQUIRE(seedFill(seed, mask, CONN8) == fill); } BOOST_AUTO_TEST_CASE(test_regression_4) { - static const int seed_data[] = {0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1}; + static const int seed_data[] = {0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1}; - static const int mask_data[] = {1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1}; + static const int mask_data[] = {1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1}; - static const int fill_data[] = {1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1}; + static const int fill_data[] = {1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1}; - const BinaryImage seed(makeBinaryImage(seed_data, 5, 5)); - const BinaryImage mask(makeBinaryImage(mask_data, 5, 5)); - const BinaryImage fill(makeBinaryImage(fill_data, 5, 5)); - BOOST_REQUIRE(seedFill(seed, mask, CONN4) == fill); + const BinaryImage seed(makeBinaryImage(seed_data, 5, 5)); + const BinaryImage mask(makeBinaryImage(mask_data, 5, 5)); + const BinaryImage fill(makeBinaryImage(fill_data, 5, 5)); + BOOST_REQUIRE(seedFill(seed, mask, CONN4) == fill); } BOOST_AUTO_TEST_CASE(test_gray4_random) { - for (int i = 0; i < 200; ++i) { - const GrayImage seed(randomGrayImage(5, 5)); - const GrayImage mask(randomGrayImage(5, 5)); - const GrayImage fill_new(seedFillGray(seed, mask, CONN4)); - const GrayImage fill_old(seedFillGraySlow(seed, mask, CONN4)); - if (fill_new != fill_old) { - BOOST_ERROR("fill_new != fill_old at iteration " << i); - dumpGrayImage(seed, "seed"); - dumpGrayImage(mask, "mask"); - dumpGrayImage(fill_old, "fill_old"); - dumpGrayImage(fill_new, "fill_new"); - break; - } + for (int i = 0; i < 200; ++i) { + const GrayImage seed(randomGrayImage(5, 5)); + const GrayImage mask(randomGrayImage(5, 5)); + const GrayImage fill_new(seedFillGray(seed, mask, CONN4)); + const GrayImage fill_old(seedFillGraySlow(seed, mask, CONN4)); + if (fill_new != fill_old) { + BOOST_ERROR("fill_new != fill_old at iteration " << i); + dumpGrayImage(seed, "seed"); + dumpGrayImage(mask, "mask"); + dumpGrayImage(fill_old, "fill_old"); + dumpGrayImage(fill_new, "fill_new"); + break; } + } } BOOST_AUTO_TEST_CASE(test_gray8_random) { - for (int i = 0; i < 200; ++i) { - const GrayImage seed(randomGrayImage(5, 5)); - const GrayImage mask(randomGrayImage(5, 5)); - const GrayImage fill_new(seedFillGray(seed, mask, CONN8)); - const GrayImage fill_old(seedFillGraySlow(seed, mask, CONN8)); - if (fill_new != fill_old) { - BOOST_ERROR("fill_new != fill_old at iteration " << i); - dumpGrayImage(seed, "seed"); - dumpGrayImage(mask, "mask"); - dumpGrayImage(fill_old, "fill_old"); - dumpGrayImage(fill_new, "fill_new"); - break; - } + for (int i = 0; i < 200; ++i) { + const GrayImage seed(randomGrayImage(5, 5)); + const GrayImage mask(randomGrayImage(5, 5)); + const GrayImage fill_new(seedFillGray(seed, mask, CONN8)); + const GrayImage fill_old(seedFillGraySlow(seed, mask, CONN8)); + if (fill_new != fill_old) { + BOOST_ERROR("fill_new != fill_old at iteration " << i); + dumpGrayImage(seed, "seed"); + dumpGrayImage(mask, "mask"); + dumpGrayImage(fill_old, "fill_old"); + dumpGrayImage(fill_new, "fill_new"); + break; } + } } BOOST_AUTO_TEST_CASE(test_gray_vs_binary) { - for (int i = 0; i < 200; ++i) { - const BinaryImage bin_seed(randomBinaryImage(5, 5)); - const BinaryImage bin_mask(randomBinaryImage(5, 5)); - const GrayImage gray_seed(toGrayscale(bin_seed.toQImage())); - const GrayImage gray_mask(toGrayscale(bin_mask.toQImage())); - const BinaryImage fill_bin4(seedFill(bin_seed, bin_mask, CONN4)); - const BinaryImage fill_bin8(seedFill(bin_seed, bin_mask, CONN8)); - const GrayImage fill_gray4(seedFillGray(gray_seed, gray_mask, CONN4)); - const GrayImage fill_gray8(seedFillGray(gray_seed, gray_mask, CONN8)); - - if (fill_gray4 != GrayImage(fill_bin4.toQImage())) { - BOOST_ERROR("grayscale 4-fill != binary 4-fill at index " << i); - dumpBinaryImage(bin_seed, "seed"); - dumpBinaryImage(bin_mask, "mask"); - dumpBinaryImage(fill_bin4, "bin_fill"); - dumpBinaryImage(BinaryImage(fill_gray4), "gray_fill"); - break; - } - - if (fill_gray8 != GrayImage(fill_bin8.toQImage())) { - BOOST_ERROR("grayscale 8-fill != binary 8-fill at index " << i); - dumpBinaryImage(bin_seed, "seed"); - dumpBinaryImage(bin_mask, "mask"); - dumpBinaryImage(fill_bin8, "bin_fill"); - dumpBinaryImage(BinaryImage(fill_gray8), "gray_fill"); - break; - } + for (int i = 0; i < 200; ++i) { + const BinaryImage bin_seed(randomBinaryImage(5, 5)); + const BinaryImage bin_mask(randomBinaryImage(5, 5)); + const GrayImage gray_seed(toGrayscale(bin_seed.toQImage())); + const GrayImage gray_mask(toGrayscale(bin_mask.toQImage())); + const BinaryImage fill_bin4(seedFill(bin_seed, bin_mask, CONN4)); + const BinaryImage fill_bin8(seedFill(bin_seed, bin_mask, CONN8)); + const GrayImage fill_gray4(seedFillGray(gray_seed, gray_mask, CONN4)); + const GrayImage fill_gray8(seedFillGray(gray_seed, gray_mask, CONN8)); + + if (fill_gray4 != GrayImage(fill_bin4.toQImage())) { + BOOST_ERROR("grayscale 4-fill != binary 4-fill at index " << i); + dumpBinaryImage(bin_seed, "seed"); + dumpBinaryImage(bin_mask, "mask"); + dumpBinaryImage(fill_bin4, "bin_fill"); + dumpBinaryImage(BinaryImage(fill_gray4), "gray_fill"); + break; + } + + if (fill_gray8 != GrayImage(fill_bin8.toQImage())) { + BOOST_ERROR("grayscale 8-fill != binary 8-fill at index " << i); + dumpBinaryImage(bin_seed, "seed"); + dumpBinaryImage(bin_mask, "mask"); + dumpBinaryImage(fill_bin8, "bin_fill"); + dumpBinaryImage(BinaryImage(fill_gray8), "gray_fill"); + break; } + } } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestShear.cpp b/imageproc/tests/TestShear.cpp index d7c5fcae6..0db91836f 100644 --- a/imageproc/tests/TestShear.cpp +++ b/imageproc/tests/TestShear.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ -#include "Shear.h" -#include "BinaryImage.h" -#include "BWColor.h" -#include "Utils.h" #include #include +#include "BWColor.h" +#include "BinaryImage.h" +#include "Shear.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -30,35 +30,35 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(ShearTestSuite); BOOST_AUTO_TEST_CASE(test_small_image) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int h_out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const int h_out[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - static const int v_out[] = {0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; + static const int v_out[] = {0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); - const BinaryImage h_out_img(makeBinaryImage(h_out, 9, 9)); - const BinaryImage v_out_img(makeBinaryImage(v_out, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage h_out_img(makeBinaryImage(h_out, 9, 9)); + const BinaryImage v_out_img(makeBinaryImage(v_out, 9, 9)); - const BinaryImage h_shear = hShear(img, -1.0, 0.5 * img.height(), WHITE); - BOOST_REQUIRE(h_shear == h_out_img); + const BinaryImage h_shear = hShear(img, -1.0, 0.5 * img.height(), WHITE); + BOOST_REQUIRE(h_shear == h_out_img); - const BinaryImage v_shear = vShear(img, 1.0, 0.5 * img.width(), WHITE); - BOOST_REQUIRE(v_shear == v_out_img); + const BinaryImage v_shear = vShear(img, 1.0, 0.5 * img.width(), WHITE); + BOOST_REQUIRE(v_shear == v_out_img); - BinaryImage h_shear_inplace(img); - hShearInPlace(h_shear_inplace, -1.0, 0.5 * img.height(), WHITE); - BOOST_REQUIRE(h_shear_inplace == h_out_img); + BinaryImage h_shear_inplace(img); + hShearInPlace(h_shear_inplace, -1.0, 0.5 * img.height(), WHITE); + BOOST_REQUIRE(h_shear_inplace == h_out_img); - BinaryImage v_shear_inplace(img); - vShearInPlace(v_shear_inplace, 1.0, 0.5 * img.width(), WHITE); - BOOST_REQUIRE(v_shear_inplace == v_out_img); + BinaryImage v_shear_inplace(img); + vShearInPlace(v_shear_inplace, 1.0, 0.5 * img.width(), WHITE); + BOOST_REQUIRE(v_shear_inplace == v_out_img); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestSkewFinder.cpp b/imageproc/tests/TestSkewFinder.cpp index 7961f655a..ea4a3be38 100644 --- a/imageproc/tests/TestSkewFinder.cpp +++ b/imageproc/tests/TestSkewFinder.cpp @@ -16,74 +16,74 @@ along with this program. If not, see . */ -#include "SkewFinder.h" -#include "BinaryImage.h" #include +#include #include #include -#include -#include #include +#include #include #include #include +#include "BinaryImage.h" +#include "SkewFinder.h" namespace imageproc { namespace tests { BOOST_AUTO_TEST_SUITE(SkewFinderTestSuite); BOOST_AUTO_TEST_CASE(test_positive_detection) { - int argc = 1; - char argv0[] = "test"; - char* argv[1] = {argv0}; - QApplication app(argc, argv); - QImage image(1000, 800, QImage::Format_ARGB32_Premultiplied); - image.fill(0xffffffff); - { - QPainter painter(&image); - painter.setPen(QColor(0, 0, 0)); - QTransform xform1; - xform1.translate(-0.5 * image.width(), -0.5 * image.height()); - QTransform xform2; - xform2.rotate(4.5); - QTransform xform3; - xform3.translate(0.5 * image.width(), 0.5 * image.height()); - painter.setWorldTransform(xform1 * xform2 * xform3); + int argc = 1; + char argv0[] = "test"; + char* argv[1] = {argv0}; + QApplication app(argc, argv); + QImage image(1000, 800, QImage::Format_ARGB32_Premultiplied); + image.fill(0xffffffff); + { + QPainter painter(&image); + painter.setPen(QColor(0, 0, 0)); + QTransform xform1; + xform1.translate(-0.5 * image.width(), -0.5 * image.height()); + QTransform xform2; + xform2.rotate(4.5); + QTransform xform3; + xform3.translate(0.5 * image.width(), 0.5 * image.height()); + painter.setWorldTransform(xform1 * xform2 * xform3); - QString text; - for (int line = 0; line < 40; ++line) { - for (int i = 0; i < 100; ++i) { - text += '1'; - } - text += '\n'; - } - QTextOption opt; - opt.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - painter.drawText(image.rect(), text, opt); + QString text; + for (int line = 0; line < 40; ++line) { + for (int i = 0; i < 100; ++i) { + text += '1'; + } + text += '\n'; } + QTextOption opt; + opt.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + painter.drawText(image.rect(), text, opt); + } - SkewFinder skew_finder; - const Skew skew(skew_finder.findSkew(BinaryImage(image))); - BOOST_REQUIRE(std::fabs(skew.angle() - 4.5) < 0.15); - BOOST_CHECK(skew.confidence() >= Skew::GOOD_CONFIDENCE); + SkewFinder skew_finder; + const Skew skew(skew_finder.findSkew(BinaryImage(image))); + BOOST_REQUIRE(std::fabs(skew.angle() - 4.5) < 0.15); + BOOST_CHECK(skew.confidence() >= Skew::GOOD_CONFIDENCE); } BOOST_AUTO_TEST_CASE(test_negative_detection) { - QImage image(1000, 800, QImage::Format_Mono); - image.fill(1); + QImage image(1000, 800, QImage::Format_Mono); + image.fill(1); - const int num_dots = image.width() * image.height() / 5; - for (int i = 0; i < num_dots; ++i) { - const int x = rand() % image.width(); - const int y = rand() % image.height(); - image.setPixel(x, y, 0); - } + const int num_dots = image.width() * image.height() / 5; + for (int i = 0; i < num_dots; ++i) { + const int x = rand() % image.width(); + const int y = rand() % image.height(); + image.setPixel(x, y, 0); + } - SkewFinder skew_finder; - skew_finder.setCoarseReduction(0); - skew_finder.setFineReduction(0); - const Skew skew(skew_finder.findSkew(BinaryImage(image))); - BOOST_CHECK(skew.confidence() < Skew::GOOD_CONFIDENCE); + SkewFinder skew_finder; + skew_finder.setCoarseReduction(0); + skew_finder.setFineReduction(0); + const Skew skew(skew_finder.findSkew(BinaryImage(image))); + BOOST_CHECK(skew.confidence() < Skew::GOOD_CONFIDENCE); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestSlicedHistogram.cpp b/imageproc/tests/TestSlicedHistogram.cpp index 8d6359907..64c88a841 100644 --- a/imageproc/tests/TestSlicedHistogram.cpp +++ b/imageproc/tests/TestSlicedHistogram.cpp @@ -16,72 +16,72 @@ along with this program. If not, see . */ -#include "SlicedHistogram.h" -#include "BinaryImage.h" -#include "Utils.h" #include -#include -#include #include +#include +#include +#include "BinaryImage.h" +#include "SlicedHistogram.h" +#include "Utils.h" namespace imageproc { namespace tests { using namespace utils; static bool checkHistogram(const SlicedHistogram& hist, const int* data_begin, const int* data_end) { - if (hist.size() != size_t(data_end - data_begin)) { - return false; - } - for (unsigned i = 0; i < hist.size(); ++i) { - if (hist[i] != data_begin[i]) { - return false; - } + if (hist.size() != size_t(data_end - data_begin)) { + return false; + } + for (unsigned i = 0; i < hist.size(); ++i) { + if (hist[i] != data_begin[i]) { + return false; } + } - return true; + return true; } BOOST_AUTO_TEST_SUITE(SlicedHistogramTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - const BinaryImage null_img; + const BinaryImage null_img; - SlicedHistogram hor_hist(null_img, SlicedHistogram::ROWS); - BOOST_CHECK(hor_hist.size() == 0); + SlicedHistogram hor_hist(null_img, SlicedHistogram::ROWS); + BOOST_CHECK(hor_hist.size() == 0); - SlicedHistogram ver_hist(null_img, SlicedHistogram::COLS); - BOOST_CHECK(ver_hist.size() == 0); + SlicedHistogram ver_hist(null_img, SlicedHistogram::COLS); + BOOST_CHECK(ver_hist.size() == 0); } BOOST_AUTO_TEST_CASE(test_exceeding_area) { - const BinaryImage img(1, 1); - const QRect area(0, 0, 1, 2); + const BinaryImage img(1, 1); + const QRect area(0, 0, 1, 2); - BOOST_CHECK_THROW(SlicedHistogram(img, area, SlicedHistogram::ROWS), std::invalid_argument); + BOOST_CHECK_THROW(SlicedHistogram(img, area, SlicedHistogram::ROWS), std::invalid_argument); } BOOST_AUTO_TEST_CASE(test_small_image) { - static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, - 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + static const int inp[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; - static const int hor_counts[] = {0, 1, 2, 3, 9, 2, 6, 3, 1}; + static const int hor_counts[] = {0, 1, 2, 3, 9, 2, 6, 3, 1}; - static const int ver_counts[] = {2, 2, 4, 4, 5, 3, 2, 3, 2}; + static const int ver_counts[] = {2, 2, 4, 4, 5, 3, 2, 3, 2}; - const BinaryImage img(makeBinaryImage(inp, 9, 9)); + const BinaryImage img(makeBinaryImage(inp, 9, 9)); - SlicedHistogram hor_hist(img, SlicedHistogram::ROWS); - BOOST_CHECK(checkHistogram(hor_hist, hor_counts, hor_counts + 9)); + SlicedHistogram hor_hist(img, SlicedHistogram::ROWS); + BOOST_CHECK(checkHistogram(hor_hist, hor_counts, hor_counts + 9)); - SlicedHistogram ver_hist(img, SlicedHistogram::COLS); - BOOST_CHECK(checkHistogram(ver_hist, ver_counts, ver_counts + 9)); + SlicedHistogram ver_hist(img, SlicedHistogram::COLS); + BOOST_CHECK(checkHistogram(ver_hist, ver_counts, ver_counts + 9)); - hor_hist = SlicedHistogram(img, img.rect().adjusted(0, 1, 0, 0), SlicedHistogram::ROWS); - BOOST_CHECK(checkHistogram(hor_hist, hor_counts + 1, hor_counts + 9)); + hor_hist = SlicedHistogram(img, img.rect().adjusted(0, 1, 0, 0), SlicedHistogram::ROWS); + BOOST_CHECK(checkHistogram(hor_hist, hor_counts + 1, hor_counts + 9)); - ver_hist = SlicedHistogram(img, img.rect().adjusted(1, 0, 0, 0), SlicedHistogram::COLS); - BOOST_CHECK(checkHistogram(ver_hist, ver_counts + 1, ver_counts + 9)); + ver_hist = SlicedHistogram(img, img.rect().adjusted(1, 0, 0, 0), SlicedHistogram::COLS); + BOOST_CHECK(checkHistogram(ver_hist, ver_counts + 1, ver_counts + 9)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/TestTransform.cpp b/imageproc/tests/TestTransform.cpp index 523ec9983..cc8098e5a 100644 --- a/imageproc/tests/TestTransform.cpp +++ b/imageproc/tests/TestTransform.cpp @@ -16,15 +16,15 @@ along with this program. If not, see . */ -#include "Transform.h" -#include "Grayscale.h" -#include "Utils.h" #include #include #include +#include #include #include -#include +#include "Grayscale.h" +#include "Transform.h" +#include "Utils.h" namespace imageproc { namespace tests { @@ -33,29 +33,29 @@ using namespace utils; BOOST_AUTO_TEST_SUITE(TransformTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { - const QImage null_img; - const QTransform null_xform; - const QRect unit_rect(0, 0, 1, 1); - const QColor bgcolor(0xff, 0xff, 0xff); - const OutsidePixels outside_pixels(OutsidePixels::assumeColor(bgcolor)); - BOOST_CHECK(transformToGray(null_img, null_xform, unit_rect, outside_pixels).isNull()); + const QImage null_img; + const QTransform null_xform; + const QRect unit_rect(0, 0, 1, 1); + const QColor bgcolor(0xff, 0xff, 0xff); + const OutsidePixels outside_pixels(OutsidePixels::assumeColor(bgcolor)); + BOOST_CHECK(transformToGray(null_img, null_xform, unit_rect, outside_pixels).isNull()); } BOOST_AUTO_TEST_CASE(test_random_image) { - GrayImage img(QSize(100, 100)); - uint8_t* line = img.data(); - for (int y = 0; y < img.height(); ++y) { - for (int x = 0; x < img.width(); ++x) { - line[x] = static_cast(rand() % 256); - } - line += img.stride(); + GrayImage img(QSize(100, 100)); + uint8_t* line = img.data(); + for (int y = 0; y < img.height(); ++y) { + for (int x = 0; x < img.width(); ++x) { + line[x] = static_cast(rand() % 256); } + line += img.stride(); + } - const QColor bgcolor(0xff, 0xff, 0xff); - const OutsidePixels outside_pixels(OutsidePixels::assumeColor(bgcolor)); + const QColor bgcolor(0xff, 0xff, 0xff); + const OutsidePixels outside_pixels(OutsidePixels::assumeColor(bgcolor)); - const QTransform null_xform; - BOOST_CHECK(transformToGray(img, null_xform, img.rect(), outside_pixels) == img); + const QTransform null_xform; + BOOST_CHECK(transformToGray(img, null_xform, img.rect(), outside_pixels) == img); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/imageproc/tests/Utils.cpp b/imageproc/tests/Utils.cpp index 6456c4fe3..3efd1278f 100644 --- a/imageproc/tests/Utils.cpp +++ b/imageproc/tests/Utils.cpp @@ -17,180 +17,180 @@ */ #include "Utils.h" -#include "BinaryImage.h" -#include "Grayscale.h" #include #include -#include +#include #include #include -#include +#include +#include "BinaryImage.h" +#include "Grayscale.h" namespace imageproc { namespace tests { namespace utils { BinaryImage randomBinaryImage(const int width, const int height) { - BinaryImage image(width, height); - uint32_t* pword = image.data(); - uint32_t* const end = pword + image.height() * image.wordsPerLine(); - for (; pword != end; ++pword) { - const uint32_t w1 = rand() % (1 << 16); - const uint32_t w2 = rand() % (1 << 16); - *pword = (w1 << 16) | w2; - } - - return image; + BinaryImage image(width, height); + uint32_t* pword = image.data(); + uint32_t* const end = pword + image.height() * image.wordsPerLine(); + for (; pword != end; ++pword) { + const uint32_t w1 = rand() % (1 << 16); + const uint32_t w2 = rand() % (1 << 16); + *pword = (w1 << 16) | w2; + } + + return image; } QImage randomMonoQImage(const int width, const int height) { - QImage image(width, height, QImage::Format_Mono); - image.setColorCount(2); - image.setColor(0, 0xffffffff); - image.setColor(1, 0xff000000); - auto* pword = (uint32_t*) image.bits(); - assert(image.bytesPerLine() % 4 == 0); - uint32_t* const end = pword + image.height() * (image.bytesPerLine() / 4); - for (; pword != end; ++pword) { - const uint32_t w1 = rand() % (1 << 16); - const uint32_t w2 = rand() % (1 << 16); - *pword = (w1 << 16) | w2; - } - - return image; + QImage image(width, height, QImage::Format_Mono); + image.setColorCount(2); + image.setColor(0, 0xffffffff); + image.setColor(1, 0xff000000); + auto* pword = (uint32_t*) image.bits(); + assert(image.bytesPerLine() % 4 == 0); + uint32_t* const end = pword + image.height() * (image.bytesPerLine() / 4); + for (; pword != end; ++pword) { + const uint32_t w1 = rand() % (1 << 16); + const uint32_t w2 = rand() % (1 << 16); + *pword = (w1 << 16) | w2; + } + + return image; } QImage randomGrayImage(int width, int height) { - QImage img(width, height, QImage::Format_Indexed8); - img.setColorTable(createGrayscalePalette()); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - img.setPixel(x, y, rand() % 10); - } + QImage img(width, height, QImage::Format_Indexed8); + img.setColorTable(createGrayscalePalette()); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + img.setPixel(x, y, rand() % 10); } + } - return img; + return img; } BinaryImage makeBinaryImage(const int* data, const int width, const int height) { - return BinaryImage(makeMonoQImage(data, width, height)); + return BinaryImage(makeMonoQImage(data, width, height)); } QImage makeMonoQImage(const int* data, const int width, const int height) { - QImage img(width, height, QImage::Format_Mono); - img.setColorCount(2); - img.setColor(0, 0xffffffff); - img.setColor(1, 0xff000000); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - img.setPixel(x, y, data[y * width + x] ? 1 : 0); - } + QImage img(width, height, QImage::Format_Mono); + img.setColorCount(2); + img.setColor(0, 0xffffffff); + img.setColor(1, 0xff000000); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + img.setPixel(x, y, data[y * width + x] ? 1 : 0); } + } - return img; + return img; } QImage makeGrayImage(const int* data, const int width, const int height) { - QImage img(width, height, QImage::Format_Indexed8); - img.setColorTable(createGrayscalePalette()); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - img.setPixel(x, y, data[y * width + x]); - } + QImage img(width, height, QImage::Format_Indexed8); + img.setColorTable(createGrayscalePalette()); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + img.setPixel(x, y, data[y * width + x]); } + } - return img; + return img; } void dumpBinaryImage(const BinaryImage& img, const char* name) { - if (name) { - std::cout << name << " = "; - } - - if (img.isNull()) { - std::cout << "NULL image" << std::endl; - - return; - } - - const int width = img.width(); - const int height = img.height(); - const uint32_t* line = img.data(); - const int wpl = img.wordsPerLine(); - - std::cout << "{\n"; - for (int y = 0; y < height; ++y, line += wpl) { - std::cout << "\t"; - for (int x = 0; x < width; ++x) { - std::cout << ((line[x >> 5] >> (31 - (x & 31))) & 1) << ", "; - } - std::cout << "\n"; - } - std::cout << "}" << std::endl; + if (name) { + std::cout << name << " = "; + } + + if (img.isNull()) { + std::cout << "NULL image" << std::endl; + + return; + } + + const int width = img.width(); + const int height = img.height(); + const uint32_t* line = img.data(); + const int wpl = img.wordsPerLine(); + + std::cout << "{\n"; + for (int y = 0; y < height; ++y, line += wpl) { + std::cout << "\t"; + for (int x = 0; x < width; ++x) { + std::cout << ((line[x >> 5] >> (31 - (x & 31))) & 1) << ", "; + } + std::cout << "\n"; + } + std::cout << "}" << std::endl; } void dumpGrayImage(const QImage& img, const char* name) { - if (name) { - std::cout << name << " = "; - } - - if (img.isNull()) { - std::cout << "NULL image" << std::endl; - - return; - } - if (img.format() != QImage::Format_Indexed8) { - std::cout << "Not grayscale image" << std::endl; - } - - const int width = img.width(); - const int height = img.height(); - - std::cout << "{\n"; - for (int y = 0; y < height; ++y) { - std::cout << "\t"; - for (int x = 0; x < width; ++x) { - std::cout << img.pixelIndex(x, y) << ", "; - } - std::cout << "\n"; - } - std::cout << "}" << std::endl; + if (name) { + std::cout << name << " = "; + } + + if (img.isNull()) { + std::cout << "NULL image" << std::endl; + + return; + } + if (img.format() != QImage::Format_Indexed8) { + std::cout << "Not grayscale image" << std::endl; + } + + const int width = img.width(); + const int height = img.height(); + + std::cout << "{\n"; + for (int y = 0; y < height; ++y) { + std::cout << "\t"; + for (int x = 0; x < width; ++x) { + std::cout << img.pixelIndex(x, y) << ", "; + } + std::cout << "\n"; + } + std::cout << "}" << std::endl; } bool surroundingsIntact(const QImage& img1, const QImage& img2, const QRect& rect) { - assert(img1.size() == img2.size()); + assert(img1.size() == img2.size()); - const int w = img1.width(); - const int h = img1.height(); + const int w = img1.width(); + const int h = img1.height(); - if (rect.left() != 0) { - const QRect left_of(0, 0, rect.x(), h); - if (img1.copy(left_of) != img2.copy(left_of)) { - return false; - } + if (rect.left() != 0) { + const QRect left_of(0, 0, rect.x(), h); + if (img1.copy(left_of) != img2.copy(left_of)) { + return false; } + } - if (rect.right() != img1.rect().right()) { - const QRect right_of(rect.x() + w, 0, w - rect.x() - rect.width(), h); - if (img1.copy(right_of) != img2.copy(right_of)) { - return false; - } + if (rect.right() != img1.rect().right()) { + const QRect right_of(rect.x() + w, 0, w - rect.x() - rect.width(), h); + if (img1.copy(right_of) != img2.copy(right_of)) { + return false; } + } - if (rect.top() != 0) { - const QRect top_of(0, 0, w, rect.y()); - if (img1.copy(top_of) != img2.copy(top_of)) { - return false; - } + if (rect.top() != 0) { + const QRect top_of(0, 0, w, rect.y()); + if (img1.copy(top_of) != img2.copy(top_of)) { + return false; } + } - if (rect.bottom() != img1.rect().bottom()) { - const QRect bottom_of(0, rect.y() + rect.height(), w, h - rect.y() - rect.height()); - if (img1.copy(bottom_of) != img2.copy(bottom_of)) { - return false; - } + if (rect.bottom() != img1.rect().bottom()) { + const QRect bottom_of(0, rect.y() + rect.height(), w, h - rect.y() - rect.height()); + if (img1.copy(bottom_of) != img2.copy(bottom_of)) { + return false; } + } - return true; + return true; } // surroundingsIntact } // namespace utils } // namespace tests diff --git a/interaction/CMakeLists.txt b/interaction/CMakeLists.txt index 5af929c8c..a04988c86 100644 --- a/interaction/CMakeLists.txt +++ b/interaction/CMakeLists.txt @@ -1,25 +1,25 @@ -PROJECT(interaction) +project(interaction) -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_BINARY_DIR}") -SET( - sources - DraggableObject.h - DraggablePoint.cpp DraggablePoint.h - DraggableLineSegment.cpp DraggableLineSegment.h - ObjectDragHandler.cpp ObjectDragHandler.h - InteractionHandler.cpp InteractionHandler.h - InteractionState.cpp InteractionState.h - DragHandler.cpp DragHandler.h - DragWatcher.cpp DragWatcher.h - ZoomHandler.cpp ZoomHandler.h - InteractiveXSpline.cpp InteractiveXSpline.h - DraggablePolygon.cpp DraggablePolygon.h) +set( + sources + DraggableObject.h + DraggablePoint.cpp DraggablePoint.h + DraggableLineSegment.cpp DraggableLineSegment.h + ObjectDragHandler.cpp ObjectDragHandler.h + InteractionHandler.cpp InteractionHandler.h + InteractionState.cpp InteractionState.h + DragHandler.cpp DragHandler.h + DragWatcher.cpp DragWatcher.h + ZoomHandler.cpp ZoomHandler.h + InteractiveXSpline.cpp InteractiveXSpline.h + DraggablePolygon.cpp DraggablePolygon.h) -SOURCE_GROUP(Sources FILES ${sources}) +source_group(Sources FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(interaction STATIC ${sources}) +add_library(interaction STATIC ${sources}) -TRANSLATION_SOURCES(scantailor ${sources}) \ No newline at end of file +translation_sources(scantailor ${sources}) \ No newline at end of file diff --git a/interaction/DragHandler.cpp b/interaction/DragHandler.cpp index ee7639613..e88dd9be4 100644 --- a/interaction/DragHandler.cpp +++ b/interaction/DragHandler.cpp @@ -17,62 +17,62 @@ */ #include "DragHandler.h" -#include "ImageViewBase.h" #include +#include "ImageViewBase.h" DragHandler::DragHandler(ImageViewBase& image_view) - : m_rImageView(image_view), m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter) { - init(); + : m_imageView(image_view), m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter) { + init(); } DragHandler::DragHandler(ImageViewBase& image_view, const boost::function& explicit_interaction_permitter) - : m_rImageView(image_view), m_interactionPermitter(explicit_interaction_permitter) { - init(); + : m_imageView(image_view), m_interactionPermitter(explicit_interaction_permitter) { + init(); } void DragHandler::init() { - m_interaction.setInteractionStatusTip(tr("Unrestricted dragging is possible by holding down the Shift key.")); + m_interaction.setInteractionStatusTip(tr("Unrestricted dragging is possible by holding down the Shift key.")); } bool DragHandler::isActive() const { - return m_rImageView.interactionState().capturedBy(m_interaction); + return m_imageView.interactionState().capturedBy(m_interaction); } void DragHandler::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - m_lastMousePos = event->pos(); + m_lastMousePos = event->pos(); - if ((event->buttons() & (Qt::LeftButton | Qt::MidButton)) && !interaction.capturedBy(m_interaction) - && m_interactionPermitter(interaction)) { - interaction.capture(m_interaction); - } + if ((event->buttons() & (Qt::LeftButton | Qt::MidButton)) && !interaction.capturedBy(m_interaction) + && m_interactionPermitter(interaction)) { + interaction.capture(m_interaction); + } } void DragHandler::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if (interaction.capturedBy(m_interaction)) { - m_interaction.release(); - event->accept(); - } + if (!interaction.capturedBy(m_interaction)) { + return; + } + + m_interaction.release(); + event->accept(); } void DragHandler::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - if (!((event->modifiers() == Qt::NoModifier) || (event->modifiers() & Qt::ShiftModifier))) { - return; - } - - if (interaction.capturedBy(m_interaction)) { - QPoint movement(event->pos()); - movement -= m_lastMousePos; - m_lastMousePos = event->pos(); - - QPointF adjusted_fp(m_rImageView.getWidgetFocalPoint()); - adjusted_fp += movement; - - // These will call update() if necessary. - if (event->modifiers() & Qt::ShiftModifier) { - m_rImageView.setWidgetFocalPoint(adjusted_fp); - } else { - m_rImageView.adjustAndSetWidgetFocalPoint(adjusted_fp); - } - } + if (!interaction.capturedBy(m_interaction)) { + return; + } + + QPoint movement(event->pos()); + movement -= m_lastMousePos; + m_lastMousePos = event->pos(); + + QPointF adjusted_fp(m_imageView.getWidgetFocalPoint()); + adjusted_fp += movement; + + // These will call update() if necessary. + if (event->modifiers() & Qt::ShiftModifier) { + m_imageView.setWidgetFocalPoint(adjusted_fp); + } else { + m_imageView.adjustAndSetWidgetFocalPoint(adjusted_fp); + } } diff --git a/interaction/DragHandler.h b/interaction/DragHandler.h index eaa6e5afd..d20f5c3e9 100644 --- a/interaction/DragHandler.h +++ b/interaction/DragHandler.h @@ -21,38 +21,38 @@ #define BOOST_SIGNALS_NAMESPACE signal -#include "InteractionHandler.h" -#include "InteractionState.h" -#include #include +#include #include +#include "InteractionHandler.h" +#include "InteractionState.h" class ImageViewBase; class DragHandler : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(DragHandler) -public: - explicit DragHandler(ImageViewBase& image_view); + Q_DECLARE_TR_FUNCTIONS(DragHandler) + public: + explicit DragHandler(ImageViewBase& image_view); - DragHandler(ImageViewBase& image_view, - const boost::function& explicit_interaction_permitter); + DragHandler(ImageViewBase& image_view, + const boost::function& explicit_interaction_permitter); - bool isActive() const; + bool isActive() const; -protected: - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + protected: + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; -private: - void init(); + private: + void init(); - ImageViewBase& m_rImageView; - InteractionState::Captor m_interaction; - QPoint m_lastMousePos; - boost::function m_interactionPermitter; + ImageViewBase& m_imageView; + InteractionState::Captor m_interaction; + QPoint m_lastMousePos; + boost::function m_interactionPermitter; }; diff --git a/interaction/DragWatcher.cpp b/interaction/DragWatcher.cpp index f6ea3bc2f..24907c2f3 100644 --- a/interaction/DragWatcher.cpp +++ b/interaction/DragWatcher.cpp @@ -17,54 +17,53 @@ */ #include "DragWatcher.h" -#include "DragHandler.h" -#include #include +#include +#include "DragHandler.h" DragWatcher::DragWatcher(DragHandler& drag_handler) - : m_rDragHandler(drag_handler), m_dragMaxSqDist(0), m_dragInProgress(false) { -} + : m_dragHandler(drag_handler), m_dragMaxSqDist(0), m_dragInProgress(false) {} bool DragWatcher::haveSignificantDrag() const { - if (!m_dragInProgress) { - return false; - } + if (!m_dragInProgress) { + return false; + } - const QDateTime now(QDateTime::currentDateTime()); - qint64 msec_passed = m_dragStartTime.time().msecsTo(now.time()); - if (msec_passed < 0) { - msec_passed += 60 * 60 * 24; - } + const QDateTime now(QDateTime::currentDateTime()); + qint64 msec_passed = m_dragStartTime.time().msecsTo(now.time()); + if (msec_passed < 0) { + msec_passed += 60 * 60 * 24; + } - const double dist_score = std::sqrt((double) m_dragMaxSqDist) / 12.0; - const double time_score = msec_passed / 500.0; + const double dist_score = std::sqrt((double) m_dragMaxSqDist) / 12.0; + const double time_score = msec_passed / 500.0; - return dist_score + time_score >= 1.0; + return dist_score + time_score >= 1.0; } void DragWatcher::onMousePressEvent(QMouseEvent* event, InteractionState&) { - updateState(event->pos()); + updateState(event->pos()); } void DragWatcher::onMouseMoveEvent(QMouseEvent* event, InteractionState&) { - updateState(event->pos()); + updateState(event->pos()); } void DragWatcher::updateState(const QPoint mouse_pos) { - if (m_rDragHandler.isActive()) { - if (!m_dragInProgress) { - m_dragStartTime = QDateTime::currentDateTime(); - m_dragStartPos = mouse_pos; - m_dragMaxSqDist = 0; - } else { - const QPoint delta(mouse_pos - m_dragStartPos); - const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); - if (sqdist > m_dragMaxSqDist) { - m_dragMaxSqDist = sqdist; - } - } - m_dragInProgress = true; + if (m_dragHandler.isActive()) { + if (!m_dragInProgress) { + m_dragStartTime = QDateTime::currentDateTime(); + m_dragStartPos = mouse_pos; + m_dragMaxSqDist = 0; } else { - m_dragInProgress = false; + const QPoint delta(mouse_pos - m_dragStartPos); + const int sqdist = delta.x() * delta.x() + delta.y() * delta.y(); + if (sqdist > m_dragMaxSqDist) { + m_dragMaxSqDist = sqdist; + } } + m_dragInProgress = true; + } else { + m_dragInProgress = false; + } } diff --git a/interaction/DragWatcher.h b/interaction/DragWatcher.h index a9013054f..bf59078f7 100644 --- a/interaction/DragWatcher.h +++ b/interaction/DragWatcher.h @@ -19,9 +19,9 @@ #ifndef DRAG_WATCHER_H_ #define DRAG_WATCHER_H_ -#include "InteractionHandler.h" -#include #include +#include +#include "InteractionHandler.h" class DragHandler; class InteractionState; @@ -34,24 +34,24 @@ class QMouseEvent; * case we could perform some other operation on mouse release. */ class DragWatcher : public InteractionHandler { -public: - explicit DragWatcher(DragHandler& drag_handler); + public: + explicit DragWatcher(DragHandler& drag_handler); - bool haveSignificantDrag() const; + bool haveSignificantDrag() const; -protected: - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + protected: + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; -private: - void updateState(QPoint mouse_pos); + private: + void updateState(QPoint mouse_pos); - DragHandler& m_rDragHandler; - QDateTime m_dragStartTime; - QPoint m_dragStartPos; - int m_dragMaxSqDist; - bool m_dragInProgress; + DragHandler& m_dragHandler; + QDateTime m_dragStartTime; + QPoint m_dragStartPos; + int m_dragMaxSqDist; + bool m_dragInProgress; }; diff --git a/interaction/DraggableLineSegment.cpp b/interaction/DraggableLineSegment.cpp index 7cd7ae233..892f0327c 100644 --- a/interaction/DraggableLineSegment.cpp +++ b/interaction/DraggableLineSegment.cpp @@ -17,25 +17,24 @@ */ #include "DraggableLineSegment.h" -#include "ImageViewBase.h" #include +#include "ImageViewBase.h" -DraggableLineSegment::DraggableLineSegment() : m_proximityPriority(0) { -} +DraggableLineSegment::DraggableLineSegment() : m_proximityPriority(0) {} int DraggableLineSegment::proximityPriority() const { - return m_proximityPriority; + return m_proximityPriority; } Proximity DraggableLineSegment::proximity(const QPointF& mouse_pos) { - return Proximity::pointAndLineSegment(mouse_pos, lineSegmentPosition()); + return Proximity::pointAndLineSegment(mouse_pos, lineSegmentPosition()); } void DraggableLineSegment::dragInitiated(const QPointF& mouse_pos) { - m_initialMousePos = mouse_pos; - m_initialLinePos = lineSegmentPosition(); + m_initialMousePos = mouse_pos; + m_initialLinePos = lineSegmentPosition(); } void DraggableLineSegment::dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) { - lineSegmentMoveRequest(m_initialLinePos.translated(mouse_pos - m_initialMousePos), mask); + lineSegmentMoveRequest(m_initialLinePos.translated(mouse_pos - m_initialMousePos), mask); } diff --git a/interaction/DraggableLineSegment.h b/interaction/DraggableLineSegment.h index d080590ed..ee59508a9 100644 --- a/interaction/DraggableLineSegment.h +++ b/interaction/DraggableLineSegment.h @@ -19,56 +19,48 @@ #ifndef DRAGGABLE_LINE_SEGMENT_H_ #define DRAGGABLE_LINE_SEGMENT_H_ -#include "DraggableObject.h" -#include #include +#include #include +#include "DraggableObject.h" class ObjectDragHandler; class DraggableLineSegment : public DraggableObject { -public: - typedef boost::function PositionCallback; + public: + typedef boost::function PositionCallback; - typedef boost::function MoveRequestCallback; + typedef boost::function MoveRequestCallback; - DraggableLineSegment(); + DraggableLineSegment(); - void setProximityPriority(int priority) { - m_proximityPriority = priority; - } + void setProximityPriority(int priority) { m_proximityPriority = priority; } - int proximityPriority() const override; + int proximityPriority() const override; - Proximity proximity(const QPointF& mouse_pos) override; + Proximity proximity(const QPointF& mouse_pos) override; - void dragInitiated(const QPointF& mouse_pos) override; + void dragInitiated(const QPointF& mouse_pos) override; - void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; + void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; - void setPositionCallback(const PositionCallback& callback) { - m_positionCallback = callback; - } + void setPositionCallback(const PositionCallback& callback) { m_positionCallback = callback; } - void setMoveRequestCallback(const MoveRequestCallback& callback) { - m_moveRequestCallback = callback; - } + void setMoveRequestCallback(const MoveRequestCallback& callback) { m_moveRequestCallback = callback; } -protected: - virtual QLineF lineSegmentPosition() const { - return m_positionCallback(); - } + protected: + virtual QLineF lineSegmentPosition() const { return m_positionCallback(); } - virtual void lineSegmentMoveRequest(const QLineF& line, Qt::KeyboardModifiers mask) { - m_moveRequestCallback(line, mask); - } + virtual void lineSegmentMoveRequest(const QLineF& line, Qt::KeyboardModifiers mask) { + m_moveRequestCallback(line, mask); + } -private: - PositionCallback m_positionCallback; - MoveRequestCallback m_moveRequestCallback; - QPointF m_initialMousePos; - QLineF m_initialLinePos; - int m_proximityPriority; + private: + PositionCallback m_positionCallback; + MoveRequestCallback m_moveRequestCallback; + QPointF m_initialMousePos; + QLineF m_initialLinePos; + int m_proximityPriority; }; diff --git a/interaction/DraggableObject.h b/interaction/DraggableObject.h index e6619a015..448b643cd 100644 --- a/interaction/DraggableObject.h +++ b/interaction/DraggableObject.h @@ -19,9 +19,9 @@ #ifndef DRAGGABLE_OBJECT_H_ #define DRAGGABLE_OBJECT_H_ +#include #include "InteractionState.h" #include "Proximity.h" -#include class ObjectDragHandler; class QPoint; @@ -29,133 +29,108 @@ class QPointF; class QPainter; class DraggableObject { -public: - typedef boost::function PaintCallback; - - typedef boost::function ProximityThresholdCallback; - - typedef boost::function ProximityPriorityCallback; - - typedef boost::function ProximityCallback; - - typedef boost::function DragInitiatedCallback; - - typedef boost::function DragContinuationCallback; - - typedef boost::function DragFinishedCallback; - - DraggableObject() - : m_paintCallback(&DraggableObject::defaultPaint), - m_proximityThresholdCallback(&DraggableObject::defaultProximityThreshold), - m_proximityPriorityCallback(&DraggableObject::defaultProximityPriority), - m_proximityCallback(), - m_dragInitiatedCallback(), - m_dragContinuationCallback(), - m_dragFinishedCallback(&DraggableObject::defaultDragFinished) { - } - - virtual ~DraggableObject() = default; - - virtual void paint(QPainter& painter, const InteractionState& interaction) { - m_paintCallback(painter, interaction); - } - - void setPaintCallback(const PaintCallback& callback) { - m_paintCallback = callback; - } - - /** - * \return The maximum distance from the object (in widget coordinates) that - * still allows to initiate a dragging operation. - */ - virtual Proximity proximityThreshold(const InteractionState& interaction) const { - return m_proximityThresholdCallback(interaction); - } - - void setProximityThresholdCallback(const ProximityThresholdCallback& callback) { - m_proximityThresholdCallback = callback; - } - - /** - * Sometimes a more distant object should be selected for dragging in favor of - * a closer one. Consider for example a line segment with handles at its endpoints. - * In this example, you would assign higher priority to those handles. - */ - virtual int proximityPriority() const { - return m_proximityPriorityCallback(); - } - - void setProximityPriorityCallback(const ProximityPriorityCallback& callback) { - m_proximityPriorityCallback = callback; - } - - /** - * \return The proximity from the mouse position in widget coordinates to - * any draggable part of the object. - */ - virtual Proximity proximity(const QPointF& widget_mouse_pos) { - return m_proximityCallback(widget_mouse_pos); - } - - void setProximityCallback(const ProximityCallback& callback) { - m_proximityCallback = callback; - } - - /** - * \brief Called when dragging is initiated, that is when the mouse button is pressed. - */ - virtual void dragInitiated(const QPointF& mouse_pos) { - m_dragInitiatedCallback(mouse_pos); - } - - void setDragInitiatedCallback(const DragInitiatedCallback& callback) { - m_dragInitiatedCallback = callback; - } - - /** - * \brief Handles a request to move to a particular position in widget coordinates. - */ - virtual void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) { - m_dragContinuationCallback(mouse_pos); - } - - void setDragContinuationCallback(const DragInitiatedCallback& callback) { - m_dragContinuationCallback = callback; - } - - /** - * \brief Called when dragging is finished, that is when the mouse button is released. - */ - virtual void dragFinished(const QPointF& mouse_pos) { - m_dragFinishedCallback(mouse_pos); - } - - void setDragFinishedCallback(const DragFinishedCallback& callback) { - m_dragFinishedCallback = callback; - } - -private: - static void defaultPaint(QPainter&, const InteractionState&) { - } - - static Proximity defaultProximityThreshold(const InteractionState& interaction) { - return interaction.proximityThreshold(); - } - - static int defaultProximityPriority() { - return 0; - } - - static void defaultDragFinished(const QPointF&) { - } - - PaintCallback m_paintCallback; - ProximityThresholdCallback m_proximityThresholdCallback; - ProximityPriorityCallback m_proximityPriorityCallback; - ProximityCallback m_proximityCallback; - DragInitiatedCallback m_dragInitiatedCallback; - DragContinuationCallback m_dragContinuationCallback; - DragFinishedCallback m_dragFinishedCallback; + public: + typedef boost::function PaintCallback; + + typedef boost::function ProximityThresholdCallback; + + typedef boost::function ProximityPriorityCallback; + + typedef boost::function ProximityCallback; + + typedef boost::function DragInitiatedCallback; + + typedef boost::function DragContinuationCallback; + + typedef boost::function DragFinishedCallback; + + DraggableObject() + : m_paintCallback(&DraggableObject::defaultPaint), + m_proximityThresholdCallback(&DraggableObject::defaultProximityThreshold), + m_proximityPriorityCallback(&DraggableObject::defaultProximityPriority), + m_proximityCallback(), + m_dragInitiatedCallback(), + m_dragContinuationCallback(), + m_dragFinishedCallback(&DraggableObject::defaultDragFinished) {} + + virtual ~DraggableObject() = default; + + virtual void paint(QPainter& painter, const InteractionState& interaction) { m_paintCallback(painter, interaction); } + + void setPaintCallback(const PaintCallback& callback) { m_paintCallback = callback; } + + /** + * \return The maximum distance from the object (in widget coordinates) that + * still allows to initiate a dragging operation. + */ + virtual Proximity proximityThreshold(const InteractionState& interaction) const { + return m_proximityThresholdCallback(interaction); + } + + void setProximityThresholdCallback(const ProximityThresholdCallback& callback) { + m_proximityThresholdCallback = callback; + } + + /** + * Sometimes a more distant object should be selected for dragging in favor of + * a closer one. Consider for example a line segment with handles at its endpoints. + * In this example, you would assign higher priority to those handles. + */ + virtual int proximityPriority() const { return m_proximityPriorityCallback(); } + + void setProximityPriorityCallback(const ProximityPriorityCallback& callback) { + m_proximityPriorityCallback = callback; + } + + /** + * \return The proximity from the mouse position in widget coordinates to + * any draggable part of the object. + */ + virtual Proximity proximity(const QPointF& widget_mouse_pos) { return m_proximityCallback(widget_mouse_pos); } + + void setProximityCallback(const ProximityCallback& callback) { m_proximityCallback = callback; } + + /** + * \brief Called when dragging is initiated, that is when the mouse button is pressed. + */ + virtual void dragInitiated(const QPointF& mouse_pos) { m_dragInitiatedCallback(mouse_pos); } + + void setDragInitiatedCallback(const DragInitiatedCallback& callback) { m_dragInitiatedCallback = callback; } + + /** + * \brief Handles a request to move to a particular position in widget coordinates. + */ + virtual void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) { + m_dragContinuationCallback(mouse_pos, mask); + } + + void setDragContinuationCallback(const DragContinuationCallback& callback) { m_dragContinuationCallback = callback; } + + /** + * \brief Called when dragging is finished, that is when the mouse button is released. + */ + virtual void dragFinished(const QPointF& mouse_pos) { m_dragFinishedCallback(mouse_pos); } + + void setDragFinishedCallback(const DragFinishedCallback& callback) { m_dragFinishedCallback = callback; } + + private: + static void defaultPaint(QPainter&, const InteractionState&) {} + + static Proximity defaultProximityThreshold(const InteractionState& interaction) { + return interaction.proximityThreshold(); + } + + static int defaultProximityPriority() { return 0; } + + static void defaultDragFinished(const QPointF&) {} + + PaintCallback m_paintCallback; + ProximityThresholdCallback m_proximityThresholdCallback; + ProximityPriorityCallback m_proximityPriorityCallback; + ProximityCallback m_proximityCallback; + DragInitiatedCallback m_dragInitiatedCallback; + DragContinuationCallback m_dragContinuationCallback; + DragFinishedCallback m_dragFinishedCallback; }; diff --git a/interaction/DraggablePoint.cpp b/interaction/DraggablePoint.cpp index 55233e2d6..396ab1123 100644 --- a/interaction/DraggablePoint.cpp +++ b/interaction/DraggablePoint.cpp @@ -19,29 +19,28 @@ #include "DraggablePoint.h" #include "ImageViewBase.h" -DraggablePoint::DraggablePoint() : m_hitAreaRadius(), m_proximityPriority(1) { -} +DraggablePoint::DraggablePoint() : m_hitAreaRadius(), m_proximityPriority(1) {} Proximity DraggablePoint::proximityThreshold(const InteractionState& state) const { - if (m_hitAreaRadius == 0.0) { - return state.proximityThreshold(); - } else { - return Proximity::fromDist(m_hitAreaRadius); - } + if (m_hitAreaRadius == 0.0) { + return state.proximityThreshold(); + } else { + return Proximity::fromDist(m_hitAreaRadius); + } } int DraggablePoint::proximityPriority() const { - return m_proximityPriority; + return m_proximityPriority; } Proximity DraggablePoint::proximity(const QPointF& mouse_pos) { - return Proximity(pointPosition(), mouse_pos); + return Proximity(pointPosition(), mouse_pos); } void DraggablePoint::dragInitiated(const QPointF& mouse_pos) { - m_pointRelativeToMouse = pointPosition() - mouse_pos; + m_pointRelativeToMouse = pointPosition() - mouse_pos; } void DraggablePoint::dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) { - pointMoveRequest(mouse_pos + m_pointRelativeToMouse, mask); + pointMoveRequest(mouse_pos + m_pointRelativeToMouse, mask); } diff --git a/interaction/DraggablePoint.h b/interaction/DraggablePoint.h index 08ad49b72..6dce1d1f9 100644 --- a/interaction/DraggablePoint.h +++ b/interaction/DraggablePoint.h @@ -19,67 +19,55 @@ #ifndef DRAGGABLE_POINT_H_ #define DRAGGABLE_POINT_H_ -#include "DraggableObject.h" #include #include +#include "DraggableObject.h" class DraggablePoint : public DraggableObject { -public: - typedef boost::function PositionCallback; + public: + typedef boost::function PositionCallback; - typedef boost::function MoveRequestCallback; + typedef boost::function MoveRequestCallback; - DraggablePoint(); + DraggablePoint(); - /** - * Returns the hit area radius, with zero indicating the global - * proximity threshold of InteractionState is to be used. - */ - double hitRadius() const { - return m_hitAreaRadius; - } + /** + * Returns the hit area radius, with zero indicating the global + * proximity threshold of InteractionState is to be used. + */ + double hitRadius() const { return m_hitAreaRadius; } - void setHitRadius(double radius) { - m_hitAreaRadius = radius; - } + void setHitRadius(double radius) { m_hitAreaRadius = radius; } - Proximity proximityThreshold(const InteractionState& interaction) const override; + Proximity proximityThreshold(const InteractionState& interaction) const override; - void setProximityPriority(int priority) { - m_proximityPriority = priority; - } + void setProximityPriority(int priority) { m_proximityPriority = priority; } - int proximityPriority() const override; + int proximityPriority() const override; - Proximity proximity(const QPointF& mouse_pos) override; + Proximity proximity(const QPointF& mouse_pos) override; - void dragInitiated(const QPointF& mouse_pos) override; + void dragInitiated(const QPointF& mouse_pos) override; - void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; + void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; - void setPositionCallback(const PositionCallback& callback) { - m_positionCallback = callback; - } + void setPositionCallback(const PositionCallback& callback) { m_positionCallback = callback; } - void setMoveRequestCallback(const MoveRequestCallback& callback) { - m_moveRequestCallback = callback; - } + void setMoveRequestCallback(const MoveRequestCallback& callback) { m_moveRequestCallback = callback; } -protected: - virtual QPointF pointPosition() const { - return m_positionCallback(); - } + protected: + virtual QPointF pointPosition() const { return m_positionCallback(); } - virtual void pointMoveRequest(const QPointF& widget_pos, Qt::KeyboardModifiers mask) { - m_moveRequestCallback(widget_pos, mask); - } + virtual void pointMoveRequest(const QPointF& widget_pos, Qt::KeyboardModifiers mask) { + m_moveRequestCallback(widget_pos, mask); + } -private: - PositionCallback m_positionCallback; - MoveRequestCallback m_moveRequestCallback; - QPointF m_pointRelativeToMouse; - double m_hitAreaRadius; - int m_proximityPriority; + private: + PositionCallback m_positionCallback; + MoveRequestCallback m_moveRequestCallback; + QPointF m_pointRelativeToMouse; + double m_hitAreaRadius; + int m_proximityPriority; }; diff --git a/interaction/DraggablePolygon.cpp b/interaction/DraggablePolygon.cpp index 75c22f176..3821632cc 100644 --- a/interaction/DraggablePolygon.cpp +++ b/interaction/DraggablePolygon.cpp @@ -1,25 +1,24 @@ #include "DraggablePolygon.h" -#include "ImageViewBase.h" #include +#include "ImageViewBase.h" -DraggablePolygon::DraggablePolygon() : m_proximityPriority(0) { -} +DraggablePolygon::DraggablePolygon() : m_proximityPriority(0) {} int DraggablePolygon::proximityPriority() const { - return m_proximityPriority; + return m_proximityPriority; } Proximity DraggablePolygon::proximity(const QPointF& mouse_pos) { - double value = polygonPosition().containsPoint(mouse_pos, Qt::WindingFill) ? 0 : std::numeric_limits::max(); - return Proximity::fromSqDist(value); + double value = polygonPosition().containsPoint(mouse_pos, Qt::WindingFill) ? 0 : std::numeric_limits::max(); + return Proximity::fromSqDist(value); } void DraggablePolygon::dragInitiated(const QPointF& mouse_pos) { - m_initialMousePos = mouse_pos; - m_initialPolygonPos = polygonPosition(); + m_initialMousePos = mouse_pos; + m_initialPolygonPos = polygonPosition(); } void DraggablePolygon::dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) { - polygonMoveRequest(m_initialPolygonPos.translated(mouse_pos - m_initialMousePos)); + polygonMoveRequest(m_initialPolygonPos.translated(mouse_pos - m_initialMousePos)); } diff --git a/interaction/DraggablePolygon.h b/interaction/DraggablePolygon.h index f20dd3bb1..55dfea628 100644 --- a/interaction/DraggablePolygon.h +++ b/interaction/DraggablePolygon.h @@ -2,56 +2,46 @@ #ifndef SCANTAILOR_DRAGGABLEPOLYGON_H #define SCANTAILOR_DRAGGABLEPOLYGON_H -#include "DraggableObject.h" #include #include #include +#include "DraggableObject.h" class ObjectDragHandler; class DraggablePolygon : public DraggableObject { -public: - typedef boost::function PositionCallback; + public: + typedef boost::function PositionCallback; - typedef boost::function MoveRequestCallback; + typedef boost::function MoveRequestCallback; - DraggablePolygon(); + DraggablePolygon(); - int proximityPriority() const override; + int proximityPriority() const override; - Proximity proximity(const QPointF& mouse_pos) override; + Proximity proximity(const QPointF& mouse_pos) override; - void dragInitiated(const QPointF& mouse_pos) override; + void dragInitiated(const QPointF& mouse_pos) override; - void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; + void dragContinuation(const QPointF& mouse_pos, Qt::KeyboardModifiers mask) override; - void setProximityPriority(int priority) { - m_proximityPriority = priority; - } + void setProximityPriority(int priority) { m_proximityPriority = priority; } - void setPositionCallback(const PositionCallback& callback) { - m_positionCallback = callback; - } + void setPositionCallback(const PositionCallback& callback) { m_positionCallback = callback; } - void setMoveRequestCallback(const MoveRequestCallback& callback) { - m_moveRequestCallback = callback; - } + void setMoveRequestCallback(const MoveRequestCallback& callback) { m_moveRequestCallback = callback; } -protected: - virtual QPolygonF polygonPosition() const { - return m_positionCallback(); - } + protected: + virtual QPolygonF polygonPosition() const { return m_positionCallback(); } - virtual void polygonMoveRequest(const QPolygonF& polygon) { - m_moveRequestCallback(polygon); - } + virtual void polygonMoveRequest(const QPolygonF& polygon) { m_moveRequestCallback(polygon); } -private: - PositionCallback m_positionCallback; - MoveRequestCallback m_moveRequestCallback; - QPointF m_initialMousePos; - QPolygonF m_initialPolygonPos; - int m_proximityPriority; + private: + PositionCallback m_positionCallback; + MoveRequestCallback m_moveRequestCallback; + QPointF m_initialMousePos; + QPolygonF m_initialPolygonPos; + int m_proximityPriority; }; diff --git a/interaction/InteractionHandler.cpp b/interaction/InteractionHandler.cpp index ee5beb441..94b08db22 100644 --- a/interaction/InteractionHandler.cpp +++ b/interaction/InteractionHandler.cpp @@ -17,226 +17,224 @@ */ #include "InteractionHandler.h" -#include "InteractionState.h" -#include #include -#include -#include +#include #include +#include +#include +#include "InteractionState.h" -#define DISPATCH(list, call) \ - { \ - HandlerList::iterator it(list->begin()); \ - const HandlerList::iterator end(list->end()); \ - while (it != end) { \ - (it++)->call; \ - } \ - } - -#define RETURN_IF_ACCEPTED(event) \ - { \ - if (event->isAccepted()) { \ - return; \ - } \ - } +#define DISPATCH(list, call) \ + { \ + HandlerList::iterator it(list->begin()); \ + const HandlerList::iterator end(list->end()); \ + while (it != end) { \ + (it++)->call; \ + } \ + } + +#define RETURN_IF_ACCEPTED(event) \ + { \ + if (event->isAccepted()) { \ + return; \ + } \ + } namespace { class ScopedClearAcceptance { - DECLARE_NON_COPYABLE(ScopedClearAcceptance) + DECLARE_NON_COPYABLE(ScopedClearAcceptance) -public: - explicit ScopedClearAcceptance(QEvent* event); + public: + explicit ScopedClearAcceptance(QEvent* event); - ~ScopedClearAcceptance(); + ~ScopedClearAcceptance(); -private: - QEvent* m_pEvent; - bool m_wasAccepted; + private: + QEvent* m_event; + bool m_wasAccepted; }; -ScopedClearAcceptance::ScopedClearAcceptance(QEvent* event) : m_pEvent(event), m_wasAccepted(event->isAccepted()) { - m_pEvent->setAccepted(false); +ScopedClearAcceptance::ScopedClearAcceptance(QEvent* event) : m_event(event), m_wasAccepted(event->isAccepted()) { + m_event->setAccepted(false); } ScopedClearAcceptance::~ScopedClearAcceptance() { - if (m_wasAccepted) { - m_pEvent->setAccepted(true); - } + if (m_wasAccepted) { + m_event->setAccepted(true); + } } } // anonymous namespace -InteractionHandler::InteractionHandler() : m_ptrPreceeders(new HandlerList), m_ptrFollowers(new HandlerList) { -} +InteractionHandler::InteractionHandler() : m_preceeders(new HandlerList), m_followers(new HandlerList) {} InteractionHandler::~InteractionHandler() { - using namespace boost::lambda; - m_ptrPreceeders->clear_and_dispose(bind(delete_ptr(), _1)); - m_ptrFollowers->clear_and_dispose(bind(delete_ptr(), _1)); + using namespace boost::lambda; + m_preceeders->clear_and_dispose(bind(delete_ptr(), _1)); + m_followers->clear_and_dispose(bind(delete_ptr(), _1)); } void InteractionHandler::paint(QPainter& painter, const InteractionState& interaction) { - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, paint(painter, interaction)); - painter.save(); - onPaint(painter, interaction); - painter.restore(); - DISPATCH(followers, paint(painter, interaction)); + DISPATCH(preceeders, paint(painter, interaction)); + painter.save(); + onPaint(painter, interaction); + painter.restore(); + DISPATCH(followers, paint(painter, interaction)); } void InteractionHandler::proximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) { - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, proximityUpdate(screen_mouse_pos, interaction)); - onProximityUpdate(screen_mouse_pos, interaction); - assert(!interaction.captured() && "onProximityUpdate() must not capture interaction"); - DISPATCH(followers, proximityUpdate(screen_mouse_pos, interaction)); + DISPATCH(preceeders, proximityUpdate(screen_mouse_pos, interaction)); + onProximityUpdate(screen_mouse_pos, interaction); + assert(!interaction.captured() && "onProximityUpdate() must not capture interaction"); + DISPATCH(followers, proximityUpdate(screen_mouse_pos, interaction)); } void InteractionHandler::keyPressEvent(QKeyEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); + RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, keyPressEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onKeyPressEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, keyPressEvent(event, interaction)); + DISPATCH(preceeders, keyPressEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onKeyPressEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, keyPressEvent(event, interaction)); } void InteractionHandler::keyReleaseEvent(QKeyEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + RETURN_IF_ACCEPTED(event); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, keyReleaseEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onKeyReleaseEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, keyReleaseEvent(event, interaction)); + DISPATCH(preceeders, keyReleaseEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onKeyReleaseEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, keyReleaseEvent(event, interaction)); } void InteractionHandler::mousePressEvent(QMouseEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); + RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, mousePressEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onMousePressEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, mousePressEvent(event, interaction)); + DISPATCH(preceeders, mousePressEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onMousePressEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, mousePressEvent(event, interaction)); } void InteractionHandler::mouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + RETURN_IF_ACCEPTED(event); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, mouseReleaseEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onMouseReleaseEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, mouseReleaseEvent(event, interaction)); + DISPATCH(preceeders, mouseReleaseEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onMouseReleaseEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, mouseReleaseEvent(event, interaction)); } void InteractionHandler::mouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + RETURN_IF_ACCEPTED(event); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, mouseDoubleClickEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onMouseDoubleClickEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, mouseDoubleClickEvent(event, interaction)); + DISPATCH(preceeders, mouseDoubleClickEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onMouseDoubleClickEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, mouseDoubleClickEvent(event, interaction)); } void InteractionHandler::mouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); + RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, mouseMoveEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onMouseMoveEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, mouseMoveEvent(event, interaction)); + DISPATCH(preceeders, mouseMoveEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onMouseMoveEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, mouseMoveEvent(event, interaction)); } void InteractionHandler::wheelEvent(QWheelEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); + RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, wheelEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onWheelEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, wheelEvent(event, interaction)); + DISPATCH(preceeders, wheelEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onWheelEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, wheelEvent(event, interaction)); } void InteractionHandler::contextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { - RETURN_IF_ACCEPTED(event); - // Keep them alive in case this object gets destroyed. - intrusive_ptr preceeders(m_ptrPreceeders); - intrusive_ptr followers(m_ptrFollowers); + RETURN_IF_ACCEPTED(event); + // Keep them alive in case this object gets destroyed. + intrusive_ptr preceeders(m_preceeders); + intrusive_ptr followers(m_followers); - DISPATCH(preceeders, contextMenuEvent(event, interaction)); - RETURN_IF_ACCEPTED(event); - onContextMenuEvent(event, interaction); - ScopedClearAcceptance guard(event); - DISPATCH(followers, contextMenuEvent(event, interaction)); + DISPATCH(preceeders, contextMenuEvent(event, interaction)); + RETURN_IF_ACCEPTED(event); + onContextMenuEvent(event, interaction); + ScopedClearAcceptance guard(event); + DISPATCH(followers, contextMenuEvent(event, interaction)); } void InteractionHandler::makePeerPreceeder(InteractionHandler& handler) { - handler.unlink(); - HandlerList::node_algorithms::link_before(this, &handler); + handler.unlink(); + HandlerList::node_algorithms::link_before(this, &handler); } void InteractionHandler::makePeerFollower(InteractionHandler& handler) { - using namespace boost::intrusive; - handler.unlink(); - HandlerList::node_algorithms::link_after(this, &handler); + handler.unlink(); + HandlerList::node_algorithms::link_after(this, &handler); } void InteractionHandler::makeFirstPreceeder(InteractionHandler& handler) { - handler.unlink(); - m_ptrPreceeders->push_front(handler); + handler.unlink(); + m_preceeders->push_front(handler); } void InteractionHandler::makeLastPreceeder(InteractionHandler& handler) { - handler.unlink(); - m_ptrPreceeders->push_back(handler); + handler.unlink(); + m_preceeders->push_back(handler); } void InteractionHandler::makeFirstFollower(InteractionHandler& handler) { - handler.unlink(); - m_ptrFollowers->push_front(handler); + handler.unlink(); + m_followers->push_front(handler); } void InteractionHandler::makeLastFollower(InteractionHandler& handler) { - handler.unlink(); - m_ptrFollowers->push_back(handler); + handler.unlink(); + m_followers->push_back(handler); } bool InteractionHandler::defaultInteractionPermitter(const InteractionState& interaction) { - return !interaction.captured(); + return !interaction.captured(); } diff --git a/interaction/InteractionHandler.h b/interaction/InteractionHandler.h index 3e8be7185..5470c2980 100644 --- a/interaction/InteractionHandler.h +++ b/interaction/InteractionHandler.h @@ -19,10 +19,10 @@ #ifndef INTERACTION_HANDLER_H_ #define INTERACTION_HANDLER_H_ +#include #include "NonCopyable.h" -#include "ref_countable.h" #include "intrusive_ptr.h" -#include +#include "ref_countable.h" class InteractionState; class QPainter; @@ -33,87 +33,76 @@ class QContextMenuEvent; class QPointF; class InteractionHandler - : public boost::intrusive::list_base_hook> { - DECLARE_NON_COPYABLE(InteractionHandler) + : public boost::intrusive::list_base_hook> { + DECLARE_NON_COPYABLE(InteractionHandler) -public: - InteractionHandler(); + public: + InteractionHandler(); - virtual ~InteractionHandler(); + virtual ~InteractionHandler(); - void paint(QPainter& painter, const InteractionState& interaction); + void paint(QPainter& painter, const InteractionState& interaction); - void proximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction); + void proximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction); - void keyPressEvent(QKeyEvent* event, InteractionState& interaction); + void keyPressEvent(QKeyEvent* event, InteractionState& interaction); - void keyReleaseEvent(QKeyEvent* event, InteractionState& interaction); + void keyReleaseEvent(QKeyEvent* event, InteractionState& interaction); - void mousePressEvent(QMouseEvent* event, InteractionState& interaction); + void mousePressEvent(QMouseEvent* event, InteractionState& interaction); - void mouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); + void mouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); - void mouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction); + void mouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction); - void mouseMoveEvent(QMouseEvent* event, InteractionState& interaction); + void mouseMoveEvent(QMouseEvent* event, InteractionState& interaction); - void wheelEvent(QWheelEvent* event, InteractionState& interaction); + void wheelEvent(QWheelEvent* event, InteractionState& interaction); - void contextMenuEvent(QContextMenuEvent* event, InteractionState& interaction); + void contextMenuEvent(QContextMenuEvent* event, InteractionState& interaction); - void makePeerPreceeder(InteractionHandler& handler); + void makePeerPreceeder(InteractionHandler& handler); - void makePeerFollower(InteractionHandler& handler); + void makePeerFollower(InteractionHandler& handler); - void makeFirstPreceeder(InteractionHandler& handler); + void makeFirstPreceeder(InteractionHandler& handler); - void makeLastPreceeder(InteractionHandler& handler); + void makeLastPreceeder(InteractionHandler& handler); - void makeFirstFollower(InteractionHandler& handler); + void makeFirstFollower(InteractionHandler& handler); - void makeLastFollower(InteractionHandler& handler); + void makeLastFollower(InteractionHandler& handler); -protected: - virtual void onPaint(QPainter& painter, const InteractionState& interaction) { - } + protected: + virtual void onPaint(QPainter& painter, const InteractionState& interaction) {} - virtual void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) { - } + virtual void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) {} - virtual void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - } + virtual void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) {} - virtual void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) { - } + virtual void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) {} - virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - } + virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) {} - virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - } + virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) {} - virtual void onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) { - } + virtual void onMouseDoubleClickEvent(QMouseEvent* event, InteractionState& interaction) {} - virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - } + virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) {} - virtual void onWheelEvent(QWheelEvent* event, InteractionState& interaction) { - } + virtual void onWheelEvent(QWheelEvent* event, InteractionState& interaction) {} - virtual void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { - } + virtual void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) {} - static bool defaultInteractionPermitter(const InteractionState& interaction); + static bool defaultInteractionPermitter(const InteractionState& interaction); -private: - class HandlerList : public ref_countable, - public boost::intrusive::list> { - }; + private: + class HandlerList : public ref_countable, + public boost::intrusive::list> {}; - intrusive_ptr m_ptrPreceeders; - intrusive_ptr m_ptrFollowers; + intrusive_ptr m_preceeders; + intrusive_ptr m_followers; }; diff --git a/interaction/InteractionState.cpp b/interaction/InteractionState.cpp index 21e93632a..b57921ac0 100644 --- a/interaction/InteractionState.cpp +++ b/interaction/InteractionState.cpp @@ -19,87 +19,86 @@ #include "InteractionState.h" InteractionState::Captor& InteractionState::Captor::operator=(Captor& other) { - swap_nodes(other); - other.unlink(); + swap_nodes(other); + other.unlink(); - return *this; + return *this; } InteractionState::Captor& InteractionState::Captor::operator=(CopyHelper other) { - return (*this = *other.captor); + return (*this = *other.captor); } InteractionState::InteractionState() - : m_proximityThreshold(Proximity::fromDist(10.0)), - m_bestProximityPriority(std::numeric_limits::min()), - m_redrawRequested(false) { -} + : m_proximityThreshold(Proximity::fromDist(10.0)), + m_bestProximityPriority(std::numeric_limits::min()), + m_redrawRequested(false) {} void InteractionState::capture(Captor& captor) { - captor.unlink(); - m_captorList.push_back(captor); + captor.unlink(); + m_captorList.push_back(captor); } bool InteractionState::capturedBy(const Captor& captor) const { - return !m_captorList.empty() && &m_captorList.back() == &captor; + return !m_captorList.empty() && &m_captorList.back() == &captor; } void InteractionState::resetProximity() { - m_proximityLeader.clear(); - m_bestProximity = Proximity(); - m_bestProximityPriority = std::numeric_limits::min(); + m_proximityLeader.clear(); + m_bestProximity = Proximity(); + m_bestProximityPriority = std::numeric_limits::min(); } void InteractionState::updateProximity(Captor& captor, const Proximity& proximity, int priority, Proximity proximity_threshold) { - if (captor.is_linked()) { - return; - } - - if (proximity_threshold == Proximity()) { - proximity_threshold = m_proximityThreshold; - } - - if (proximity <= proximity_threshold) { - if (betterProximity(proximity, priority)) { - m_proximityLeader.clear(); - m_proximityLeader.push_back(captor); - m_bestProximity = proximity; - m_bestProximityPriority = priority; - } + if (captor.is_linked()) { + return; + } + + if (proximity_threshold == Proximity()) { + proximity_threshold = m_proximityThreshold; + } + + if (proximity <= proximity_threshold) { + if (betterProximity(proximity, priority)) { + m_proximityLeader.clear(); + m_proximityLeader.push_back(captor); + m_bestProximity = proximity; + m_bestProximityPriority = priority; } + } } bool InteractionState::proximityLeader(const Captor& captor) const { - return !m_proximityLeader.empty() && &m_proximityLeader.front() == &captor; + return !m_proximityLeader.empty() && &m_proximityLeader.front() == &captor; } bool InteractionState::betterProximity(const Proximity& proximity, const int priority) const { - if (priority != m_bestProximityPriority) { - return priority > m_bestProximityPriority; - } + if (priority != m_bestProximityPriority) { + return priority > m_bestProximityPriority; + } - return proximity < m_bestProximity; + return proximity < m_bestProximity; } QCursor InteractionState::cursor() const { - if (!m_captorList.empty()) { - return m_captorList.back().interactionCursor(); - } else if (!m_proximityLeader.empty()) { - return m_proximityLeader.front().proximityCursor(); - } else { - return QCursor(); - } + if (!m_captorList.empty()) { + return m_captorList.back().interactionCursor(); + } else if (!m_proximityLeader.empty()) { + return m_proximityLeader.front().proximityCursor(); + } else { + return QCursor(); + } } QString InteractionState::statusTip() const { - if (!m_captorList.empty()) { - return m_captorList.back().interactionOrProximityStatusTip(); - } else if (!m_proximityLeader.empty()) { - return m_proximityLeader.front().proximityStatusTip(); - } else { - return m_defaultStatusTip; - } + if (!m_captorList.empty()) { + return m_captorList.back().interactionOrProximityStatusTip(); + } else if (!m_proximityLeader.empty()) { + return m_proximityLeader.front().proximityStatusTip(); + } else { + return m_defaultStatusTip; + } } diff --git a/interaction/InteractionState.h b/interaction/InteractionState.h index 730024c00..6c36e80ac 100644 --- a/interaction/InteractionState.h +++ b/interaction/InteractionState.h @@ -19,154 +19,117 @@ #ifndef INTERACTION_STATE_H_ #define INTERACTION_STATE_H_ -#include "NonCopyable.h" -#include "Proximity.h" -#include #include #include +#include +#include "NonCopyable.h" +#include "Proximity.h" class Proximity; class InteractionState { - DECLARE_NON_COPYABLE(InteractionState) + DECLARE_NON_COPYABLE(InteractionState) -public: - class Captor : public boost::intrusive::list_base_hook> { - friend class InteractionState; + public: + class Captor : public boost::intrusive::list_base_hook> { + friend class InteractionState; - private: - struct CopyHelper { - Captor* captor; + private: + struct CopyHelper { + Captor* captor; - explicit CopyHelper(Captor* cap) : captor(cap) { - } - }; + explicit CopyHelper(Captor* cap) : captor(cap) {} + }; - public: - Captor() = default; + public: + Captor() = default; - Captor(Captor& other) { - swap_nodes(other); - } + Captor(Captor& other) { swap_nodes(other); } - explicit Captor(CopyHelper other) { - swap_nodes(*other.captor); - } + explicit Captor(CopyHelper other) { swap_nodes(*other.captor); } - Captor& operator=(Captor& other); + Captor& operator=(Captor& other); - Captor& operator=(CopyHelper other); + Captor& operator=(CopyHelper other); - explicit operator CopyHelper() { - return CopyHelper(this); - } + explicit operator CopyHelper() { return CopyHelper(this); } - void release() { - unlink(); - } + void release() { unlink(); } - const QCursor& proximityCursor() const { - return m_proximityCursor; - } + const QCursor& proximityCursor() const { return m_proximityCursor; } - void setProximityCursor(const QCursor& cursor) { - m_proximityCursor = cursor; - } + void setProximityCursor(const QCursor& cursor) { m_proximityCursor = cursor; } - const QCursor& interactionCursor() const { - return m_interactionCursor; - } + const QCursor& interactionCursor() const { return m_interactionCursor; } - void setInteractionCursor(const QCursor& cursor) { - m_interactionCursor = cursor; - } + void setInteractionCursor(const QCursor& cursor) { m_interactionCursor = cursor; } - const QString& proximityStatusTip() const { - return m_proximityStatusTip; - } + const QString& proximityStatusTip() const { return m_proximityStatusTip; } - void setProximityStatusTip(const QString& tip) { - m_proximityStatusTip = tip; - } + void setProximityStatusTip(const QString& tip) { m_proximityStatusTip = tip; } - const QString& interactionStatusTip() const { - return m_interactionStatusTip; - } + const QString& interactionStatusTip() const { return m_interactionStatusTip; } - void setInteractionStatusTip(const QString& tip) { - m_interactionStatusTip = tip; - } + void setInteractionStatusTip(const QString& tip) { m_interactionStatusTip = tip; } - const QString& interactionOrProximityStatusTip() const { - return m_interactionStatusTip.isNull() ? m_proximityStatusTip : m_interactionStatusTip; - } + const QString& interactionOrProximityStatusTip() const { + return m_interactionStatusTip.isNull() ? m_proximityStatusTip : m_interactionStatusTip; + } - private: - QCursor m_proximityCursor; - QCursor m_interactionCursor; - QString m_proximityStatusTip; - QString m_interactionStatusTip; - }; + private: + QCursor m_proximityCursor; + QCursor m_interactionCursor; + QString m_proximityStatusTip; + QString m_interactionStatusTip; + }; - InteractionState(); + InteractionState(); - void capture(Captor& captor); + void capture(Captor& captor); - bool captured() const { - return !m_captorList.empty(); - } + bool captured() const { return !m_captorList.empty(); } - bool capturedBy(const Captor& captor) const; + bool capturedBy(const Captor& captor) const; - void resetProximity(); + void resetProximity(); - void updateProximity(Captor& captor, - const Proximity& proximity, - int priority = 0, - Proximity proximity_threshold = Proximity()); + void updateProximity(Captor& captor, + const Proximity& proximity, + int priority = 0, + Proximity proximity_threshold = Proximity()); - bool proximityLeader(const Captor& captor) const; + bool proximityLeader(const Captor& captor) const; - const Proximity& proximityThreshold() const { - return m_proximityThreshold; - } + const Proximity& proximityThreshold() const { return m_proximityThreshold; } - QCursor cursor() const; + QCursor cursor() const; - QString statusTip() const; + QString statusTip() const; - const QString& defaultStatusTip() const { - return m_defaultStatusTip; - } + const QString& defaultStatusTip() const { return m_defaultStatusTip; } - void setDefaultStatusTip(const QString& status_tip) { - m_defaultStatusTip = status_tip; - } + void setDefaultStatusTip(const QString& status_tip) { m_defaultStatusTip = status_tip; } - bool redrawRequested() const { - return m_redrawRequested; - } + bool redrawRequested() const { return m_redrawRequested; } - void setRedrawRequested(bool requested) { - m_redrawRequested = requested; - } + void setRedrawRequested(bool requested) { m_redrawRequested = requested; } + + private: + typedef boost::intrusive::list> CaptorList; + + /** + * Returns true if the provided proximity is better than the stored one. + */ + bool betterProximity(const Proximity& proximity, int priority) const; -private: - typedef boost::intrusive::list> CaptorList; - - /** - * Returns true if the provided proximity is better than the stored one. - */ - bool betterProximity(const Proximity& proximity, int priority) const; - - QString m_defaultStatusTip; - CaptorList m_captorList; - CaptorList m_proximityLeader; - Proximity m_bestProximity; - Proximity m_proximityThreshold; - int m_bestProximityPriority; - bool m_redrawRequested; + QString m_defaultStatusTip; + CaptorList m_captorList; + CaptorList m_proximityLeader; + Proximity m_bestProximity; + Proximity m_proximityThreshold; + int m_bestProximityPriority; + bool m_redrawRequested; }; diff --git a/interaction/InteractiveXSpline.cpp b/interaction/InteractiveXSpline.cpp index 7134862c1..a38ee0278 100644 --- a/interaction/InteractiveXSpline.cpp +++ b/interaction/InteractiveXSpline.cpp @@ -17,13 +17,13 @@ */ #include "InteractiveXSpline.h" -#include "Proximity.h" -#include "VecNT.h" -#include "MatrixCalc.h" #include +#include #include #include -#include +#include "MatrixCalc.h" +#include "Proximity.h" +#include "VecNT.h" #ifndef Q_MOC_RUN @@ -32,245 +32,240 @@ #endif struct InteractiveXSpline::NoOp { - void operator()() const { - } + void operator()() const {} }; struct InteractiveXSpline::IdentTransform { - QPointF operator()(const QPointF& pt) const { - return pt; - } + QPointF operator()(const QPointF& pt) const { return pt; } }; InteractiveXSpline::InteractiveXSpline() - : m_modifiedCallback(NoOp()), - m_dragFinishedCallback(NoOp()), - m_fromStorage(IdentTransform()), - m_toStorage(IdentTransform()), - m_curveProximityT(), - m_lastProximity(false) { - m_curveProximity.setProximityCursor(Qt::PointingHandCursor); - m_curveProximity.setProximityStatusTip(tr("Click to create a new control point.")); + : m_modifiedCallback(NoOp()), + m_dragFinishedCallback(NoOp()), + m_fromStorage(IdentTransform()), + m_toStorage(IdentTransform()), + m_curveProximityT(), + m_lastProximity(false) { + m_curveProximity.setProximityCursor(Qt::PointingHandCursor); + m_curveProximity.setProximityStatusTip(tr("Click to create a new control point.")); } void InteractiveXSpline::setSpline(const XSpline& spline) { - const int num_control_points = spline.numControlPoints(); - - XSpline new_spline(spline); - boost::scoped_array new_control_points(new ControlPoint[num_control_points]); - - for (int i = 0; i < num_control_points; ++i) { - new_control_points[i].point.setPositionCallback( - boost::bind(&InteractiveXSpline::controlPointPosition, this, i)); - new_control_points[i].point.setMoveRequestCallback( - boost::bind(&InteractiveXSpline::controlPointMoveRequest, this, i, _1, _2)); - new_control_points[i].point.setDragFinishedCallback(boost::bind(&InteractiveXSpline::dragFinished, this)); - - if ((i == 0) || (i == num_control_points - 1)) { - // Endpoints can't be deleted. - new_control_points[i].handler.setProximityStatusTip( - tr("This point can be dragged. Hold Ctrl or Shift to drag along axes.")); - } else { - new_control_points[i].handler.setProximityStatusTip( - tr("Drag this point or delete it by pressing Del or D.")); - } - new_control_points[i].handler.setInteractionCursor(Qt::BlankCursor); - new_control_points[i].handler.setObject(&new_control_points[i].point); - - makeLastFollower(new_control_points[i].handler); + const int num_control_points = spline.numControlPoints(); + + XSpline new_spline(spline); + boost::scoped_array new_control_points(new ControlPoint[num_control_points]); + + for (int i = 0; i < num_control_points; ++i) { + new_control_points[i].point.setPositionCallback(boost::bind(&InteractiveXSpline::controlPointPosition, this, i)); + new_control_points[i].point.setMoveRequestCallback( + boost::bind(&InteractiveXSpline::controlPointMoveRequest, this, i, _1, _2)); + new_control_points[i].point.setDragFinishedCallback(boost::bind(&InteractiveXSpline::dragFinished, this)); + + if ((i == 0) || (i == num_control_points - 1)) { + new_control_points[i].handler.setKeyboardModifiers( + {Qt::NoModifier, Qt::ShiftModifier, Qt::ControlModifier, Qt::ShiftModifier | Qt::ControlModifier}); + // Endpoints can't be deleted. + new_control_points[i].handler.setProximityStatusTip( + tr("This point can be dragged. Hold Ctrl or Shift to drag along axes.")); + } else { + new_control_points[i].handler.setProximityStatusTip(tr("Drag this point or delete it by pressing Del or D.")); } + new_control_points[i].handler.setInteractionCursor(Qt::BlankCursor); + new_control_points[i].handler.setObject(&new_control_points[i].point); + + makeLastFollower(new_control_points[i].handler); + } - m_spline.swap(new_spline); - m_controlPoints.swap(new_control_points); + m_spline.swap(new_spline); + m_controlPoints.swap(new_control_points); - m_modifiedCallback(); + m_modifiedCallback(); } // InteractiveXSpline::setSpline void InteractiveXSpline::setStorageTransform(const Transform& from_storage, const Transform& to_storage) { - m_fromStorage = from_storage; - m_toStorage = to_storage; + m_fromStorage = from_storage; + m_toStorage = to_storage; } void InteractiveXSpline::setModifiedCallback(const ModifiedCallback& callback) { - m_modifiedCallback = callback; + m_modifiedCallback = callback; } void InteractiveXSpline::setDragFinishedCallback(const DragFinishedCallback& callback) { - m_dragFinishedCallback = callback; + m_dragFinishedCallback = callback; } bool InteractiveXSpline::curveIsProximityLeader(const InteractionState& state, QPointF* pt, double* t) const { - if (state.proximityLeader(m_curveProximity)) { - if (pt) { - *pt = m_curveProximityPointScreen; - } - if (t) { - *t = m_curveProximityT; - } - return true; + if (state.proximityLeader(m_curveProximity)) { + if (pt) { + *pt = m_curveProximityPointScreen; } + if (t) { + *t = m_curveProximityT; + } + return true; + } - return false; + return false; } void InteractiveXSpline::onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) { - m_curveProximityPointStorage = m_spline.pointClosestTo(m_toStorage(screen_mouse_pos), &m_curveProximityT); - m_curveProximityPointScreen = m_fromStorage(m_curveProximityPointStorage); + m_curveProximityPointStorage = m_spline.pointClosestTo(m_toStorage(screen_mouse_pos), &m_curveProximityT); + m_curveProximityPointScreen = m_fromStorage(m_curveProximityPointStorage); - const Proximity proximity(screen_mouse_pos, m_curveProximityPointScreen); - interaction.updateProximity(m_curveProximity, proximity, -1); + const Proximity proximity(screen_mouse_pos, m_curveProximityPointScreen); + interaction.updateProximity(m_curveProximity, proximity, -1); } void InteractiveXSpline::onMouseMoveEvent(QMouseEvent*, InteractionState& interaction) { - if (interaction.proximityLeader(m_curveProximity)) { - // We need to redraw the highlighted point. - interaction.setRedrawRequested(true); - m_lastProximity = true; - } else if (m_lastProximity) { - // In this case we need to un-draw the highlighted point. - interaction.setRedrawRequested(true); - m_lastProximity = false; - } + if (interaction.proximityLeader(m_curveProximity)) { + // We need to redraw the highlighted point. + interaction.setRedrawRequested(true); + m_lastProximity = true; + } else if (m_lastProximity) { + // In this case we need to un-draw the highlighted point. + interaction.setRedrawRequested(true); + m_lastProximity = false; + } } void InteractiveXSpline::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (interaction.captured()) { - return; - } + if (interaction.captured()) { + return; + } - if (interaction.proximityLeader(m_curveProximity)) { - const auto segment = int(m_curveProximityT * m_spline.numSegments()); - const int pnt_idx = segment + 1; + if (interaction.proximityLeader(m_curveProximity)) { + const auto segment = int(m_curveProximityT * m_spline.numSegments()); + const int pnt_idx = segment + 1; - m_spline.insertControlPoint(pnt_idx, m_curveProximityPointStorage, 1); - setSpline(m_spline); + m_spline.insertControlPoint(pnt_idx, m_curveProximityPointStorage, 1); + setSpline(m_spline); - m_controlPoints[pnt_idx].handler.forceEnterDragState(interaction, event->pos()); - event->accept(); + m_controlPoints[pnt_idx].handler.forceEnterDragState(interaction, event->pos()); + event->accept(); - interaction.setRedrawRequested(true); - } + interaction.setRedrawRequested(true); + } } void InteractiveXSpline::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - if (interaction.captured()) { - return; - } - - switch (event->key()) { - case Qt::Key_Delete: - case Qt::Key_D: - const int num_control_points = m_spline.numControlPoints(); - // Check if one of our control points is a proximity leader. - // Note that we don't consider the endpoints. - for (int i = 1; i < num_control_points - 1; ++i) { - if (m_controlPoints[i].handler.proximityLeader(interaction)) { - m_spline.eraseControlPoint(i); - setSpline(m_spline); - interaction.setRedrawRequested(true); - event->accept(); - break; - } - } - } + if (interaction.captured()) { + return; + } + + switch (event->key()) { + case Qt::Key_Delete: + case Qt::Key_D: + const int num_control_points = m_spline.numControlPoints(); + // Check if one of our control points is a proximity leader. + // Note that we don't consider the endpoints. + for (int i = 1; i < num_control_points - 1; ++i) { + if (m_controlPoints[i].handler.proximityLeader(interaction)) { + m_spline.eraseControlPoint(i); + setSpline(m_spline); + interaction.setRedrawRequested(true); + event->accept(); + break; + } + } + } } QPointF InteractiveXSpline::controlPointPosition(int idx) const { - return m_fromStorage(m_spline.controlPointPosition(idx)); + return m_fromStorage(m_spline.controlPointPosition(idx)); } double findAngle(QPointF p1, QPointF p2) { // return angle between vector (0,0)(1,0) and this - // move p1 to 0,0 - p2 -= p1; + // move p1 to 0,0 + p2 -= p1; - // get angle with (1,0) - double a = p2.x() / std::sqrt(p2.x() * p2.x() + p2.y() * p2.y()); - return std::acos(a) * 180.0 / 3.14159265; + // get angle with (1,0) + double a = p2.x() / std::sqrt(p2.x() * p2.x() + p2.y() * p2.y()); + return std::acos(a) * 180.0 / 3.14159265; } void InteractiveXSpline::controlPointMoveRequest(int idx, const QPointF& pos, Qt::KeyboardModifiers mask) { - // end of modified by monday2000 - const QPointF storage_pt(m_toStorage(pos)); - bool swap_sides = false; - bool modified = mask.testFlag(Qt::ControlModifier) || mask.testFlag(Qt::ShiftModifier); + const QPointF storage_pt(m_toStorage(pos)); + bool swap_sides = false; + bool modified = mask.testFlag(Qt::ControlModifier) || mask.testFlag(Qt::ShiftModifier); - if (modified) { - double ang = findAngle(m_toStorage(QPointF(0, 0)), m_toStorage(QPointF(1, 0))); - swap_sides = (ang >= 45 && ang <= 135); // page has been rotated + if (modified) { + double ang = findAngle(m_toStorage(QPointF(0, 0)), m_toStorage(QPointF(1, 0))); + swap_sides = (ang >= 45 && ang <= 135); // page has been rotated - if (mask.testFlag(Qt::ShiftModifier)) { - swap_sides = !swap_sides; - } + if (mask.testFlag(Qt::ShiftModifier)) { + swap_sides = !swap_sides; } - - const int num_control_points = m_spline.numControlPoints(); - if ((idx > 0) && (idx < num_control_points - 1)) { - // A midpoint - just move it. - m_spline.moveControlPoint(idx, storage_pt); - } else { - // An endpoint was moved. Instead of moving it on its own, - // we are going to rotate and / or scale all of the points - // relative to the opposite endpoint. - const int origin_idx = idx == 0 ? num_control_points - 1 : 0; - const QPointF origin(m_spline.controlPointPosition(origin_idx)); - const QPointF old_pos(m_spline.controlPointPosition(idx)); - if (Vec2d(old_pos - origin).squaredNorm() > 1.0) { - // rotationAndScale() would throw an exception if old_pos == origin. - const Vec4d mat(rotationAndScale(old_pos - origin, storage_pt - origin)); - for (int i = 0; i < num_control_points; ++i) { - Vec2d pt(m_spline.controlPointPosition(i) - origin); - MatrixCalc mc; - (mc(mat, 2, 2) * mc(pt, 2, 1)).write(pt); - - if (!modified) { // default behavior - m_spline.moveControlPoint(i, pt + origin); - } else { // Ctrl or Shift is pressed - Vec2d shift = storage_pt - old_pos; - QPointF new_position = m_spline.controlPointPosition(i) + shift; - - if (!swap_sides) { - new_position.setX(m_spline.controlPointPosition(i).x()); - } else { - new_position.setY(m_spline.controlPointPosition(i).y()); - } - - m_spline.moveControlPoint(i, new_position); - } - // end of modified by monday2000 - } - } else { - // Move the endpoint and distribute midpoints uniformly. - const QLineF line(origin, storage_pt); - const double scale = 1.0 / (num_control_points - 1); - for (int i = 0; i < num_control_points; ++i) { - m_spline.moveControlPoint(i, line.pointAt(i * scale)); - } + } + + const int num_control_points = m_spline.numControlPoints(); + if ((idx > 0) && (idx < num_control_points - 1)) { + // A midpoint - just move it. + m_spline.moveControlPoint(idx, storage_pt); + } else { + // An endpoint was moved. Instead of moving it on its own, + // we are going to rotate and / or scale all of the points + // relative to the opposite endpoint. + const int origin_idx = idx == 0 ? num_control_points - 1 : 0; + const QPointF origin(m_spline.controlPointPosition(origin_idx)); + const QPointF old_pos(m_spline.controlPointPosition(idx)); + if (Vec2d(old_pos - origin).squaredNorm() > 1.0) { + // rotationAndScale() would throw an exception if old_pos == origin. + const Vec4d mat(rotationAndScale(old_pos - origin, storage_pt - origin)); + for (int i = 0; i < num_control_points; ++i) { + Vec2d pt(m_spline.controlPointPosition(i) - origin); + MatrixCalc mc; + (mc(mat, 2, 2) * mc(pt, 2, 1)).write(pt); + + if (!modified) { // default behavior + m_spline.moveControlPoint(i, pt + origin); + } else { // Ctrl or Shift is pressed + Vec2d shift = storage_pt - old_pos; + QPointF new_position = m_spline.controlPointPosition(i) + shift; + + if (!swap_sides) { + new_position.setX(m_spline.controlPointPosition(i).x()); + } else { + new_position.setY(m_spline.controlPointPosition(i).y()); + } + + m_spline.moveControlPoint(i, new_position); } + } + } else { + // Move the endpoint and distribute midpoints uniformly. + const QLineF line(origin, storage_pt); + const double scale = 1.0 / (num_control_points - 1); + for (int i = 0; i < num_control_points; ++i) { + m_spline.moveControlPoint(i, line.pointAt(i * scale)); + } } + } - m_modifiedCallback(); + m_modifiedCallback(); } // InteractiveXSpline::controlPointMoveRequest void InteractiveXSpline::dragFinished() { - m_dragFinishedCallback(); + m_dragFinishedCallback(); } Vec4d InteractiveXSpline::rotationAndScale(const QPointF& from, const QPointF& to) { - Vec4d A; - A[0] = from.x(); - A[1] = from.y(); - A[2] = from.y(); - A[3] = -from.x(); - - Vec2d B(to.x(), to.y()); - - Vec2d x; - MatrixCalc mc; - mc(A, 2, 2).solve(mc(B, 2, 1)).write(x); - - A[0] = x[0]; - A[1] = -x[1]; - A[2] = x[1]; - A[3] = x[0]; - return A; + Vec4d A; + A[0] = from.x(); + A[1] = from.y(); + A[2] = from.y(); + A[3] = -from.x(); + + Vec2d B(to.x(), to.y()); + + Vec2d x; + MatrixCalc mc; + mc(A, 2, 2).solve(mc(B, 2, 1)).write(x); + + A[0] = x[0]; + A[1] = -x[1]; + A[2] = x[1]; + A[3] = x[0]; + return A; } diff --git a/interaction/InteractiveXSpline.h b/interaction/InteractiveXSpline.h index c3319e40e..a3e6c6d7e 100644 --- a/interaction/InteractiveXSpline.h +++ b/interaction/InteractiveXSpline.h @@ -19,90 +19,88 @@ #ifndef INTERACTIVE_XSPLINE_H_ #define INTERACTIVE_XSPLINE_H_ -#include "XSpline.h" -#include "DraggablePoint.h" -#include "ObjectDragHandler.h" -#include "InteractionState.h" -#include "VecNT.h" -#include #include +#include #include #include #include +#include "DraggablePoint.h" +#include "InteractionState.h" +#include "ObjectDragHandler.h" +#include "VecNT.h" +#include "XSpline.h" class InteractiveXSpline : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(InteractiveXSpline) -public: - typedef boost::function Transform; - typedef boost::function ModifiedCallback; - typedef boost::function DragFinishedCallback; + Q_DECLARE_TR_FUNCTIONS(InteractiveXSpline) + public: + typedef boost::function Transform; + typedef boost::function ModifiedCallback; + typedef boost::function DragFinishedCallback; - InteractiveXSpline(); + InteractiveXSpline(); - void setSpline(const XSpline& spline); + void setSpline(const XSpline& spline); - const XSpline& spline() const { - return m_spline; - } + const XSpline& spline() const { return m_spline; } - void setStorageTransform(const Transform& from_storage, const Transform& to_storage); + void setStorageTransform(const Transform& from_storage, const Transform& to_storage); - void setModifiedCallback(const ModifiedCallback& callback); + void setModifiedCallback(const ModifiedCallback& callback); - void setDragFinishedCallback(const DragFinishedCallback& callback); + void setDragFinishedCallback(const DragFinishedCallback& callback); - /** - * \brief Returns true if the curve is a proximity leader. - * - * \param state Interaction state, used to tell whether - * the curve is the proximity leader. - * \param pt If provided, the point on the curve closest to - * the cursor will be written there. - * \param t If provided, the splie's T parameter corresponding - * to the point closest to the cursor will be written there. - * \return true if the curve is the proximity leader. - */ - bool curveIsProximityLeader(const InteractionState& state, QPointF* pt = nullptr, double* t = nullptr) const; + /** + * \brief Returns true if the curve is a proximity leader. + * + * \param state Interaction state, used to tell whether + * the curve is the proximity leader. + * \param pt If provided, the point on the curve closest to + * the cursor will be written there. + * \param t If provided, the splie's T parameter corresponding + * to the point closest to the cursor will be written there. + * \return true if the curve is the proximity leader. + */ + bool curveIsProximityLeader(const InteractionState& state, QPointF* pt = nullptr, double* t = nullptr) const; -protected: - void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; + protected: + void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; -private: - struct NoOp; - struct IdentTransform; + private: + struct NoOp; + struct IdentTransform; - struct ControlPoint { - DraggablePoint point; - ObjectDragHandler handler; + struct ControlPoint { + DraggablePoint point; + ObjectDragHandler handler; - ControlPoint() = default; - }; + ControlPoint() = default; + }; - QPointF controlPointPosition(int idx) const; + QPointF controlPointPosition(int idx) const; - void controlPointMoveRequest(int idx, const QPointF& pos, Qt::KeyboardModifiers mask); + void controlPointMoveRequest(int idx, const QPointF& pos, Qt::KeyboardModifiers mask); - void dragFinished(); + void dragFinished(); - static Vec4d rotationAndScale(const QPointF& from, const QPointF& to); + static Vec4d rotationAndScale(const QPointF& from, const QPointF& to); - ModifiedCallback m_modifiedCallback; - DragFinishedCallback m_dragFinishedCallback; - Transform m_fromStorage; - Transform m_toStorage; - XSpline m_spline; - boost::scoped_array m_controlPoints; - InteractionState::Captor m_curveProximity; - QPointF m_curveProximityPointStorage; - QPointF m_curveProximityPointScreen; - double m_curveProximityT; - bool m_lastProximity; + ModifiedCallback m_modifiedCallback; + DragFinishedCallback m_dragFinishedCallback; + Transform m_fromStorage; + Transform m_toStorage; + XSpline m_spline; + boost::scoped_array m_controlPoints; + InteractionState::Captor m_curveProximity; + QPointF m_curveProximityPointStorage; + QPointF m_curveProximityPointScreen; + double m_curveProximityT; + bool m_lastProximity; }; diff --git a/interaction/ObjectDragHandler.cpp b/interaction/ObjectDragHandler.cpp index 4f96c7ffa..98ed8f8b6 100644 --- a/interaction/ObjectDragHandler.cpp +++ b/interaction/ObjectDragHandler.cpp @@ -20,86 +20,86 @@ #include ObjectDragHandler::ObjectDragHandler(DraggableObject* obj) - : m_pObj(obj), m_keyboardModifiers(Qt::NoModifier), m_activeKeyboardModifiers(Qt::NoModifier) { - setProximityCursor(Qt::OpenHandCursor); - setInteractionCursor(Qt::ClosedHandCursor); + : m_obj(obj), m_keyboardModifiersSet({Qt::NoModifier}), m_activeKeyboardModifiers(Qt::NoModifier) { + setProximityCursor(Qt::OpenHandCursor); + setInteractionCursor(Qt::ClosedHandCursor); } void ObjectDragHandler::setProximityCursor(const QCursor& cursor) { - m_interaction.setProximityCursor(cursor); + m_interaction.setProximityCursor(cursor); } void ObjectDragHandler::setInteractionCursor(const QCursor& cursor) { - m_interaction.setInteractionCursor(cursor); + m_interaction.setInteractionCursor(cursor); } void ObjectDragHandler::setProximityStatusTip(const QString& tip) { - m_interaction.setProximityStatusTip(tip); + m_interaction.setProximityStatusTip(tip); } void ObjectDragHandler::setInteractionStatusTip(const QString& tip) { - m_interaction.setInteractionStatusTip(tip); + m_interaction.setInteractionStatusTip(tip); } bool ObjectDragHandler::interactionInProgress(const InteractionState& interaction) const { - return interaction.capturedBy(m_interaction); + return interaction.capturedBy(m_interaction); } bool ObjectDragHandler::proximityLeader(const InteractionState& interaction) const { - return interaction.proximityLeader(m_interaction); + return interaction.proximityLeader(m_interaction); } void ObjectDragHandler::forceEnterDragState(InteractionState& interaction, QPoint widget_mouse_pos) { - interaction.capture(m_interaction); - m_pObj->dragInitiated(QPointF(0.5, 0.5) + widget_mouse_pos); + interaction.capture(m_interaction); + m_obj->dragInitiated(QPointF(0.5, 0.5) + widget_mouse_pos); } void ObjectDragHandler::onPaint(QPainter& painter, const InteractionState& interaction) { - m_pObj->paint(painter, interaction); + m_obj->paint(painter, interaction); } void ObjectDragHandler::onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) { - if (m_keyboardModifiers != m_activeKeyboardModifiers) { - return; - } + if (m_keyboardModifiersSet.find(m_activeKeyboardModifiers) == m_keyboardModifiersSet.end()) { + return; + } - interaction.updateProximity(m_interaction, m_pObj->proximity(screen_mouse_pos), m_pObj->proximityPriority(), - m_pObj->proximityThreshold(interaction)); + interaction.updateProximity(m_interaction, m_obj->proximity(screen_mouse_pos), m_obj->proximityPriority(), + m_obj->proximityThreshold(interaction)); } void ObjectDragHandler::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (interaction.captured() || (event->button() != Qt::LeftButton) - || (m_keyboardModifiers != m_activeKeyboardModifiers)) { - return; - } + if (interaction.captured() || (event->button() != Qt::LeftButton) + || (m_keyboardModifiersSet.find(m_activeKeyboardModifiers) == m_keyboardModifiersSet.end())) { + return; + } - if (interaction.proximityLeader(m_interaction)) { - interaction.capture(m_interaction); - m_pObj->dragInitiated(QPointF(0.5, 0.5) + event->pos()); - } + if (interaction.proximityLeader(m_interaction)) { + interaction.capture(m_interaction); + m_obj->dragInitiated(QPointF(0.5, 0.5) + event->pos()); + } } void ObjectDragHandler::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if ((event->button() == Qt::LeftButton) && interaction.capturedBy(m_interaction)) { - m_interaction.release(); - m_pObj->dragFinished(QPointF(0.5, 0.5) + event->pos()); - } + if ((event->button() == Qt::LeftButton) && interaction.capturedBy(m_interaction)) { + m_interaction.release(); + m_obj->dragFinished(QPointF(0.5, 0.5) + event->pos()); + } } void ObjectDragHandler::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - if (interaction.capturedBy(m_interaction)) { - m_pObj->dragContinuation(QPointF(0.5, 0.5) + event->pos(), event->modifiers()); - } + if (interaction.capturedBy(m_interaction)) { + m_obj->dragContinuation(QPointF(0.5, 0.5) + event->pos(), event->modifiers()); + } } -void ObjectDragHandler::setKeyboardModifiers(const Qt::KeyboardModifiers modifiers) { - m_keyboardModifiers = modifiers; +void ObjectDragHandler::setKeyboardModifiers(const std::set& modifiers_set) { + m_keyboardModifiersSet = modifiers_set; } void ObjectDragHandler::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - m_activeKeyboardModifiers = event->modifiers(); + m_activeKeyboardModifiers = event->modifiers(); } void ObjectDragHandler::onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) { - m_activeKeyboardModifiers = event->modifiers(); + m_activeKeyboardModifiers = event->modifiers(); } diff --git a/interaction/ObjectDragHandler.h b/interaction/ObjectDragHandler.h index b5bd7a951..769522a78 100644 --- a/interaction/ObjectDragHandler.h +++ b/interaction/ObjectDragHandler.h @@ -19,63 +19,61 @@ #ifndef OBJECT_DRAG_HANDLER_H_ #define OBJECT_DRAG_HANDLER_H_ -#include "NonCopyable.h" +#include +#include +#include "DraggableObject.h" #include "InteractionHandler.h" #include "InteractionState.h" -#include "DraggableObject.h" -#include -#include +#include "NonCopyable.h" class QPainter; class QCursor; class QString; class ObjectDragHandler : public InteractionHandler { - DECLARE_NON_COPYABLE(ObjectDragHandler) + DECLARE_NON_COPYABLE(ObjectDragHandler) -public: - explicit ObjectDragHandler(DraggableObject* obj = nullptr); + public: + explicit ObjectDragHandler(DraggableObject* obj = nullptr); - void setObject(DraggableObject* obj) { - m_pObj = obj; - } + void setObject(DraggableObject* obj) { m_obj = obj; } - void setProximityCursor(const QCursor& cursor); + void setProximityCursor(const QCursor& cursor); - void setInteractionCursor(const QCursor& cursor); + void setInteractionCursor(const QCursor& cursor); - void setProximityStatusTip(const QString& tip); + void setProximityStatusTip(const QString& tip); - void setInteractionStatusTip(const QString& tip); + void setInteractionStatusTip(const QString& tip); - bool interactionInProgress(const InteractionState& interaction) const; + bool interactionInProgress(const InteractionState& interaction) const; - bool proximityLeader(const InteractionState& interaction) const; + bool proximityLeader(const InteractionState& interaction) const; - void forceEnterDragState(InteractionState& interaction, QPoint widget_mouse_pos); + void forceEnterDragState(InteractionState& interaction, QPoint widget_mouse_pos); - void setKeyboardModifiers(Qt::KeyboardModifiers modifiers); + void setKeyboardModifiers(const std::set& modifiers_set); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; + void onProximityUpdate(const QPointF& screen_mouse_pos, InteractionState& interaction) override; - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; - void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) override; -private: - DraggableObject* m_pObj; - InteractionState::Captor m_interaction; - Qt::KeyboardModifiers m_keyboardModifiers; - Qt::KeyboardModifiers m_activeKeyboardModifiers; + private: + DraggableObject* m_obj; + InteractionState::Captor m_interaction; + std::set m_keyboardModifiersSet; + Qt::KeyboardModifiers m_activeKeyboardModifiers; }; diff --git a/interaction/ZoomHandler.cpp b/interaction/ZoomHandler.cpp index 3999b554f..4de3c7435 100644 --- a/interaction/ZoomHandler.cpp +++ b/interaction/ZoomHandler.cpp @@ -17,84 +17,82 @@ */ #include "ZoomHandler.h" -#include "ImageViewBase.h" #include +#include "ImageViewBase.h" ZoomHandler::ZoomHandler(ImageViewBase& image_view) - : m_rImageView(image_view), - m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter), - m_focus(CURSOR) { -} + : m_imageView(image_view), + m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter), + m_focus(CURSOR) {} ZoomHandler::ZoomHandler(ImageViewBase& image_view, const boost::function& explicit_interaction_permitter) - : m_rImageView(image_view), m_interactionPermitter(explicit_interaction_permitter), m_focus(CURSOR) { -} + : m_imageView(image_view), m_interactionPermitter(explicit_interaction_permitter), m_focus(CURSOR) {} void ZoomHandler::onWheelEvent(QWheelEvent* event, InteractionState& interaction) { - if (event->orientation() != Qt::Vertical) { - return; - } - - if (!m_interactionPermitter(interaction)) { - return; - } - - event->accept(); - - double zoom = m_rImageView.zoomLevel(); - - if ((zoom == 1.0) && (event->delta() < 0)) { - // Alredy zoomed out and trying to zoom out more. - - // Scroll amount in terms of typical mouse wheel "clicks". - const double delta_clicks = event->delta() / 120; - - const double dist = -delta_clicks * 30; // 30px per "click" - m_rImageView.moveTowardsIdealPosition(dist); - - return; - } - - const double degrees = event->delta() / 8.0; - zoom *= std::pow(2.0, degrees / 60.0); // 2 times zoom for every 60 degrees - if (zoom < 1.0) { - zoom = 1.0; - } - - QPointF focus_point; - switch (m_focus) { - case CENTER: - focus_point = QRectF(m_rImageView.rect()).center(); - break; - case CURSOR: - focus_point = event->pos() + QPointF(0.5, 0.5); - break; - } - m_rImageView.setWidgetFocalPointWithoutMoving(focus_point); - m_rImageView.setZoomLevel(zoom); // this will call update() + if (event->orientation() != Qt::Vertical) { + return; + } + + if (!m_interactionPermitter(interaction)) { + return; + } + + event->accept(); + + double zoom = m_imageView.zoomLevel(); + + if ((zoom == 1.0) && (event->delta() < 0)) { + // Alredy zoomed out and trying to zoom out more. + + // Scroll amount in terms of typical mouse wheel "clicks". + const double delta_clicks = event->delta() / 120; + + const double dist = -delta_clicks * 30; // 30px per "click" + m_imageView.moveTowardsIdealPosition(dist); + + return; + } + + const double degrees = event->delta() / 8.0; + zoom *= std::pow(2.0, degrees / 60.0); // 2 times zoom for every 60 degrees + if (zoom < 1.0) { + zoom = 1.0; + } + + QPointF focus_point; + switch (m_focus) { + case CENTER: + focus_point = QRectF(m_imageView.rect()).center(); + break; + case CURSOR: + focus_point = event->pos() + QPointF(0.5, 0.5); + break; + } + m_imageView.setWidgetFocalPointWithoutMoving(focus_point); + m_imageView.setZoomLevel(zoom); // this will call update() } // ZoomHandler::onWheelEvent void ZoomHandler::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - if (!m_interactionPermitter(interaction)) { - return; - } - - double zoom = m_rImageView.zoomLevel(); - - switch (event->key()) { - case Qt::Key_Plus: - zoom *= 1.12246205; // == 2^( 1/6); - break; - case Qt::Key_Minus: - zoom *= 0.89089872; // == 2^(-1/6); - break; - default: - return; - } - - QPointF focus_point = QRectF(m_rImageView.rect()).center(); - - m_rImageView.setWidgetFocalPointWithoutMoving(focus_point); - m_rImageView.setZoomLevel(zoom); // this will call update() + if (!m_interactionPermitter(interaction)) { + return; + } + + double zoom = m_imageView.zoomLevel(); + + switch (event->key()) { + case Qt::Key_Plus: + zoom *= 1.12246205; // == 2^( 1/6); + break; + case Qt::Key_Minus: + zoom *= 0.89089872; // == 2^(-1/6); + break; + default: + return; + } + + QPointF focus_point = QRectF(m_imageView.rect()).center(); + + m_imageView.setWidgetFocalPointWithoutMoving(focus_point); + m_imageView.setZoomLevel(zoom); // this will call update() } diff --git a/interaction/ZoomHandler.h b/interaction/ZoomHandler.h index 7898f88c6..d70c0018d 100644 --- a/interaction/ZoomHandler.h +++ b/interaction/ZoomHandler.h @@ -19,42 +19,38 @@ #ifndef ZOOM_HANDLER_H_ #define ZOOM_HANDLER_H_ -#include "InteractionHandler.h" -#include "InteractionState.h" -#include #include +#include #include +#include "InteractionHandler.h" +#include "InteractionState.h" class ImageViewBase; class ZoomHandler : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ZoomHandler) -public: - enum Focus { CENTER, CURSOR }; + Q_DECLARE_TR_FUNCTIONS(ZoomHandler) + public: + enum Focus { CENTER, CURSOR }; - explicit ZoomHandler(ImageViewBase& image_view); + explicit ZoomHandler(ImageViewBase& image_view); - ZoomHandler(ImageViewBase& image_view, - const boost::function& explicit_interaction_permitter); + ZoomHandler(ImageViewBase& image_view, + const boost::function& explicit_interaction_permitter); - Focus focus() const { - return m_focus; - } + Focus focus() const { return m_focus; } - void setFocus(Focus focus) { - m_focus = focus; - } + void setFocus(Focus focus) { m_focus = focus; } -protected: - void onWheelEvent(QWheelEvent* event, InteractionState& interaction) override; + protected: + void onWheelEvent(QWheelEvent* event, InteractionState& interaction) override; - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; -private: - ImageViewBase& m_rImageView; - boost::function m_interactionPermitter; - InteractionState::Captor m_interaction; - Focus m_focus; + private: + ImageViewBase& m_imageView; + boost::function m_interactionPermitter; + InteractionState::Captor m_interaction; + Focus m_focus; }; diff --git a/main-cli.cpp b/main-cli.cpp index 0c30dd073..9f9eab0a4 100644 --- a/main-cli.cpp +++ b/main-cli.cpp @@ -25,45 +25,44 @@ int main(int argc, char** argv) { - QCoreApplication app(argc, argv); + QCoreApplication app(argc, argv); #ifdef _WIN32 - // Get rid of all references to Qt's installation directory. - app.setLibraryPaths(QStringList(app.applicationDirPath())); + // Get rid of all references to Qt's installation directory. + app.setLibraryPaths(QStringList(app.applicationDirPath())); #endif - // parse command line arguments - CommandLine cli(app.arguments(), false); - CommandLine::set(cli); + // parse command line arguments + CommandLine cli(app.arguments(), false); + CommandLine::set(cli); - if (cli.isError()) { - cli.printHelp(); + if (cli.isError()) { + cli.printHelp(); - return 1; - } + return 1; + } - if (cli.hasHelp() || cli.outputDirectory().isEmpty() - || ((cli.images().size() == 0) && cli.projectFile().isEmpty())) { - cli.printHelp(); + if (cli.hasHelp() || cli.outputDirectory().isEmpty() || ((cli.images().size() == 0) && cli.projectFile().isEmpty())) { + cli.printHelp(); - return 0; - } + return 0; + } - std::unique_ptr cbatch; - - try { - if (!cli.projectFile().isEmpty()) { - cbatch = std::make_unique(cli.projectFile()); - } else { - cbatch = std::make_unique(cli.images(), cli.outputDirectory(), cli.getLayoutDirection()); - } - cbatch->process(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - exit(1); - } + std::unique_ptr cbatch; - if (cli.hasOutputProject()) { - cbatch->saveProject(cli.outputProjectFile()); + try { + if (!cli.projectFile().isEmpty()) { + cbatch = std::make_unique(cli.projectFile()); + } else { + cbatch = std::make_unique(cli.images(), cli.outputDirectory(), cli.getLayoutDirection()); } + cbatch->process(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + exit(1); + } + + if (cli.hasOutputProject()) { + cbatch->saveProject(cli.outputProjectFile()); + } } // main diff --git a/main.cpp b/main.cpp index 621f25eb7..7083dc125 100644 --- a/main.cpp +++ b/main.cpp @@ -16,72 +16,76 @@ along with this program. If not, see . */ -#include "config.h" #include "Application.h" +#include "ColorSchemeManager.h" +#include "CommandLine.h" +#include "DarkScheme.h" +#include "JpegMetadataLoader.h" +#include "LightScheme.h" #include "MainWindow.h" #include "PngMetadataLoader.h" #include "TiffMetadataLoader.h" -#include "JpegMetadataLoader.h" -#include "DarkScheme.h" - -#include "CommandLine.h" -#include "ColorSchemeManager.h" -#include "LightScheme.h" +#include "config.h" +#include "NativeScheme.h" int main(int argc, char** argv) { - // rescaling for high DPI displays - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // rescaling for high DPI displays + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - Application app(argc, argv); + Application app(argc, argv); #ifdef _WIN32 - // Get rid of all references to Qt's installation directory. - app.setLibraryPaths(QStringList(app.applicationDirPath())); + // Get rid of all references to Qt's installation directory. + app.setLibraryPaths(QStringList(app.applicationDirPath())); #endif - // Initialize command line in gui mode. - CommandLine cli(app.arguments()); - CommandLine::set(cli); + // Initialize command line in gui mode. + CommandLine cli(app.arguments()); + CommandLine::set(cli); - // This information is used by QSettings. - app.setApplicationName("scantailor"); - app.setOrganizationName("scantailor"); + // This information is used by QSettings. + app.setApplicationName(APPLICATION_NAME); + app.setOrganizationName(ORGANIZATION_NAME); - PngMetadataLoader::registerMyself(); - TiffMetadataLoader::registerMyself(); - JpegMetadataLoader::registerMyself(); + PngMetadataLoader::registerMyself(); + TiffMetadataLoader::registerMyself(); + JpegMetadataLoader::registerMyself(); - QSettings::setDefaultFormat(QSettings::IniFormat); - QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, app.applicationDirPath() + "/config"); + QSettings::setDefaultFormat(QSettings::IniFormat); + if (app.isPortableVersion()) { + QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, app.getPortableConfigPath()); + } - QSettings settings; + QSettings settings; - app.installLanguage(settings.value("settings/language", QLocale::system().name()).toString()); + app.installLanguage(settings.value("settings/language", QLocale::system().name()).toString()); - { - QString val = settings.value("settings/color_scheme", "dark").toString(); - std::unique_ptr scheme; - if (val == "light") { - scheme = std::make_unique(); - } else { - scheme = std::make_unique(); - } - - ColorSchemeManager::instance()->setColorScheme(*scheme.release()); - } - - auto* main_wnd = new MainWindow(); - main_wnd->setAttribute(Qt::WA_DeleteOnClose); - if (settings.value("mainWindow/maximized") == false) { - main_wnd->show(); + { + QString val = settings.value("settings/color_scheme", "dark").toString(); + std::unique_ptr scheme; + if (val == "native") { + scheme = std::make_unique(); + } else if (val == "light") { + scheme = std::make_unique(); } else { - // main_wnd->showMaximized(); // Doesn't work for Windows. - QTimer::singleShot(0, main_wnd, &QMainWindow::showMaximized); + scheme = std::make_unique(); } - if (!cli.projectFile().isEmpty()) { - main_wnd->openProject(cli.projectFile()); - } + ColorSchemeManager::instance()->setColorScheme(*scheme); + } + + auto* main_wnd = new MainWindow(); + main_wnd->setAttribute(Qt::WA_DeleteOnClose); + if (settings.value("mainWindow/maximized") == false) { + main_wnd->show(); + } else { + // main_wnd->showMaximized(); // Doesn't work for Windows. + QTimer::singleShot(0, main_wnd, &QMainWindow::showMaximized); + } + + if (!cli.projectFile().isEmpty()) { + main_wnd->openProject(cli.projectFile()); + } - return app.exec(); + return app.exec(); } // main diff --git a/math/ArcLengthMapper.cpp b/math/ArcLengthMapper.cpp index 459e27a0d..6f8a108f1 100644 --- a/math/ArcLengthMapper.cpp +++ b/math/ArcLengthMapper.cpp @@ -17,214 +17,212 @@ */ #include "ArcLengthMapper.h" -#include #include +#include -ArcLengthMapper::Hint::Hint() : m_lastSegment(0), m_direction(1) { -} +ArcLengthMapper::Hint::Hint() : m_lastSegment(0), m_direction(1) {} void ArcLengthMapper::Hint::update(int new_segment) { - m_direction = new_segment < m_lastSegment ? -1 : 1; - m_lastSegment = new_segment; + m_direction = new_segment < m_lastSegment ? -1 : 1; + m_lastSegment = new_segment; } -ArcLengthMapper::ArcLengthMapper() : m_prevFX() { -} +ArcLengthMapper::ArcLengthMapper() : m_prevFX() {} void ArcLengthMapper::addSample(double x, double fx) { - double arc_len = 0; + double arc_len = 0; - if (!m_samples.empty()) { - const double dx = x - m_samples.back().x; - const double dy = fx - m_prevFX; - assert(dx > 0); - arc_len = m_samples.back().arcLen + std::sqrt(dx * dx + dy * dy); - } + if (!m_samples.empty()) { + const double dx = x - m_samples.back().x; + const double dy = fx - m_prevFX; + assert(dx > 0); + arc_len = m_samples.back().arcLen + std::sqrt(dx * dx + dy * dy); + } - m_samples.emplace_back(x, arc_len); - m_prevFX = fx; + m_samples.emplace_back(x, arc_len); + m_prevFX = fx; } double ArcLengthMapper::totalArcLength() const { - return m_samples.size() < 2 ? 0.0 : m_samples.back().arcLen; + return m_samples.size() < 2 ? 0.0 : m_samples.back().arcLen; } void ArcLengthMapper::normalizeRange(double total_arc_len) { - if (m_samples.size() <= 1) { - // If size == 1, samples.back().arcLen below will be 0. - return; - } + if (m_samples.size() <= 1) { + // If size == 1, samples.back().arcLen below will be 0. + return; + } - assert(total_arc_len != 0); + assert(total_arc_len != 0); - const double scale = total_arc_len / m_samples.back().arcLen; - for (Sample& sample : m_samples) { - sample.arcLen *= scale; - } + const double scale = total_arc_len / m_samples.back().arcLen; + for (Sample& sample : m_samples) { + sample.arcLen *= scale; + } } double ArcLengthMapper::arcLenToX(double arc_len, Hint& hint) const { - switch (m_samples.size()) { - case 0: - return 0; - case 1: - return m_samples.front().x; + switch (m_samples.size()) { + case 0: + return 0; + case 1: + return m_samples.front().x; + } + + if (arc_len < 0) { + // Beyond the first sample. + hint.update(0); + + return interpolateArcLenInSegment(arc_len, 0); + } else if (arc_len > m_samples.back().arcLen) { + // Beyond the last sample. + hint.update(static_cast(m_samples.size() - 2)); + + return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); + } + + // Check in the answer is in the segment provided by hint, + // or in an adjacent one. + if (checkSegmentForArcLen(arc_len, hint.m_lastSegment)) { + return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); + } else if (checkSegmentForArcLen(arc_len, hint.m_lastSegment + hint.m_direction)) { + hint.update(hint.m_lastSegment + hint.m_direction); + + return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); + } else if (checkSegmentForArcLen(arc_len, hint.m_lastSegment - hint.m_direction)) { + hint.update(hint.m_lastSegment - hint.m_direction); + + return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); + } + // Do a binary search. + int left_idx = 0; + auto right_idx = static_cast(m_samples.size() - 1); + double left_arc_len = m_samples[left_idx].arcLen; + while (left_idx + 1 < right_idx) { + const int mid_idx = (left_idx + right_idx) >> 1; + const double mid_arc_len = m_samples[mid_idx].arcLen; + if ((arc_len - mid_arc_len) * (arc_len - left_arc_len) <= 0) { + // Note: <= 0 vs < 0 is actually important for this branch. + // 0 would indicate either left or mid point is our exact answer. + right_idx = mid_idx; + } else { + left_idx = mid_idx; + left_arc_len = mid_arc_len; } + } - if (arc_len < 0) { - // Beyond the first sample. - hint.update(0); + hint.update(left_idx); - return interpolateArcLenInSegment(arc_len, 0); - } else if (arc_len > m_samples.back().arcLen) { - // Beyond the last sample. - hint.update(static_cast(m_samples.size() - 2)); - - return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); - } - - // Check in the answer is in the segment provided by hint, - // or in an adjacent one. - if (checkSegmentForArcLen(arc_len, hint.m_lastSegment)) { - return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); - } else if (checkSegmentForArcLen(arc_len, hint.m_lastSegment + hint.m_direction)) { - hint.update(hint.m_lastSegment + hint.m_direction); - - return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); - } else if (checkSegmentForArcLen(arc_len, hint.m_lastSegment - hint.m_direction)) { - hint.update(hint.m_lastSegment - hint.m_direction); - - return interpolateArcLenInSegment(arc_len, hint.m_lastSegment); - } - // Do a binary search. - int left_idx = 0; - auto right_idx = static_cast(m_samples.size() - 1); - double left_arc_len = m_samples[left_idx].arcLen; - while (left_idx + 1 < right_idx) { - const int mid_idx = (left_idx + right_idx) >> 1; - const double mid_arc_len = m_samples[mid_idx].arcLen; - if ((arc_len - mid_arc_len) * (arc_len - left_arc_len) <= 0) { - // Note: <= 0 vs < 0 is actually important for this branch. - // 0 would indicate either left or mid point is our exact answer. - right_idx = mid_idx; - } else { - left_idx = mid_idx; - left_arc_len = mid_arc_len; - } - } - - hint.update(left_idx); - - return interpolateArcLenInSegment(arc_len, left_idx); + return interpolateArcLenInSegment(arc_len, left_idx); } // ArcLengthMapper::arcLenToX double ArcLengthMapper::xToArcLen(double x, Hint& hint) const { - switch (m_samples.size()) { - case 0: - return 0; - case 1: - return m_samples.front().arcLen; + switch (m_samples.size()) { + case 0: + return 0; + case 1: + return m_samples.front().arcLen; + } + + if (x < m_samples.front().x) { + // Beyond the first sample. + hint.update(0); + + return interpolateXInSegment(x, 0); + } else if (x > m_samples.back().x) { + // Beyond the last sample. + hint.update(static_cast(m_samples.size() - 2)); + + return interpolateXInSegment(x, hint.m_lastSegment); + } + + // Check in the answer is in the segment provided by hint, + // or in an adjacent one. + if (checkSegmentForX(x, hint.m_lastSegment)) { + return interpolateXInSegment(x, hint.m_lastSegment); + } else if (checkSegmentForX(x, hint.m_lastSegment + hint.m_direction)) { + hint.update(hint.m_lastSegment + hint.m_direction); + + return interpolateXInSegment(x, hint.m_lastSegment); + } else if (checkSegmentForX(x, hint.m_lastSegment - hint.m_direction)) { + hint.update(hint.m_lastSegment - hint.m_direction); + + return interpolateXInSegment(x, hint.m_lastSegment); + } + // Do a binary search. + int left_idx = 0; + auto right_idx = static_cast(m_samples.size() - 1); + double left_x = m_samples[left_idx].x; + while (left_idx + 1 < right_idx) { + const int mid_idx = (left_idx + right_idx) >> 1; + const double mid_x = m_samples[mid_idx].x; + if ((x - mid_x) * (x - left_x) <= 0) { + // Note: <= 0 vs < 0 is actually important for this branch. + // 0 would indicate either left or mid point is our exact answer. + right_idx = mid_idx; + } else { + left_idx = mid_idx; + left_x = mid_x; } + } - if (x < m_samples.front().x) { - // Beyond the first sample. - hint.update(0); - - return interpolateXInSegment(x, 0); - } else if (x > m_samples.back().x) { - // Beyond the last sample. - hint.update(static_cast(m_samples.size() - 2)); - - return interpolateXInSegment(x, hint.m_lastSegment); - } - - // Check in the answer is in the segment provided by hint, - // or in an adjacent one. - if (checkSegmentForX(x, hint.m_lastSegment)) { - return interpolateXInSegment(x, hint.m_lastSegment); - } else if (checkSegmentForX(x, hint.m_lastSegment + hint.m_direction)) { - hint.update(hint.m_lastSegment + hint.m_direction); - - return interpolateXInSegment(x, hint.m_lastSegment); - } else if (checkSegmentForX(x, hint.m_lastSegment - hint.m_direction)) { - hint.update(hint.m_lastSegment - hint.m_direction); + hint.update(left_idx); - return interpolateXInSegment(x, hint.m_lastSegment); - } - // Do a binary search. - int left_idx = 0; - auto right_idx = static_cast(m_samples.size() - 1); - double left_x = m_samples[left_idx].x; - while (left_idx + 1 < right_idx) { - const int mid_idx = (left_idx + right_idx) >> 1; - const double mid_x = m_samples[mid_idx].x; - if ((x - mid_x) * (x - left_x) <= 0) { - // Note: <= 0 vs < 0 is actually important for this branch. - // 0 would indicate either left or mid point is our exact answer. - right_idx = mid_idx; - } else { - left_idx = mid_idx; - left_x = mid_x; - } - } - - hint.update(left_idx); - - return interpolateXInSegment(x, left_idx); + return interpolateXInSegment(x, left_idx); } // ArcLengthMapper::xToArcLen bool ArcLengthMapper::checkSegmentForArcLen(double arc_len, int segment) const { - assert(m_samples.size() > 1); // Enforced by the caller. - if ((segment < 0) || (segment >= int(m_samples.size()) - 1)) { - return false; - } + assert(m_samples.size() > 1); // Enforced by the caller. + if ((segment < 0) || (segment >= int(m_samples.size()) - 1)) { + return false; + } - const double left_arc_len = m_samples[segment].arcLen; - const double right_arc_len = m_samples[segment + 1].arcLen; + const double left_arc_len = m_samples[segment].arcLen; + const double right_arc_len = m_samples[segment + 1].arcLen; - return (arc_len - left_arc_len) * (arc_len - right_arc_len) <= 0; + return (arc_len - left_arc_len) * (arc_len - right_arc_len) <= 0; } bool ArcLengthMapper::checkSegmentForX(double x, int segment) const { - assert(m_samples.size() > 1); // Enforced by the caller. - if ((segment < 0) || (segment >= int(m_samples.size()) - 1)) { - return false; - } + assert(m_samples.size() > 1); // Enforced by the caller. + if ((segment < 0) || (segment >= int(m_samples.size()) - 1)) { + return false; + } - const double left_x = m_samples[segment].x; - const double right_x = m_samples[segment + 1].x; + const double left_x = m_samples[segment].x; + const double right_x = m_samples[segment + 1].x; - return (x - left_x) * (x - right_x) <= 0; + return (x - left_x) * (x - right_x) <= 0; } double ArcLengthMapper::interpolateArcLenInSegment(double arc_len, int segment) const { - // a - a0 a1 - a0 - // ------ = ------- - // x - x0 x1 - x0 - // - // x = x0 + (a - a0) * (x1 - x0) / (a1 - a0) - - const double x0 = m_samples[segment].x; - const double a0 = m_samples[segment].arcLen; - const double x1 = m_samples[segment + 1].x; - const double a1 = m_samples[segment + 1].arcLen; - const double x = x0 + (arc_len - a0) * (x1 - x0) / (a1 - a0); - - return x; + // a - a0 a1 - a0 + // ------ = ------- + // x - x0 x1 - x0 + // + // x = x0 + (a - a0) * (x1 - x0) / (a1 - a0) + + const double x0 = m_samples[segment].x; + const double a0 = m_samples[segment].arcLen; + const double x1 = m_samples[segment + 1].x; + const double a1 = m_samples[segment + 1].arcLen; + const double x = x0 + (arc_len - a0) * (x1 - x0) / (a1 - a0); + + return x; } double ArcLengthMapper::interpolateXInSegment(double x, int segment) const { - // a - a0 a1 - a0 - // ------ = ------- - // x - x0 x1 - x0 - // - // a = a0 + (a1 - a0) * (x - x0) / (x1 - x0) - - const double x0 = m_samples[segment].x; - const double a0 = m_samples[segment].arcLen; - const double x1 = m_samples[segment + 1].x; - const double a1 = m_samples[segment + 1].arcLen; - const double a = a0 + (a1 - a0) * (x - x0) / (x1 - x0); - - return a; + // a - a0 a1 - a0 + // ------ = ------- + // x - x0 x1 - x0 + // + // a = a0 + (a1 - a0) * (x - x0) / (x1 - x0) + + const double x0 = m_samples[segment].x; + const double a0 = m_samples[segment].arcLen; + const double x1 = m_samples[segment + 1].x; + const double a1 = m_samples[segment + 1].arcLen; + const double a = a0 + (a1 - a0) * (x - x0) / (x1 - x0); + + return a; } diff --git a/math/ArcLengthMapper.h b/math/ArcLengthMapper.h index 1e21fdc81..1b73bb08a 100644 --- a/math/ArcLengthMapper.h +++ b/math/ArcLengthMapper.h @@ -33,78 +33,77 @@ * to be connected by straight lines. */ class ArcLengthMapper { - // Member-wise copying is OK. -public: - class Hint { - friend class ArcLengthMapper; - - public: - Hint(); - - private: - void update(int new_segment); - - int m_lastSegment; - int m_direction; - }; - - - ArcLengthMapper(); - - /** - * \brief Adds an x -> f(x) sample. - * - * Note that x value of every sample has to be bigger than that - * of the previous one. - */ - void addSample(double x, double fx); - - /** - * \brief Returns the total arc length from the first to the last sample. - */ - double totalArcLength() const; - - /** - * \brief Scales arc lengths at every sample so that the - * total arc length becomes equal to the given value. - * - * Obviously, this should be done after all samples have been added. - * After calling this function, totalArcLength() will be returning - * the new value. - */ - void normalizeRange(double total_arc_len); - - /** - * \brief Maps from arc length to the corresponding function argument. - * - * This works even for arc length beyond the first or last samples. - * When interpolation is impossible, the closest sample is returned. - * If no samples are present, zero is returned. Providing the same - * hint on consecutive calls to this function improves performance. - */ - double arcLenToX(double arc_len, Hint& hint) const; - - double xToArcLen(double x, Hint& hint) const; - -private: - struct Sample { - double x; - double arcLen; - - Sample(double x, double arc_len) : x(x), arcLen(arc_len) { - } - }; - - bool checkSegmentForArcLen(double arc_len, int segment) const; - - bool checkSegmentForX(double x, int segment) const; - - double interpolateArcLenInSegment(double arc_len, int segment) const; - - double interpolateXInSegment(double x, int segment) const; - - std::vector m_samples; - double m_prevFX; + // Member-wise copying is OK. + public: + class Hint { + friend class ArcLengthMapper; + + public: + Hint(); + + private: + void update(int new_segment); + + int m_lastSegment; + int m_direction; + }; + + + ArcLengthMapper(); + + /** + * \brief Adds an x -> f(x) sample. + * + * Note that x value of every sample has to be bigger than that + * of the previous one. + */ + void addSample(double x, double fx); + + /** + * \brief Returns the total arc length from the first to the last sample. + */ + double totalArcLength() const; + + /** + * \brief Scales arc lengths at every sample so that the + * total arc length becomes equal to the given value. + * + * Obviously, this should be done after all samples have been added. + * After calling this function, totalArcLength() will be returning + * the new value. + */ + void normalizeRange(double total_arc_len); + + /** + * \brief Maps from arc length to the corresponding function argument. + * + * This works even for arc length beyond the first or last samples. + * When interpolation is impossible, the closest sample is returned. + * If no samples are present, zero is returned. Providing the same + * hint on consecutive calls to this function improves performance. + */ + double arcLenToX(double arc_len, Hint& hint) const; + + double xToArcLen(double x, Hint& hint) const; + + private: + struct Sample { + double x; + double arcLen; + + Sample(double x, double arc_len) : x(x), arcLen(arc_len) {} + }; + + bool checkSegmentForArcLen(double arc_len, int segment) const; + + bool checkSegmentForX(double x, int segment) const; + + double interpolateArcLenInSegment(double arc_len, int segment) const; + + double interpolateXInSegment(double x, int segment) const; + + std::vector m_samples; + double m_prevFX; }; diff --git a/math/CMakeLists.txt b/math/CMakeLists.txt index f16e4079b..c3530f59f 100644 --- a/math/CMakeLists.txt +++ b/math/CMakeLists.txt @@ -1,49 +1,49 @@ -PROJECT("Math library") +project("Math library") -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_BINARY_DIR}") -SET( - GENERIC_SOURCES - LinearSolver.cpp LinearSolver.h - MatrixCalc.h - HomographicTransform.h - SidesOfLine.cpp SidesOfLine.h - ToLineProjector.cpp ToLineProjector.h - ArcLengthMapper.cpp ArcLengthMapper.h - LineIntersectionScalar.cpp LineIntersectionScalar.h - LineBoundedByRect.cpp LineBoundedByRect.h - PolylineIntersector.cpp PolylineIntersector.h - LinearFunction.cpp LinearFunction.h - QuadraticFunction.cpp QuadraticFunction.h - XSpline.cpp XSpline.h +set( + GENERIC_SOURCES + LinearSolver.cpp LinearSolver.h + MatrixCalc.h + HomographicTransform.h + SidesOfLine.cpp SidesOfLine.h + ToLineProjector.cpp ToLineProjector.h + ArcLengthMapper.cpp ArcLengthMapper.h + LineIntersectionScalar.cpp LineIntersectionScalar.h + LineBoundedByRect.cpp LineBoundedByRect.h + PolylineIntersector.cpp PolylineIntersector.h + LinearFunction.cpp LinearFunction.h + QuadraticFunction.cpp QuadraticFunction.h + XSpline.cpp XSpline.h ) -SOURCE_GROUP("Sources" FILES ${GENERIC_SOURCES}) +source_group("Sources" FILES ${GENERIC_SOURCES}) -SET( - SPFIT_SOURCES - spfit/references.txt - spfit/FittableSpline.h - spfit/FrenetFrame.cpp spfit/FrenetFrame.h - spfit/ConstraintSet.cpp spfit/ConstraintSet.h - spfit/SqDistApproximant.cpp spfit/SqDistApproximant.h - spfit/ModelShape.h - spfit/PolylineModelShape.cpp spfit/PolylineModelShape.h - spfit/LinearForceBalancer.cpp spfit/LinearForceBalancer.h - spfit/OptimizationResult.cpp spfit/OptimizationResult.h - spfit/Optimizer.cpp spfit/Optimizer.h - spfit/SplineFitter.cpp spfit/SplineFitter.h +set( + SPFIT_SOURCES + spfit/references.txt + spfit/FittableSpline.h + spfit/FrenetFrame.cpp spfit/FrenetFrame.h + spfit/ConstraintSet.cpp spfit/ConstraintSet.h + spfit/SqDistApproximant.cpp spfit/SqDistApproximant.h + spfit/ModelShape.h + spfit/PolylineModelShape.cpp spfit/PolylineModelShape.h + spfit/LinearForceBalancer.cpp spfit/LinearForceBalancer.h + spfit/OptimizationResult.cpp spfit/OptimizationResult.h + spfit/Optimizer.cpp spfit/Optimizer.h + spfit/SplineFitter.cpp spfit/SplineFitter.h ) -SOURCE_GROUP("Sources\\Spline Fitting Framework" FILES ${SPFIT_SOURCES}) +source_group("Sources\\Spline Fitting Framework" FILES ${SPFIT_SOURCES}) -SET( - ADIFF_SOURCES - adiff/references.txt - adiff/SparseMap.cpp adiff/SparseMap.h - adiff/Function.cpp adiff/Function.h +set( + ADIFF_SOURCES + adiff/references.txt + adiff/SparseMap.cpp adiff/SparseMap.h + adiff/Function.cpp adiff/Function.h ) -SOURCE_GROUP("Sources\\Differentiation Framework" FILES ${ADIFF_SOURCES}) +source_group("Sources\\Differentiation Framework" FILES ${ADIFF_SOURCES}) -ADD_LIBRARY(math STATIC ${GENERIC_SOURCES} ${SPFIT_SOURCES} ${ADIFF_SOURCES}) +add_library(math STATIC ${GENERIC_SOURCES} ${SPFIT_SOURCES} ${ADIFF_SOURCES}) -ADD_SUBDIRECTORY(spfit/tests) -ADD_SUBDIRECTORY(adiff/tests) +add_subdirectory(spfit/tests) +add_subdirectory(adiff/tests) diff --git a/math/HomographicTransform.h b/math/HomographicTransform.h index 64e7aa690..ae1d6fd38 100644 --- a/math/HomographicTransform.h +++ b/math/HomographicTransform.h @@ -19,86 +19,81 @@ #ifndef HOMOGRAPHIC_TRANSFORM_H_ #define HOMOGRAPHIC_TRANSFORM_H_ -#include "VecNT.h" -#include "MatrixCalc.h" #include +#include "MatrixCalc.h" +#include "VecNT.h" -template +template class HomographicTransform; -template +template class HomographicTransformBase { -public: - typedef VecNT Vec; - typedef VecNT<(N + 1) * (N + 1), T> Mat; + public: + typedef VecNT Vec; + typedef VecNT<(N + 1) * (N + 1), T> Mat; - explicit HomographicTransformBase(const Mat& mat) : m_mat(mat) { - } + explicit HomographicTransformBase(const Mat& mat) : m_mat(mat) {} - HomographicTransform inv() const; + HomographicTransform inv() const; - Vec operator()(const Vec& from) const; + Vec operator()(const Vec& from) const; - const Mat& mat() const { - return m_mat; - } + const Mat& mat() const { return m_mat; } -private: - Mat m_mat; + private: + Mat m_mat; }; -template +template class HomographicTransform : public HomographicTransformBase { -public: - explicit HomographicTransform(const typename HomographicTransformBase::Mat& mat) - : HomographicTransformBase(mat) { - } + public: + explicit HomographicTransform(const typename HomographicTransformBase::Mat& mat) + : HomographicTransformBase(mat) {} }; /** An optimized, both in terms of API and performance, 1D version. */ -template +template class HomographicTransform<1, T> : public HomographicTransformBase<1, T> { -public: - explicit HomographicTransform(const typename HomographicTransformBase<1, T>::Mat& mat) - : HomographicTransformBase<1, T>(mat) { - } + public: + explicit HomographicTransform(const typename HomographicTransformBase<1, T>::Mat& mat) + : HomographicTransformBase<1, T>(mat) {} - T operator()(T from) const; + T operator()(T from) const; - // Prevent it's shadowing by the above one. - using HomographicTransformBase<1, T>::operator(); + // Prevent it's shadowing by the above one. + using HomographicTransformBase<1, T>::operator(); }; -template +template HomographicTransform HomographicTransformBase::inv() const { - StaticMatrixCalc mc; - Mat inv_mat; - mc(m_mat, static_cast(N + 1), static_cast(N + 1)).inv().write(inv_mat); + StaticMatrixCalc mc; + Mat inv_mat; + mc(m_mat, static_cast(N + 1), static_cast(N + 1)).inv().write(inv_mat); - return HomographicTransform(inv_mat); + return HomographicTransform(inv_mat); } -template +template typename HomographicTransformBase::Vec HomographicTransformBase::operator()(const Vec& from) const { - StaticMatrixCalc mc; - const VecNT hsrc(from, T(1)); - VecNT hdst; - (mc(m_mat, static_cast(N + 1), static_cast(N + 1)) * mc(hsrc, static_cast(N + 1), 1)).write(hdst); - VecNT res(&hdst[0]); - res /= hdst[N]; - - return res; + StaticMatrixCalc mc; + const VecNT hsrc(from, T(1)); + VecNT hdst; + (mc(m_mat, static_cast(N + 1), static_cast(N + 1)) * mc(hsrc, static_cast(N + 1), 1)).write(hdst); + VecNT res(&hdst[0]); + res /= hdst[N]; + + return res; } -template +template T HomographicTransform<1, T>::operator()(T from) const { - // Optimized version for 1D case. - const T* m = this->mat().data(); + // Optimized version for 1D case. + const T* m = this->mat().data(); - return (from * m[0] + m[2]) / (from * m[1] + m[3]); + return (from * m[0] + m[2]) / (from * m[1] + m[3]); } #endif // ifndef HOMOGRAPHIC_TRANSFORM_H_ diff --git a/math/LineBoundedByRect.cpp b/math/LineBoundedByRect.cpp index 177bb3ea9..78a287664 100644 --- a/math/LineBoundedByRect.cpp +++ b/math/LineBoundedByRect.cpp @@ -21,38 +21,37 @@ #include "NumericTraits.h" bool lineBoundedByRect(QLineF& line, const QRectF& rect) { - const QLineF rect_lines[4] - = {QLineF(rect.topLeft(), rect.topRight()), QLineF(rect.bottomLeft(), rect.bottomRight()), - QLineF(rect.topLeft(), rect.bottomLeft()), QLineF(rect.topRight(), rect.bottomRight())}; - - double max = NumericTraits::min(); - double min = NumericTraits::max(); - - double s1 = 0; - double s2 = 0; - for (const QLineF& rect_line : rect_lines) { - if (!lineIntersectionScalar(rect_line, line, s1, s2)) { - // line is parallel to rect_line. - continue; - } - if ((s1 < 0) || (s1 > 1)) { - // Intersection outside of rect. - continue; - } - - if (s2 > max) { - max = s2; - } - if (s2 < min) { - min = s2; - } + const QLineF rect_lines[4] = {QLineF(rect.topLeft(), rect.topRight()), QLineF(rect.bottomLeft(), rect.bottomRight()), + QLineF(rect.topLeft(), rect.bottomLeft()), QLineF(rect.topRight(), rect.bottomRight())}; + + double max = NumericTraits::min(); + double min = NumericTraits::max(); + + double s1 = 0; + double s2 = 0; + for (const QLineF& rect_line : rect_lines) { + if (!lineIntersectionScalar(rect_line, line, s1, s2)) { + // line is parallel to rect_line. + continue; + } + if ((s1 < 0) || (s1 > 1)) { + // Intersection outside of rect. + continue; } - if (max > min) { - line = QLineF(line.pointAt(min), line.pointAt(max)); - - return true; - } else { - return false; + if (s2 > max) { + max = s2; + } + if (s2 < min) { + min = s2; } + } + + if (max > min) { + line = QLineF(line.pointAt(min), line.pointAt(max)); + + return true; + } else { + return false; + } } // lineBoundedByRect diff --git a/math/LineIntersectionScalar.cpp b/math/LineIntersectionScalar.cpp index be9c21189..fa810b0cb 100644 --- a/math/LineIntersectionScalar.cpp +++ b/math/LineIntersectionScalar.cpp @@ -16,58 +16,58 @@ along with this program. If not, see . */ -#include #include "LineIntersectionScalar.h" +#include bool lineIntersectionScalar(const QLineF& line1, const QLineF& line2, double& s1, double& s2) { - const QPointF p1(line1.p1()); - const QPointF p2(line2.p1()); - const QPointF v1(line1.p2() - line1.p1()); - const QPointF v2(line2.p2() - line2.p1()); - // p1 + s1 * v1 = p2 + s2 * v2 - // which gives us a system of equations: - // s1 * v1.x - s2 * v2.x = p2.x - p1.x - // s1 * v1.y - s2 * v2.y = p2.y - p1.y - // In matrix form: - // [v1 -v2]*x = p2 - p1 - // Taking A = [v1 -v2], b = p2 - p1, and solving it by Cramer's rule: - // s1 = |b -v2|/|A| - // s2 = |v1 b|/|A| - const double det_A = v2.x() * v1.y() - v1.x() * v2.y(); - if (std::fabs(det_A) < std::numeric_limits::epsilon()) { - return false; - } + const QPointF p1(line1.p1()); + const QPointF p2(line2.p1()); + const QPointF v1(line1.p2() - line1.p1()); + const QPointF v2(line2.p2() - line2.p1()); + // p1 + s1 * v1 = p2 + s2 * v2 + // which gives us a system of equations: + // s1 * v1.x - s2 * v2.x = p2.x - p1.x + // s1 * v1.y - s2 * v2.y = p2.y - p1.y + // In matrix form: + // [v1 -v2]*x = p2 - p1 + // Taking A = [v1 -v2], b = p2 - p1, and solving it by Cramer's rule: + // s1 = |b -v2|/|A| + // s2 = |v1 b|/|A| + const double det_A = v2.x() * v1.y() - v1.x() * v2.y(); + if (std::fabs(det_A) < std::numeric_limits::epsilon()) { + return false; + } - const double r_det_A = 1.0 / det_A; - const QPointF b(p2 - p1); - s1 = (v2.x() * b.y() - b.x() * v2.y()) * r_det_A; - s2 = (v1.x() * b.y() - b.x() * v1.y()) * r_det_A; + const double r_det_A = 1.0 / det_A; + const QPointF b(p2 - p1); + s1 = (v2.x() * b.y() - b.x() * v2.y()) * r_det_A; + s2 = (v1.x() * b.y() - b.x() * v1.y()) * r_det_A; - return true; + return true; } bool lineIntersectionScalar(const QLineF& line1, const QLineF& line2, double& s1) { - const QPointF p1(line1.p1()); - const QPointF p2(line2.p1()); - const QPointF v1(line1.p2() - line1.p1()); - const QPointF v2(line2.p2() - line2.p1()); + const QPointF p1(line1.p1()); + const QPointF p2(line2.p1()); + const QPointF v1(line1.p2() - line1.p1()); + const QPointF v2(line2.p2() - line2.p1()); - // p1 + s1 * v1 = p2 + s2 * v2 - // which gives us a system of equations: - // s1 * v1.x - s2 * v2.x = p2.x - p1.x - // s1 * v1.y - s2 * v2.y = p2.y - p1.y - // In matrix form: - // [v1 -v2]*x = p2 - p1 - // Taking A = [v1 -v2], b = p2 - p1, and solving it by Cramer's rule: - // s1 = |b -v2|/|A| - // s2 = |v1 b|/|A| - const double det_A = v2.x() * v1.y() - v1.x() * v2.y(); - if (std::fabs(det_A) < std::numeric_limits::epsilon()) { - return false; - } + // p1 + s1 * v1 = p2 + s2 * v2 + // which gives us a system of equations: + // s1 * v1.x - s2 * v2.x = p2.x - p1.x + // s1 * v1.y - s2 * v2.y = p2.y - p1.y + // In matrix form: + // [v1 -v2]*x = p2 - p1 + // Taking A = [v1 -v2], b = p2 - p1, and solving it by Cramer's rule: + // s1 = |b -v2|/|A| + // s2 = |v1 b|/|A| + const double det_A = v2.x() * v1.y() - v1.x() * v2.y(); + if (std::fabs(det_A) < std::numeric_limits::epsilon()) { + return false; + } - const QPointF b(p2 - p1); - s1 = (v2.x() * b.y() - b.x() * v2.y()) / det_A; + const QPointF b(p2 - p1); + s1 = (v2.x() * b.y() - b.x() * v2.y()) / det_A; - return true; + return true; } diff --git a/math/LinearFunction.cpp b/math/LinearFunction.cpp index c1f710565..56640f2a9 100644 --- a/math/LinearFunction.cpp +++ b/math/LinearFunction.cpp @@ -19,40 +19,39 @@ #include "LinearFunction.h" #include -LinearFunction::LinearFunction(size_t num_vars) : a(num_vars), b(0) { -} +LinearFunction::LinearFunction(size_t num_vars) : a(num_vars), b(0) {} void LinearFunction::reset() { - a.fill(0); - b = 0; + a.fill(0); + b = 0; } double LinearFunction::evaluate(const double* x) const { - const size_t num_vars = numVars(); + const size_t num_vars = numVars(); - double sum = b; - for (size_t i = 0; i < num_vars; ++i) { - sum += a[i] * x[i]; - } + double sum = b; + for (size_t i = 0; i < num_vars; ++i) { + sum += a[i] * x[i]; + } - return sum; + return sum; } void LinearFunction::swap(LinearFunction& other) { - a.swap(other.a); - std::swap(b, other.b); + a.swap(other.a); + std::swap(b, other.b); } LinearFunction& LinearFunction::operator+=(const LinearFunction& other) { - a += other.a; - b += other.b; + a += other.a; + b += other.b; - return *this; + return *this; } LinearFunction& LinearFunction::operator*=(double scalar) { - a *= scalar; - b *= scalar; + a *= scalar; + b *= scalar; - return *this; + return *this; } diff --git a/math/LinearFunction.h b/math/LinearFunction.h index e02befe69..307f35be2 100644 --- a/math/LinearFunction.h +++ b/math/LinearFunction.h @@ -19,8 +19,8 @@ #ifndef LINEAR_FUNCTION_H_ #define LINEAR_FUNCTION_H_ -#include "VecT.h" #include +#include "VecT.h" /** * A linear function from arbitrary number of variables @@ -30,41 +30,39 @@ * \endcode */ class LinearFunction { - // Member-wise copying is OK. -public: - VecT a; - double b; + // Member-wise copying is OK. + public: + VecT a; + double b; - /** - * Constructs a linear function of the given number of variables, - * initializing everything to zero. - */ - explicit LinearFunction(size_t num_vars = 0); + /** + * Constructs a linear function of the given number of variables, + * initializing everything to zero. + */ + explicit LinearFunction(size_t num_vars = 0); - /** - * Resets everything to zero, so that F(x) = 0 - */ - void reset(); + /** + * Resets everything to zero, so that F(x) = 0 + */ + void reset(); - size_t numVars() const { - return a.size(); - } + size_t numVars() const { return a.size(); } - /** - * Evaluates a^T * x + b - */ - double evaluate(const double* x) const; + /** + * Evaluates a^T * x + b + */ + double evaluate(const double* x) const; - void swap(LinearFunction& other); + void swap(LinearFunction& other); - LinearFunction& operator+=(const LinearFunction& other); + LinearFunction& operator+=(const LinearFunction& other); - LinearFunction& operator*=(double scalar); + LinearFunction& operator*=(double scalar); }; inline void swap(LinearFunction& f1, LinearFunction& f2) { - f1.swap(f2); + f1.swap(f2); } #endif // ifndef LINEAR_FUNCTION_H_ diff --git a/math/LinearSolver.cpp b/math/LinearSolver.cpp index b8755fd72..d5aa284d0 100644 --- a/math/LinearSolver.cpp +++ b/math/LinearSolver.cpp @@ -19,8 +19,8 @@ #include "LinearSolver.h" LinearSolver::LinearSolver(size_t rows_AB, size_t cols_A_rows_X, size_t cols_BX) - : m_rowsAB(rows_AB), m_colsArowsX(cols_A_rows_X), m_colsBX(cols_BX) { - if (m_rowsAB < m_colsArowsX) { - throw std::runtime_error("LinearSolver: can's solve underdetermined systems"); - } + : m_rowsAB(rows_AB), m_colsArowsX(cols_A_rows_X), m_colsBX(cols_BX) { + if (m_rowsAB < m_colsArowsX) { + throw std::runtime_error("LinearSolver: can's solve underdetermined systems"); + } } diff --git a/math/LinearSolver.h b/math/LinearSolver.h index 91db43190..484c6d1bb 100644 --- a/math/LinearSolver.h +++ b/math/LinearSolver.h @@ -19,15 +19,15 @@ #ifndef LINEAR_SOLVER_H_ #define LINEAR_SOLVER_H_ -#include "NonCopyable.h" -#include "StaticPool.h" +#include +#include #include +#include #include #include #include -#include -#include -#include +#include "NonCopyable.h" +#include "StaticPool.h" /** * \brief Solves Ax = b using LU decomposition. @@ -40,181 +40,181 @@ * \see MatrixCalc */ class LinearSolver { - // Member-wise copying is OK. -public: - /* - * \throw std::runtime_error If rows_AB < cols_A_rows_X. - */ - LinearSolver(size_t rows_AB, size_t cols_A_rows_X, size_t cols_BX); - - /** - * \brief Solves Ax = b - * - * \param A Matrix A. - * \param X Matrix (or vector) X. Results will be written here. - * \param B Matrix (or vector) B. It's allowed to pass the same pointer for X and B. - * \param tbuffer Temporary buffer of at least "cols(A) * (rows(B) + cols(B))" T elements. - * \param pbuffer Temporary buffer of at least "rows(B)" size_t elements. - * - * \throw std::runtime_error If the system can't be solved. - */ - template - void solve(const T* A, T* X, const T* B, T* tbuffer, size_t* pbuffer) const; - - /** - * \brief A simplified version of the one above. - * - * In this version, buffers are allocated internally. - */ - template - void solve(const T* A, T* X, const T* B) const; - -private: - size_t m_rowsAB; - size_t m_colsArowsX; - size_t m_colsBX; + // Member-wise copying is OK. + public: + /* + * \throw std::runtime_error If rows_AB < cols_A_rows_X. + */ + LinearSolver(size_t rows_AB, size_t cols_A_rows_X, size_t cols_BX); + + /** + * \brief Solves Ax = b + * + * \param A Matrix A. + * \param X Matrix (or vector) X. Results will be written here. + * \param B Matrix (or vector) B. It's allowed to pass the same pointer for X and B. + * \param tbuffer Temporary buffer of at least "cols(A) * (rows(B) + cols(B))" T elements. + * \param pbuffer Temporary buffer of at least "rows(B)" size_t elements. + * + * \throw std::runtime_error If the system can't be solved. + */ + template + void solve(const T* A, T* X, const T* B, T* tbuffer, size_t* pbuffer) const; + + /** + * \brief A simplified version of the one above. + * + * In this version, buffers are allocated internally. + */ + template + void solve(const T* A, T* X, const T* B) const; + + private: + size_t m_rowsAB; + size_t m_colsArowsX; + size_t m_colsBX; }; -template +template void LinearSolver::solve(const T* A, T* X, const T* B, T* tbuffer, size_t* pbuffer) const { - using namespace std; // To catch different overloads of std::abs() - const T epsilon(std::sqrt(numeric_limits::epsilon())); - - const size_t num_elements_A = m_rowsAB * m_colsArowsX; - - T* const lu_data = tbuffer; // Dimensions: m_rowsAB, m_colsArowsX - tbuffer += num_elements_A; - - // Copy this matrix to lu. - for (size_t i = 0; i < num_elements_A; ++i) { - lu_data[i] = A[i]; + using namespace std; // To catch different overloads of std::abs() + const T epsilon(std::sqrt(numeric_limits::epsilon())); + + const size_t num_elements_A = m_rowsAB * m_colsArowsX; + + T* const lu_data = tbuffer; // Dimensions: m_rowsAB, m_colsArowsX + tbuffer += num_elements_A; + + // Copy this matrix to lu. + for (size_t i = 0; i < num_elements_A; ++i) { + lu_data[i] = A[i]; + } + + // Maps virtual row numbers to physical ones. + size_t* const perm = pbuffer; + for (size_t i = 0; i < m_rowsAB; ++i) { + perm[i] = i; + } + + T* p_col = lu_data; + for (size_t i = 0; i < m_colsArowsX; ++i, p_col += m_rowsAB) { + // Find the largest pivot. + size_t virt_pivot_row = i; + T largest_abs_pivot(std::abs(p_col[perm[i]])); + for (size_t j = i + 1; j < m_rowsAB; ++j) { + const T abs_pivot(std::abs(p_col[perm[j]])); + if (abs_pivot > largest_abs_pivot) { + largest_abs_pivot = abs_pivot; + virt_pivot_row = j; + } } - // Maps virtual row numbers to physical ones. - size_t* const perm = pbuffer; - for (size_t i = 0; i < m_rowsAB; ++i) { - perm[i] = i; + if (largest_abs_pivot <= epsilon) { + throw std::runtime_error("LinearSolver: not a full rank matrix"); } - T* p_col = lu_data; - for (size_t i = 0; i < m_colsArowsX; ++i, p_col += m_rowsAB) { - // Find the largest pivot. - size_t virt_pivot_row = i; - T largest_abs_pivot(std::abs(p_col[perm[i]])); - for (size_t j = i + 1; j < m_rowsAB; ++j) { - const T abs_pivot(std::abs(p_col[perm[j]])); - if (abs_pivot > largest_abs_pivot) { - largest_abs_pivot = abs_pivot; - virt_pivot_row = j; - } - } - - if (largest_abs_pivot <= epsilon) { - throw std::runtime_error("LinearSolver: not a full rank matrix"); - } - - const size_t phys_pivot_row(perm[virt_pivot_row]); - perm[virt_pivot_row] = perm[i]; - perm[i] = phys_pivot_row; - - const T* const p_pivot = p_col + phys_pivot_row; - const T r_pivot(T(1) / *p_pivot); - - // Eliminate entries below the pivot. - for (size_t j = i + 1; j < m_rowsAB; ++j) { - const T* p1 = p_pivot; - T* p2 = p_col + perm[j]; - if (std::abs(*p2) <= epsilon) { - // We consider it's already zero. - *p2 = T(); - continue; - } - - const T factor(*p2 * r_pivot); - *p2 = factor; // Factor goes into L, zero goes into U. - // Transform the rest of the row. - for (size_t col = i + 1; col < m_colsArowsX; ++col) { - p1 += m_rowsAB; - p2 += m_rowsAB; - *p2 -= *p1 * factor; - } - } + const size_t phys_pivot_row(perm[virt_pivot_row]); + perm[virt_pivot_row] = perm[i]; + perm[i] = phys_pivot_row; + + const T* const p_pivot = p_col + phys_pivot_row; + const T r_pivot(T(1) / *p_pivot); + + // Eliminate entries below the pivot. + for (size_t j = i + 1; j < m_rowsAB; ++j) { + const T* p1 = p_pivot; + T* p2 = p_col + perm[j]; + if (std::abs(*p2) <= epsilon) { + // We consider it's already zero. + *p2 = T(); + continue; + } + + const T factor(*p2 * r_pivot); + *p2 = factor; // Factor goes into L, zero goes into U. + // Transform the rest of the row. + for (size_t col = i + 1; col < m_colsArowsX; ++col) { + p1 += m_rowsAB; + p2 += m_rowsAB; + *p2 -= *p1 * factor; + } + } + } + + // First solve Ly = b + T* const y_data = tbuffer; // Dimensions: m_colsArowsX, m_colsBX + // tbuffer += m_colsArowsX * m_colsBX; + T* p_y_col = y_data; + const T* p_b_col = B; + for (size_t y_col = 0; y_col < m_colsBX; ++y_col) { + size_t virt_row = 0; + for (; virt_row < m_colsArowsX; ++virt_row) { + const int phys_row = static_cast(perm[virt_row]); + T right(p_b_col[phys_row]); + + // Move already calculated factors to the right side. + const T* p_lu = lu_data + phys_row; + // Go left to right, stop at diagonal. + for (size_t lu_col = 0; lu_col < virt_row; ++lu_col) { + right -= *p_lu * p_y_col[lu_col]; + p_lu += m_rowsAB; + } + + // We assume L has ones on the diagonal, so no division here. + p_y_col[virt_row] = right; } - // First solve Ly = b - T* const y_data = tbuffer; // Dimensions: m_colsArowsX, m_colsBX - // tbuffer += m_colsArowsX * m_colsBX; - T* p_y_col = y_data; - const T* p_b_col = B; - for (size_t y_col = 0; y_col < m_colsBX; ++y_col) { - size_t virt_row = 0; - for (; virt_row < m_colsArowsX; ++virt_row) { - const int phys_row = static_cast(perm[virt_row]); - T right(p_b_col[phys_row]); - - // Move already calculated factors to the right side. - const T* p_lu = lu_data + phys_row; - // Go left to right, stop at diagonal. - for (size_t lu_col = 0; lu_col < virt_row; ++lu_col) { - right -= *p_lu * p_y_col[lu_col]; - p_lu += m_rowsAB; - } - - // We assume L has ones on the diagonal, so no division here. - p_y_col[virt_row] = right; - } - - // Continue below the square part (if any). - for (; virt_row < m_rowsAB; ++virt_row) { - const int phys_row = static_cast(perm[virt_row]); - T right(p_b_col[phys_row]); - - // Move everything to the right side, then verify it's zero. - const T* p_lu = lu_data + phys_row; - // Go left to right all the way. - for (size_t lu_col = 0; lu_col < m_colsArowsX; ++lu_col) { - right -= *p_lu * p_y_col[lu_col]; - p_lu += m_rowsAB; - } - if (std::abs(right) > epsilon) { - throw std::runtime_error("LinearSolver: inconsistent overdetermined system"); - } - } - - p_y_col += m_colsArowsX; - p_b_col += m_rowsAB; + // Continue below the square part (if any). + for (; virt_row < m_rowsAB; ++virt_row) { + const int phys_row = static_cast(perm[virt_row]); + T right(p_b_col[phys_row]); + + // Move everything to the right side, then verify it's zero. + const T* p_lu = lu_data + phys_row; + // Go left to right all the way. + for (size_t lu_col = 0; lu_col < m_colsArowsX; ++lu_col) { + right -= *p_lu * p_y_col[lu_col]; + p_lu += m_rowsAB; + } + if (std::abs(right) > epsilon) { + throw std::runtime_error("LinearSolver: inconsistent overdetermined system"); + } } - // Now solve Ux = y - T* p_x_col = X; - p_y_col = y_data; - const T* p_lu_last_col = lu_data + (m_colsArowsX - 1) * m_rowsAB; - for (size_t x_col = 0; x_col < m_colsBX; ++x_col) { - for (int virt_row = static_cast(m_colsArowsX - 1); virt_row >= 0; --virt_row) { - T right(p_y_col[virt_row]); - - // Move already calculated factors to the right side. - const T* p_lu = p_lu_last_col + perm[virt_row]; - // Go right to left, stop at diagonal. - for (int lu_col = static_cast(m_colsArowsX - 1); lu_col > virt_row; --lu_col) { - right -= *p_lu * p_x_col[lu_col]; - p_lu -= m_rowsAB; - } - p_x_col[virt_row] = right / *p_lu; - } - - p_x_col += m_colsArowsX; - p_y_col += m_colsArowsX; + p_y_col += m_colsArowsX; + p_b_col += m_rowsAB; + } + + // Now solve Ux = y + T* p_x_col = X; + p_y_col = y_data; + const T* p_lu_last_col = lu_data + (m_colsArowsX - 1) * m_rowsAB; + for (size_t x_col = 0; x_col < m_colsBX; ++x_col) { + for (int virt_row = static_cast(m_colsArowsX - 1); virt_row >= 0; --virt_row) { + T right(p_y_col[virt_row]); + + // Move already calculated factors to the right side. + const T* p_lu = p_lu_last_col + perm[virt_row]; + // Go right to left, stop at diagonal. + for (int lu_col = static_cast(m_colsArowsX - 1); lu_col > virt_row; --lu_col) { + right -= *p_lu * p_x_col[lu_col]; + p_lu -= m_rowsAB; + } + p_x_col[virt_row] = right / *p_lu; } + + p_x_col += m_colsArowsX; + p_y_col += m_colsArowsX; + } } // LinearSolver::solve -template +template void LinearSolver::solve(const T* A, T* X, const T* B) const { - boost::scoped_array tbuffer(new T[m_colsArowsX * (m_rowsAB + m_colsBX)]); - boost::scoped_array pbuffer(new size_t[m_rowsAB]); + boost::scoped_array tbuffer(new T[m_colsArowsX * (m_rowsAB + m_colsBX)]); + boost::scoped_array pbuffer(new size_t[m_rowsAB]); - solve(A, X, B, tbuffer.get(), pbuffer.get()); + solve(A, X, B, tbuffer.get(), pbuffer.get()); } #endif // ifndef LINEAR_SOLVER_H_ diff --git a/math/MatrixCalc.h b/math/MatrixCalc.h index 5537891f1..b7cd68cbf 100644 --- a/math/MatrixCalc.h +++ b/math/MatrixCalc.h @@ -19,352 +19,338 @@ #ifndef MATRIX_CALC_H_ #define MATRIX_CALC_H_ -#include "NonCopyable.h" -#include "StaticPool.h" +#include +#include #include "DynamicPool.h" #include "LinearSolver.h" #include "MatMNT.h" #include "MatT.h" +#include "NonCopyable.h" +#include "StaticPool.h" #include "VecNT.h" #include "VecT.h" -#include -#include -template +template class MatrixCalc; namespace mcalc { -template +template class AbstractAllocator { -public: - virtual T* allocT(size_t size) = 0; + public: + virtual T* allocT(size_t size) = 0; - virtual size_t* allocP(size_t size) = 0; + virtual size_t* allocP(size_t size) = 0; }; -template +template class StaticPoolAllocator : public AbstractAllocator { -public: - virtual T* allocT(size_t size) { - return m_poolT.alloc(size); - } + public: + virtual T* allocT(size_t size) { return m_poolT.alloc(size); } - virtual size_t* allocP(size_t size) { - return m_poolP.alloc(size); - } + virtual size_t* allocP(size_t size) { return m_poolP.alloc(size); } -private: - StaticPool m_poolP; - StaticPool m_poolT; + private: + StaticPool m_poolP; + StaticPool m_poolT; }; -template +template class DynamicPoolAllocator : public AbstractAllocator { -public: - virtual T* allocT(size_t size) { - return m_poolT.alloc(size); - } + public: + virtual T* allocT(size_t size) { return m_poolT.alloc(size); } - virtual size_t* allocP(size_t size) { - return m_poolP.alloc(size); - } + virtual size_t* allocP(size_t size) { return m_poolP.alloc(size); } -private: - DynamicPool m_poolP; - DynamicPool m_poolT; + private: + DynamicPool m_poolP; + DynamicPool m_poolT; }; -template +template class Mat { - template - friend class ::MatrixCalc; + template + friend class ::MatrixCalc; - template - friend Mat operator+(const Mat& m1, const Mat& m2); + template + friend Mat operator+(const Mat& m1, const Mat& m2); - template - friend Mat operator-(const Mat& m1, const Mat& m2); + template + friend Mat operator-(const Mat& m1, const Mat& m2); - template - friend Mat operator*(const Mat& m1, const Mat& m2); + template + friend Mat operator*(const Mat& m1, const Mat& m2); - template - friend Mat operator*(OT scalar, const Mat& m); + template + friend Mat operator*(OT scalar, const Mat& m); - template - friend Mat operator*(const Mat& m, OT scalar); + template + friend Mat operator*(const Mat& m, OT scalar); - template - friend Mat operator/(const Mat& m, OT scalar); + template + friend Mat operator/(const Mat& m, OT scalar); -public: - Mat inv() const; + public: + Mat inv() const; - Mat solve(const Mat& b) const; + Mat solve(const Mat& b) const; - Mat solve(const T* data, int rows, int cols) const; + Mat solve(const T* data, int rows, int cols) const; - Mat trans() const; + Mat trans() const; - Mat write(T* buf) const; + Mat write(T* buf) const; - template - Mat write(VecNT& vec) const; + template + Mat write(VecNT& vec) const; - Mat transWrite(T* buf) const; + Mat transWrite(T* buf) const; - template - Mat transWrite(VecNT& vec) const; + template + Mat transWrite(VecNT& vec) const; - Mat operator-() const; + Mat operator-() const; - const T* rawData() const { - return data; - } + const T* rawData() const { return data; } -private: - Mat(AbstractAllocator* alloc, const T* data, int rows, int cols) - : alloc(alloc), data(data), rows(rows), cols(cols) { - } + private: + Mat(AbstractAllocator* alloc, const T* data, int rows, int cols) + : alloc(alloc), data(data), rows(rows), cols(cols) {} - AbstractAllocator* alloc; - const T* data; - int rows; - int cols; + AbstractAllocator* alloc; + const T* data; + int rows; + int cols; }; } // namespace mcalc -template> +template > class MatrixCalc { - DECLARE_NON_COPYABLE(MatrixCalc) + DECLARE_NON_COPYABLE(MatrixCalc) -public: - MatrixCalc() { - } + public: + MatrixCalc() {} - mcalc::Mat operator()(const T* data, int rows, int cols) { - return mcalc::Mat(&m_alloc, data, rows, cols); - } + mcalc::Mat operator()(const T* data, int rows, int cols) { return mcalc::Mat(&m_alloc, data, rows, cols); } - template - mcalc::Mat operator()(const VecNT& vec, int rows, int cols) { - return mcalc::Mat(&m_alloc, vec.data(), rows, cols); - } + template + mcalc::Mat operator()(const VecNT& vec, int rows, int cols) { + return mcalc::Mat(&m_alloc, vec.data(), rows, cols); + } - template - mcalc::Mat operator()(const MatMNT& mat) { - return mcalc::Mat(&m_alloc, mat.data(), mat.ROWS, mat.COLS); - } + template + mcalc::Mat operator()(const MatMNT& mat) { + return mcalc::Mat(&m_alloc, mat.data(), mat.ROWS, mat.COLS); + } - mcalc::Mat operator()(const MatT& mat) { - return mcalc::Mat(&m_alloc, mat.data(), static_cast(mat.rows()), static_cast(mat.cols())); - } + mcalc::Mat operator()(const MatT& mat) { + return mcalc::Mat(&m_alloc, mat.data(), static_cast(mat.rows()), static_cast(mat.cols())); + } - template - mcalc::Mat operator()(const VecNT& vec) { - return mcalc::Mat(&m_alloc, vec.data(), vec.SIZE, 1); - } + template + mcalc::Mat operator()(const VecNT& vec) { + return mcalc::Mat(&m_alloc, vec.data(), vec.SIZE, 1); + } - mcalc::Mat operator()(const VecT& vec) { - return mcalc::Mat(&m_alloc, vec.data(), static_cast(vec.size()), 1); - } + mcalc::Mat operator()(const VecT& vec) { + return mcalc::Mat(&m_alloc, vec.data(), static_cast(vec.size()), 1); + } -private: - Alloc m_alloc; + private: + Alloc m_alloc; }; -template +template class StaticMatrixCalc : public MatrixCalc> {}; -template +template class DynamicMatrixCalc : public MatrixCalc> {}; /*========================== Implementation =============================*/ namespace mcalc { -template +template Mat Mat::inv() const { - assert(cols == rows); - - T* ident_data = alloc->allocT(rows * cols); - Mat ident(alloc, ident_data, rows, cols); - const int todo = rows * cols; - for (int i = 0; i < todo; ++i) { - ident_data[i] = T(); - } - for (int i = 0; i < todo; i += rows + 1) { - ident_data[i] = T(1); - } - - return solve(ident); + assert(cols == rows); + + T* ident_data = alloc->allocT(rows * cols); + Mat ident(alloc, ident_data, rows, cols); + const int todo = rows * cols; + for (int i = 0; i < todo; ++i) { + ident_data[i] = T(); + } + for (int i = 0; i < todo; i += rows + 1) { + ident_data[i] = T(1); + } + + return solve(ident); } -template +template Mat Mat::solve(const Mat& b) const { - assert(rows == b.rows); + assert(rows == b.rows); - T* x_data = alloc->allocT(cols * b.cols); - T* tbuffer = alloc->allocT(cols * (b.rows + b.cols)); - size_t* pbuffer = alloc->allocP(rows); - LinearSolver(rows, cols, b.cols).solve(data, x_data, b.data, tbuffer, pbuffer); + T* x_data = alloc->allocT(cols * b.cols); + T* tbuffer = alloc->allocT(cols * (b.rows + b.cols)); + size_t* pbuffer = alloc->allocP(rows); + LinearSolver(rows, cols, b.cols).solve(data, x_data, b.data, tbuffer, pbuffer); - return Mat(alloc, x_data, cols, b.cols); + return Mat(alloc, x_data, cols, b.cols); } -template +template Mat Mat::solve(const T* data, int rows, int cols) const { - return solve(Mat(alloc, data, rows, cols)); + return solve(Mat(alloc, data, rows, cols)); } -template +template Mat Mat::trans() const { - if ((cols == 1) || (rows == 1)) { - return Mat(alloc, data, cols, rows); - } + if ((cols == 1) || (rows == 1)) { + return Mat(alloc, data, cols, rows); + } - T* p_trans = alloc->allocT(cols * rows); - transWrite(p_trans); + T* p_trans = alloc->allocT(cols * rows); + transWrite(p_trans); - return Mat(alloc, p_trans, cols, rows); + return Mat(alloc, p_trans, cols, rows); } -template +template Mat Mat::write(T* buf) const { - const int todo = rows * cols; - for (int i = 0; i < todo; ++i) { - buf[i] = data[i]; - } + const int todo = rows * cols; + for (int i = 0; i < todo; ++i) { + buf[i] = data[i]; + } - return *this; + return *this; } -template -template +template +template Mat Mat::write(VecNT& vec) const { - assert(N >= size_t(rows * cols)); + assert(N >= size_t(rows * cols)); - return write(vec.data()); + return write(vec.data()); } -template +template Mat Mat::transWrite(T* buf) const { - T* p_trans = buf; - for (int i = 0; i < rows; ++i) { - const T* p_src = data + i; - for (int j = 0; j < cols; ++j) { - *p_trans = *p_src; - ++p_trans; - p_src += rows; - } + T* p_trans = buf; + for (int i = 0; i < rows; ++i) { + const T* p_src = data + i; + for (int j = 0; j < cols; ++j) { + *p_trans = *p_src; + ++p_trans; + p_src += rows; } + } - return *this; + return *this; } -template -template +template +template Mat Mat::transWrite(VecNT& vec) const { - assert(N >= rows * cols); + assert(N >= rows * cols); - return transWrite(vec.data()); + return transWrite(vec.data()); } /** Unary minus. */ -template +template Mat Mat::operator-() const { - T* p_res = alloc->allocT(rows * cols); - Mat res(alloc, p_res, rows, cols); + T* p_res = alloc->allocT(rows * cols); + Mat res(alloc, p_res, rows, cols); - const int todo = rows * cols; - for (int i = 0; i < todo; ++i) { - p_res[i] = -data[i]; - } + const int todo = rows * cols; + for (int i = 0; i < todo; ++i) { + p_res[i] = -data[i]; + } - return res; + return res; } -template +template Mat operator+(const Mat& m1, const Mat& m2) { - assert(m1.rows == m2.rows && m1.cols == m2.cols); + assert(m1.rows == m2.rows && m1.cols == m2.cols); - T* p_res = m1.alloc->allocT(m1.rows * m1.cols); - Mat res(m1.alloc, p_res, m1.rows, m1.cols); + T* p_res = m1.alloc->allocT(m1.rows * m1.cols); + Mat res(m1.alloc, p_res, m1.rows, m1.cols); - const int todo = m1.rows * m1.cols; - for (int i = 0; i < todo; ++i) { - p_res[i] = m1.data[i] + m2.data[i]; - } + const int todo = m1.rows * m1.cols; + for (int i = 0; i < todo; ++i) { + p_res[i] = m1.data[i] + m2.data[i]; + } - return res; + return res; } -template +template Mat operator-(const Mat& m1, const Mat& m2) { - assert(m1.rows == m2.rows && m1.cols == m2.cols); + assert(m1.rows == m2.rows && m1.cols == m2.cols); - T* p_res = m1.alloc->allocT(m1.rows * m1.cols); - Mat res(m1.alloc, p_res, m1.rows, m1.cols); + T* p_res = m1.alloc->allocT(m1.rows * m1.cols); + Mat res(m1.alloc, p_res, m1.rows, m1.cols); - const int todo = m1.rows * m1.cols; - for (int i = 0; i < todo; ++i) { - p_res[i] = m1.data[i] - m2.data[i]; - } + const int todo = m1.rows * m1.cols; + for (int i = 0; i < todo; ++i) { + p_res[i] = m1.data[i] - m2.data[i]; + } - return res; + return res; } -template +template Mat operator*(const Mat& m1, const Mat& m2) { - assert(m1.cols == m2.rows); - - T* p_res = m1.alloc->allocT(m1.rows * m2.cols); - Mat res(m1.alloc, p_res, m1.rows, m2.cols); - - for (int rcol = 0; rcol < res.cols; ++rcol) { - for (int rrow = 0; rrow < res.rows; ++rrow) { - const T* p_m1 = m1.data + rrow; - const T* p_m2 = m2.data + rcol * m2.rows; - T sum = T(); - for (int i = 0; i < m1.cols; ++i) { - sum += *p_m1 * *p_m2; - p_m1 += m1.rows; - ++p_m2; - } - *p_res = sum; - ++p_res; - } + assert(m1.cols == m2.rows); + + T* p_res = m1.alloc->allocT(m1.rows * m2.cols); + Mat res(m1.alloc, p_res, m1.rows, m2.cols); + + for (int rcol = 0; rcol < res.cols; ++rcol) { + for (int rrow = 0; rrow < res.rows; ++rrow) { + const T* p_m1 = m1.data + rrow; + const T* p_m2 = m2.data + rcol * m2.rows; + T sum = T(); + for (int i = 0; i < m1.cols; ++i) { + sum += *p_m1 * *p_m2; + p_m1 += m1.rows; + ++p_m2; + } + *p_res = sum; + ++p_res; } + } - return res; + return res; } -template +template Mat operator*(T scalar, const Mat& m) { - T* p_res = m.alloc->allocT(m.rows * m.cols); - Mat res(m.alloc, p_res, m.rows, m.cols); + T* p_res = m.alloc->allocT(m.rows * m.cols); + Mat res(m.alloc, p_res, m.rows, m.cols); - const int todo = m.rows * m.cols; - for (int i = 0; i < todo; ++i) { - p_res[i] = m.data[i] * scalar; - } + const int todo = m.rows * m.cols; + for (int i = 0; i < todo; ++i) { + p_res[i] = m.data[i] * scalar; + } - return res; + return res; } -template +template Mat operator*(const Mat& m, T scalar) { - return scalar * m; + return scalar * m; } -template +template Mat operator/(const Mat& m, T scalar) { - return m * (1.0f / scalar); + return m * (1.0f / scalar); } } // namespace mcalc #endif // ifndef MATRIX_CALC_H_ diff --git a/math/PolylineIntersector.cpp b/math/PolylineIntersector.cpp index 5851841bf..c445f982b 100644 --- a/math/PolylineIntersector.cpp +++ b/math/PolylineIntersector.cpp @@ -16,132 +16,130 @@ along with this program. If not, see . */ -#include #include "PolylineIntersector.h" +#include #include "ToLineProjector.h" -PolylineIntersector::Hint::Hint() : m_lastSegment(0), m_direction(1) { -} +PolylineIntersector::Hint::Hint() : m_lastSegment(0), m_direction(1) {} void PolylineIntersector::Hint::update(int new_segment) { - m_direction = new_segment < m_lastSegment ? -1 : 1; - m_lastSegment = new_segment; + m_direction = new_segment < m_lastSegment ? -1 : 1; + m_lastSegment = new_segment; } PolylineIntersector::PolylineIntersector(const std::vector& polyline) - : m_polyline(polyline), m_numSegments(static_cast(polyline.size() - 1)) { -} + : m_polyline(polyline), m_numSegments(static_cast(polyline.size() - 1)) {} QPointF PolylineIntersector::intersect(const QLineF& line, Hint& hint) const { - const QLineF normal(line.normalVector()); + const QLineF normal(line.normalVector()); - if (intersectsSegment(normal, hint.m_lastSegment)) { - return intersectWithSegment(line, hint.m_lastSegment); - } + if (intersectsSegment(normal, hint.m_lastSegment)) { + return intersectWithSegment(line, hint.m_lastSegment); + } - int segment; + int segment; - // Check the next segment in direction provided by hint. - if (intersectsSegment(normal, (segment = hint.m_lastSegment + hint.m_direction))) { - hint.update(segment); + // Check the next segment in direction provided by hint. + if (intersectsSegment(normal, (segment = hint.m_lastSegment + hint.m_direction))) { + hint.update(segment); - return intersectWithSegment(line, segment); - } + return intersectWithSegment(line, segment); + } - // Check the next segment in opposite direction. - if (intersectsSegment(normal, (segment = hint.m_lastSegment - hint.m_direction))) { - hint.update(segment); + // Check the next segment in opposite direction. + if (intersectsSegment(normal, (segment = hint.m_lastSegment - hint.m_direction))) { + hint.update(segment); - return intersectWithSegment(line, segment); - } + return intersectWithSegment(line, segment); + } - // Does the whole polyline intersect our line? - QPointF intersection; - if (tryIntersectingOutsideOfPolyline(line, intersection, hint)) { - return intersection; - } - // OK, let's do a binary search then. - const QPointF origin(normal.p1()); - const Vec2d nv(normal.p2() - normal.p1()); - int left_idx = 0; - auto right_idx = static_cast(m_polyline.size() - 1); - double left_dot = nv.dot(m_polyline[left_idx] - origin); - - while (left_idx + 1 < right_idx) { - const int mid_idx = (left_idx + right_idx) >> 1; - const double mid_dot = nv.dot(m_polyline[mid_idx] - origin); - - if (mid_dot * left_dot <= 0) { - // Note: <= 0 vs < 0 is actually important for this branch. - // 0 would indicate either left or mid point is our exact answer. - right_idx = mid_idx; - } else { - left_idx = mid_idx; - left_dot = mid_dot; - } + // Does the whole polyline intersect our line? + QPointF intersection; + if (tryIntersectingOutsideOfPolyline(line, intersection, hint)) { + return intersection; + } + // OK, let's do a binary search then. + const QPointF origin(normal.p1()); + const Vec2d nv(normal.p2() - normal.p1()); + int left_idx = 0; + auto right_idx = static_cast(m_polyline.size() - 1); + double left_dot = nv.dot(m_polyline[left_idx] - origin); + + while (left_idx + 1 < right_idx) { + const int mid_idx = (left_idx + right_idx) >> 1; + const double mid_dot = nv.dot(m_polyline[mid_idx] - origin); + + if (mid_dot * left_dot <= 0) { + // Note: <= 0 vs < 0 is actually important for this branch. + // 0 would indicate either left or mid point is our exact answer. + right_idx = mid_idx; + } else { + left_idx = mid_idx; + left_dot = mid_dot; } + } - hint.update(left_idx); + hint.update(left_idx); - return intersectWithSegment(line, left_idx); + return intersectWithSegment(line, left_idx); } // PolylineIntersector::intersect bool PolylineIntersector::intersectsSegment(const QLineF& normal, int segment) const { - if ((segment < 0) || (segment >= m_numSegments)) { - return false; - } + if ((segment < 0) || (segment >= m_numSegments)) { + return false; + } - const QLineF seg_line(m_polyline[segment], m_polyline[segment + 1]); + const QLineF seg_line(m_polyline[segment], m_polyline[segment + 1]); - return intersectsSpan(normal, seg_line); + return intersectsSpan(normal, seg_line); } bool PolylineIntersector::intersectsSpan(const QLineF& normal, const QLineF& span) const { - const Vec2d v1(normal.p2() - normal.p1()); - const Vec2d v2(span.p1() - normal.p1()); - const Vec2d v3(span.p2() - normal.p1()); + const Vec2d v1(normal.p2() - normal.p1()); + const Vec2d v2(span.p1() - normal.p1()); + const Vec2d v3(span.p2() - normal.p1()); - return v1.dot(v2) * v1.dot(v3) <= 0; + return v1.dot(v2) * v1.dot(v3) <= 0; } QPointF PolylineIntersector::intersectWithSegment(const QLineF& line, int segment) const { - const QLineF seg_line(m_polyline[segment], m_polyline[segment + 1]); - QPointF intersection; - if (line.intersect(seg_line, &intersection) == QLineF::NoIntersection) { - // Considering we were called for a reason, the segment must - // be on the same line as our subject line. Just return segment - // midpoint in this case. - return seg_line.pointAt(0.5); - } - - return intersection; + const QLineF seg_line(m_polyline[segment], m_polyline[segment + 1]); + QPointF intersection; + if (line.intersect(seg_line, &intersection) == QLineF::NoIntersection) { + // Considering we were called for a reason, the segment must + // be on the same line as our subject line. Just return segment + // midpoint in this case. + return seg_line.pointAt(0.5); + } + + return intersection; } bool PolylineIntersector::tryIntersectingOutsideOfPolyline(const QLineF& line, QPointF& intersection, Hint& hint) const { - const QLineF normal(line.normalVector()); - const QPointF origin(normal.p1()); - const Vec2d nv(normal.p2() - normal.p1()); - - const Vec2d front_vec(m_polyline.front() - origin); - const Vec2d back_vec(m_polyline.back() - origin); - const double front_dot = nv.dot(front_vec); - const double back_dot = nv.dot(back_vec); - - if (front_dot * back_dot <= 0) { - return false; - } - - const ToLineProjector proj(line); - - if (std::fabs(front_dot) < std::fabs(back_dot)) { - hint.update(-1); - intersection = proj.projectionPoint(m_polyline.front()); - } else { - hint.update(m_numSegments); - intersection = proj.projectionPoint(m_polyline.back()); - } - - return true; + const QLineF normal(line.normalVector()); + const QPointF origin(normal.p1()); + const Vec2d nv(normal.p2() - normal.p1()); + + const Vec2d front_vec(m_polyline.front() - origin); + const Vec2d back_vec(m_polyline.back() - origin); + const double front_dot = nv.dot(front_vec); + const double back_dot = nv.dot(back_vec); + + if (front_dot * back_dot <= 0) { + return false; + } + + const ToLineProjector proj(line); + + if (std::fabs(front_dot) < std::fabs(back_dot)) { + hint.update(-1); + intersection = proj.projectionPoint(m_polyline.front()); + } else { + hint.update(m_numSegments); + intersection = proj.projectionPoint(m_polyline.back()); + } + + return true; } diff --git a/math/PolylineIntersector.h b/math/PolylineIntersector.h index 3af893c72..c977ceb73 100644 --- a/math/PolylineIntersector.h +++ b/math/PolylineIntersector.h @@ -19,42 +19,42 @@ #ifndef POLYLINE_INTERSECTOR_H_ #define POLYLINE_INTERSECTOR_H_ -#include "VecNT.h" -#include #include +#include #include +#include "VecNT.h" class PolylineIntersector { -public: - class Hint { - friend class PolylineIntersector; + public: + class Hint { + friend class PolylineIntersector; - public: - Hint(); + public: + Hint(); - private: - void update(int new_segment); + private: + void update(int new_segment); - int m_lastSegment; - int m_direction; - }; + int m_lastSegment; + int m_direction; + }; - explicit PolylineIntersector(const std::vector& polyline); + explicit PolylineIntersector(const std::vector& polyline); - QPointF intersect(const QLineF& line, Hint& hint) const; + QPointF intersect(const QLineF& line, Hint& hint) const; -private: - bool intersectsSegment(const QLineF& normal, int segment) const; + private: + bool intersectsSegment(const QLineF& normal, int segment) const; - bool intersectsSpan(const QLineF& normal, const QLineF& span) const; + bool intersectsSpan(const QLineF& normal, const QLineF& span) const; - QPointF intersectWithSegment(const QLineF& line, int segment) const; + QPointF intersectWithSegment(const QLineF& line, int segment) const; - bool tryIntersectingOutsideOfPolyline(const QLineF& line, QPointF& intersection, Hint& hint) const; + bool tryIntersectingOutsideOfPolyline(const QLineF& line, QPointF& intersection, Hint& hint) const; - std::vector m_polyline; - int m_numSegments; + std::vector m_polyline; + int m_numSegments; }; diff --git a/math/QuadraticFunction.cpp b/math/QuadraticFunction.cpp index b7e4e1ddb..c9b97629e 100644 --- a/math/QuadraticFunction.cpp +++ b/math/QuadraticFunction.cpp @@ -19,82 +19,81 @@ #include "QuadraticFunction.h" #include -QuadraticFunction::QuadraticFunction(size_t num_vars) : A(num_vars, num_vars), b(num_vars), c(0) { -} +QuadraticFunction::QuadraticFunction(size_t num_vars) : A(num_vars, num_vars), b(num_vars), c(0) {} void QuadraticFunction::reset() { - A.fill(0); - b.fill(0); - c = 0; + A.fill(0); + b.fill(0); + c = 0; } double QuadraticFunction::evaluate(const double* x) const { - const size_t num_vars = numVars(); - - double sum = c; - for (size_t i = 0; i < num_vars; ++i) { - sum += b[i] * x[i]; - for (size_t j = 0; j < num_vars; ++j) { - sum += x[i] * x[j] * A(i, j); - } + const size_t num_vars = numVars(); + + double sum = c; + for (size_t i = 0; i < num_vars; ++i) { + sum += b[i] * x[i]; + for (size_t j = 0; j < num_vars; ++j) { + sum += x[i] * x[j] * A(i, j); } + } - return sum; + return sum; } QuadraticFunction::Gradient QuadraticFunction::gradient() const { - const size_t num_vars = numVars(); - Gradient grad; - - MatT(num_vars, num_vars).swap(grad.A); - for (size_t i = 0; i < num_vars; ++i) { - for (size_t j = 0; j < num_vars; ++j) { - grad.A(i, j) = A(i, j) + A(j, i); - } + const size_t num_vars = numVars(); + Gradient grad; + + MatT(num_vars, num_vars).swap(grad.A); + for (size_t i = 0; i < num_vars; ++i) { + for (size_t j = 0; j < num_vars; ++j) { + grad.A(i, j) = A(i, j) + A(j, i); } + } - grad.b = b; + grad.b = b; - return grad; + return grad; } void QuadraticFunction::recalcForTranslatedArguments(const double* translation) { - const size_t num_vars = numVars(); - - for (size_t i = 0; i < num_vars; ++i) { - // Bi * (Xi + Ti) = Bi * Xi + Bi * Ti - c += b[i] * translation[i]; - } - - for (size_t i = 0; i < num_vars; ++i) { - for (size_t j = 0; j < num_vars; ++j) { - // (Xi + Ti)*Aij*(Xj + Tj) = Xi*Aij*Xj + Aij*Tj*Xi + Aij*Ti*Xj + Aij*Ti*Tj - const double a = A(i, j); - b[i] += a * translation[j]; - b[j] += a * translation[i]; - c += a * translation[i] * translation[j]; - } + const size_t num_vars = numVars(); + + for (size_t i = 0; i < num_vars; ++i) { + // Bi * (Xi + Ti) = Bi * Xi + Bi * Ti + c += b[i] * translation[i]; + } + + for (size_t i = 0; i < num_vars; ++i) { + for (size_t j = 0; j < num_vars; ++j) { + // (Xi + Ti)*Aij*(Xj + Tj) = Xi*Aij*Xj + Aij*Tj*Xi + Aij*Ti*Xj + Aij*Ti*Tj + const double a = A(i, j); + b[i] += a * translation[j]; + b[j] += a * translation[i]; + c += a * translation[i] * translation[j]; } + } } void QuadraticFunction::swap(QuadraticFunction& other) { - A.swap(other.A); - b.swap(other.b); - std::swap(c, other.c); + A.swap(other.A); + b.swap(other.b); + std::swap(c, other.c); } QuadraticFunction& QuadraticFunction::operator+=(const QuadraticFunction& other) { - A += other.A; - b += other.b; - c += other.c; + A += other.A; + b += other.b; + c += other.c; - return *this; + return *this; } QuadraticFunction& QuadraticFunction::operator*=(double scalar) { - A *= scalar; - b *= scalar; - c *= scalar; + A *= scalar; + b *= scalar; + c *= scalar; - return *this; + return *this; } diff --git a/math/QuadraticFunction.h b/math/QuadraticFunction.h index e36f6e136..fb42aab71 100644 --- a/math/QuadraticFunction.h +++ b/math/QuadraticFunction.h @@ -19,9 +19,9 @@ #ifndef QUADRATIC_FUNCTION_H_ #define QUADRATIC_FUNCTION_H_ +#include #include "MatT.h" #include "VecT.h" -#include /** * A quadratic function from arbitrary number of variables @@ -36,66 +36,64 @@ * c: constant component.\n */ class QuadraticFunction { - // Member-wise copying is OK. -public: - /** - * Quadratic function's gradient can be written in matrix form as: - * \code - * nabla F(x) = A * x + b - * \endcode - */ - class Gradient { - public: - MatT A; - VecT b; - }; - - - /** - * Matrix A has column-major data storage, so that it can be used with MatrixCalc. - */ + // Member-wise copying is OK. + public: + /** + * Quadratic function's gradient can be written in matrix form as: + * \code + * nabla F(x) = A * x + b + * \endcode + */ + class Gradient { + public: MatT A; VecT b; - double c; + }; + + + /** + * Matrix A has column-major data storage, so that it can be used with MatrixCalc. + */ + MatT A; + VecT b; + double c; - /** - * Constructs a quadratic functiono of the given number of variables, - * initializing everything to zero. - */ - explicit QuadraticFunction(size_t num_vars = 0); + /** + * Constructs a quadratic functiono of the given number of variables, + * initializing everything to zero. + */ + explicit QuadraticFunction(size_t num_vars = 0); - /** - * Resets everything to zero, so that F(x) = 0 - */ - void reset(); + /** + * Resets everything to zero, so that F(x) = 0 + */ + void reset(); - size_t numVars() const { - return b.size(); - } + size_t numVars() const { return b.size(); } - /** - * Evaluates x^T * A * x + b^T * x + c - */ - double evaluate(const double* x) const; + /** + * Evaluates x^T * A * x + b^T * x + c + */ + double evaluate(const double* x) const; - Gradient gradient() const; + Gradient gradient() const; - /** - * f(x) is our function. This method will replace f(x) with g(x) so that - * g(x) = f(x + translation) - */ - void recalcForTranslatedArguments(const double* translation); + /** + * f(x) is our function. This method will replace f(x) with g(x) so that + * g(x) = f(x + translation) + */ + void recalcForTranslatedArguments(const double* translation); - void swap(QuadraticFunction& other); + void swap(QuadraticFunction& other); - QuadraticFunction& operator+=(const QuadraticFunction& other); + QuadraticFunction& operator+=(const QuadraticFunction& other); - QuadraticFunction& operator*=(double scalar); + QuadraticFunction& operator*=(double scalar); }; inline void swap(QuadraticFunction& f1, QuadraticFunction& f2) { - f1.swap(f2); + f1.swap(f2); } #endif // ifndef QUADRATIC_FUNCTION_H_ diff --git a/math/SidesOfLine.cpp b/math/SidesOfLine.cpp index 15393b9e3..68ed62a62 100644 --- a/math/SidesOfLine.cpp +++ b/math/SidesOfLine.cpp @@ -19,11 +19,11 @@ #include "SidesOfLine.h" qreal sidesOfLine(const QLineF& line, const QPointF& p1, const QPointF& p2) { - const QPointF normal(line.normalVector().p2() - line.p1()); - const QPointF vec1(p1 - line.p1()); - const QPointF vec2(p2 - line.p1()); - const qreal dot1 = normal.x() * vec1.x() + normal.y() * vec1.y(); - const qreal dot2 = normal.x() * vec2.x() + normal.y() * vec2.y(); + const QPointF normal(line.normalVector().p2() - line.p1()); + const QPointF vec1(p1 - line.p1()); + const QPointF vec2(p2 - line.p1()); + const qreal dot1 = normal.x() * vec1.x() + normal.y() * vec1.y(); + const qreal dot2 = normal.x() * vec2.x() + normal.y() * vec2.y(); - return dot1 * dot2; + return dot1 * dot2; } diff --git a/math/SidesOfLine.h b/math/SidesOfLine.h index e6bab00ea..4c172a5f9 100644 --- a/math/SidesOfLine.h +++ b/math/SidesOfLine.h @@ -19,8 +19,8 @@ #ifndef SIDES_OF_LINE_H_ #define SIDES_OF_LINE_H_ -#include #include +#include /** * This function allows you to check if a pair of points is on the same diff --git a/math/ToLineProjector.cpp b/math/ToLineProjector.cpp index 70625d758..005e52cbe 100644 --- a/math/ToLineProjector.cpp +++ b/math/ToLineProjector.cpp @@ -16,41 +16,41 @@ along with this program. If not, see . */ -#include #include "ToLineProjector.h" +#include ToLineProjector::ToLineProjector(const QLineF& line) : m_origin(line.p1()), m_vec(line.p2() - line.p1()), m_mat(m_vec) { - using namespace std; - // At*A*x = At*b - const double AtA = m_mat.dot(m_mat); - - if (std::abs(AtA) > numeric_limits::epsilon()) { - // x = (At*A)-1 * At - m_mat /= AtA; - } else { - m_mat[0] = 0; - m_mat[1] = 0; - } + using namespace std; + // At*A*x = At*b + const double AtA = m_mat.dot(m_mat); + + if (std::abs(AtA) > numeric_limits::epsilon()) { + // x = (At*A)-1 * At + m_mat /= AtA; + } else { + m_mat[0] = 0; + m_mat[1] = 0; + } } double ToLineProjector::projectionScalar(const QPointF& pt) const { - const Vec2d b(pt - m_origin); + const Vec2d b(pt - m_origin); - return m_mat.dot(b); + return m_mat.dot(b); } QPointF ToLineProjector::projectionPoint(const QPointF& pt) const { - return m_origin + m_vec * projectionScalar(pt); + return m_origin + m_vec * projectionScalar(pt); } QPointF ToLineProjector::projectionVector(const QPointF& pt) const { - return projectionPoint(pt) - pt; + return projectionPoint(pt) - pt; } double ToLineProjector::projectionDist(const QPointF& pt) const { - return std::sqrt(projectionSqDist(pt)); + return std::sqrt(projectionSqDist(pt)); } double ToLineProjector::projectionSqDist(const QPointF& pt) const { - return Vec2d(projectionPoint(pt) - pt).squaredNorm(); + return Vec2d(projectionPoint(pt) - pt).squaredNorm(); } diff --git a/math/ToLineProjector.h b/math/ToLineProjector.h index 7f75fb2ef..af83b365b 100644 --- a/math/ToLineProjector.h +++ b/math/ToLineProjector.h @@ -19,9 +19,9 @@ #ifndef TO_LINE_PROJECTOR_H_ #define TO_LINE_PROJECTOR_H_ -#include "VecNT.h" -#include #include +#include +#include "VecNT.h" /** * \brief Projects points onto a line (not a line segment). @@ -30,48 +30,48 @@ * to the given point. */ class ToLineProjector { -public: - /** - * \brief Initializes line projector. - * - * If line endpoints match, all points will - * be projected to that point. - */ - explicit ToLineProjector(const QLineF& line); + public: + /** + * \brief Initializes line projector. + * + * If line endpoints match, all points will + * be projected to that point. + */ + explicit ToLineProjector(const QLineF& line); - /** - * \brief Finds the projection point. - */ - QPointF projectionPoint(const QPointF& pt) const; + /** + * \brief Finds the projection point. + */ + QPointF projectionPoint(const QPointF& pt) const; - /** - * \brief Equivalent to projectionPoint(pt) - pt. - */ - QPointF projectionVector(const QPointF& pt) const; + /** + * \brief Equivalent to projectionPoint(pt) - pt. + */ + QPointF projectionVector(const QPointF& pt) const; - /** - * Solves the equation of:\n - * line.p1() + x * (line.p2() - line.p1()) = p\n - * for x, where p would be the projection point. - * This function is faster than projectionPoint(). - */ - double projectionScalar(const QPointF& pt) const; + /** + * Solves the equation of:\n + * line.p1() + x * (line.p2() - line.p1()) = p\n + * for x, where p would be the projection point. + * This function is faster than projectionPoint(). + */ + double projectionScalar(const QPointF& pt) const; - /** - * Returns the distance from \p pt to the projection point. - */ - double projectionDist(const QPointF& pt) const; + /** + * Returns the distance from \p pt to the projection point. + */ + double projectionDist(const QPointF& pt) const; - /** - * Returns the squared distance from \p pt to the projection point. - * This function is faster than projectionDist(). - */ - double projectionSqDist(const QPointF& pt) const; + /** + * Returns the squared distance from \p pt to the projection point. + * This function is faster than projectionDist(). + */ + double projectionSqDist(const QPointF& pt) const; -private: - QPointF m_origin; - QPointF m_vec; - Vec2d m_mat; + private: + QPointF m_origin; + QPointF m_vec; + Vec2d m_mat; }; diff --git a/math/XSpline.cpp b/math/XSpline.cpp index 25a0f3f46..9441047eb 100644 --- a/math/XSpline.cpp +++ b/math/XSpline.cpp @@ -17,198 +17,197 @@ */ #include "XSpline.h" -#include "VecNT.h" -#include "ToLineProjector.h" -#include "adiff/SparseMap.h" -#include "adiff/Function.h" #include #include #include +#include "ToLineProjector.h" +#include "VecNT.h" +#include "adiff/Function.h" +#include "adiff/SparseMap.h" struct XSpline::TensionDerivedParams { - static const double t0; - static const double t1; - static const double t2; - static const double t3; - - // These correspond to Tk- and Tk+ in [1]. - double T0p; - double T1p; - double T2m; - double T3m; - - // q parameters for GBlendFunc and HBlendFunc. - double q[4]{}; - // p parameters for GBlendFunc. - double p[4]{}; - - TensionDerivedParams(double tension1, double tension2); + static const double t0; + static const double t1; + static const double t2; + static const double t3; + + // These correspond to Tk- and Tk+ in [1]. + double T0p; + double T1p; + double T2m; + double T3m; + + // q parameters for GBlendFunc and HBlendFunc. + double q[4]{}; + // p parameters for GBlendFunc. + double p[4]{}; + + TensionDerivedParams(double tension1, double tension2); }; class XSpline::GBlendFunc { -public: - GBlendFunc(double q, double p); + public: + GBlendFunc(double q, double p); - double value(double u) const; + double value(double u) const; - double firstDerivative(double u) const; + double firstDerivative(double u) const; - double secondDerivative(double u) const; + double secondDerivative(double u) const; -private: - double m_c1; - double m_c2; - double m_c3; - double m_c4; - double m_c5; + private: + double m_c1; + double m_c2; + double m_c3; + double m_c4; + double m_c5; }; class XSpline::HBlendFunc { -public: - explicit HBlendFunc(double q); + public: + explicit HBlendFunc(double q); - double value(double u) const; + double value(double u) const; - double firstDerivative(double u) const; + double firstDerivative(double u) const; - double secondDerivative(double u) const; + double secondDerivative(double u) const; -private: - double m_c1; - double m_c2; - double m_c4; - double m_c5; + private: + double m_c1; + double m_c2; + double m_c4; + double m_c5; }; struct XSpline::DecomposedDerivs { - double zeroDerivCoeffs[4]; - double firstDerivCoeffs[4]; - double secondDerivCoeffs[4]; - int controlPoints[4]; - int numControlPoints; + double zeroDerivCoeffs[4]; + double firstDerivCoeffs[4]; + double secondDerivCoeffs[4]; + int controlPoints[4]; + int numControlPoints; - bool hasNonZeroCoeffs(int idx) const { - double sum = std::fabs(zeroDerivCoeffs[idx]) + std::fabs(firstDerivCoeffs[idx]) - + std::fabs(secondDerivCoeffs[idx]); + bool hasNonZeroCoeffs(int idx) const { + double sum = std::fabs(zeroDerivCoeffs[idx]) + std::fabs(firstDerivCoeffs[idx]) + std::fabs(secondDerivCoeffs[idx]); - return sum > std::numeric_limits::epsilon(); - } + return sum > std::numeric_limits::epsilon(); + } }; int XSpline::numControlPoints() const { - return static_cast(m_controlPoints.size()); + return static_cast(m_controlPoints.size()); } int XSpline::numSegments() const { - return std::max(static_cast(m_controlPoints.size() - 1), 0); + return std::max(static_cast(m_controlPoints.size() - 1), 0); } double XSpline::controlPointIndexToT(int idx) const { - assert(idx >= 0 || idx <= (int) m_controlPoints.size()); + assert(idx >= 0 || idx <= (int) m_controlPoints.size()); - return double(idx) / (m_controlPoints.size() - 1); + return double(idx) / (m_controlPoints.size() - 1); } void XSpline::appendControlPoint(const QPointF& pos, double tension) { - m_controlPoints.emplace_back(pos, tension); + m_controlPoints.emplace_back(pos, tension); } void XSpline::insertControlPoint(int idx, const QPointF& pos, double tension) { - assert(idx >= 0 || idx <= (int) m_controlPoints.size()); - m_controlPoints.insert(m_controlPoints.begin() + idx, ControlPoint(pos, tension)); + assert(idx >= 0 || idx <= (int) m_controlPoints.size()); + m_controlPoints.insert(m_controlPoints.begin() + idx, ControlPoint(pos, tension)); } void XSpline::eraseControlPoint(int idx) { - assert(idx >= 0 || idx < (int) m_controlPoints.size()); - m_controlPoints.erase(m_controlPoints.begin() + idx); + assert(idx >= 0 || idx < (int) m_controlPoints.size()); + m_controlPoints.erase(m_controlPoints.begin() + idx); } QPointF XSpline::controlPointPosition(int idx) const { - return m_controlPoints[idx].pos; + return m_controlPoints[idx].pos; } void XSpline::moveControlPoint(int idx, const QPointF& pos) { - assert(idx >= 0 || idx < (int) m_controlPoints.size()); - m_controlPoints[idx].pos = pos; + assert(idx >= 0 || idx < (int) m_controlPoints.size()); + m_controlPoints[idx].pos = pos; } double XSpline::controlPointTension(int idx) const { - assert(idx >= 0 && idx < (int) m_controlPoints.size()); + assert(idx >= 0 && idx < (int) m_controlPoints.size()); - return m_controlPoints[idx].tension; + return m_controlPoints[idx].tension; } void XSpline::setControlPointTension(int idx, double tension) { - assert(idx >= 0 && idx < (int) m_controlPoints.size()); - m_controlPoints[idx].tension = tension; + assert(idx >= 0 && idx < (int) m_controlPoints.size()); + m_controlPoints[idx].tension = tension; } QPointF XSpline::pointAt(double t) const { - const int num_segments = numSegments(); - assert(num_segments > 0); - assert(t >= 0 && t <= 1); - - if (t == 1.0) { - // If we went with the branch below, we would end up with - // segment == num_segments, which is an error. - return pointAtImpl(num_segments - 1, 1.0); - } else { - const double t2 = t * num_segments; - const double segment = std::floor(t2); - - return pointAtImpl((int) segment, t2 - segment); - } + const int num_segments = numSegments(); + assert(num_segments > 0); + assert(t >= 0 && t <= 1); + + if (t == 1.0) { + // If we went with the branch below, we would end up with + // segment == num_segments, which is an error. + return pointAtImpl(num_segments - 1, 1.0); + } else { + const double t2 = t * num_segments; + const double segment = std::floor(t2); + + return pointAtImpl((int) segment, t2 - segment); + } } QPointF XSpline::pointAtImpl(int segment, double t) const { - LinearCoefficient coeffs[4]; - const int num_coeffs = linearCombinationFor(coeffs, segment, t); + LinearCoefficient coeffs[4]; + const int num_coeffs = linearCombinationFor(coeffs, segment, t); - QPointF pt(0, 0); - for (int i = 0; i < num_coeffs; ++i) { - const LinearCoefficient& c = coeffs[i]; - pt += m_controlPoints[c.controlPointIdx].pos * c.coeff; - } + QPointF pt(0, 0); + for (int i = 0; i < num_coeffs; ++i) { + const LinearCoefficient& c = coeffs[i]; + pt += m_controlPoints[c.controlPointIdx].pos * c.coeff; + } - return pt; + return pt; } void XSpline::sample(const VirtualFunction& sink, const SamplingParams& params, double from_t, double to_t) const { - if (m_controlPoints.empty()) { - return; - } - - double max_sqdist_to_spline = params.maxDistFromSpline; - if (max_sqdist_to_spline != NumericTraits::max()) { - max_sqdist_to_spline *= params.maxDistFromSpline; - } - - double max_sqdist_between_samples = params.maxDistBetweenSamples; - if (max_sqdist_between_samples != NumericTraits::max()) { - max_sqdist_between_samples *= params.maxDistBetweenSamples; - } - - const QPointF from_pt(pointAt(from_t)); - const QPointF to_pt(pointAt(to_t)); - sink(from_pt, from_t, HEAD_SAMPLE); - - const int num_segments = numSegments(); - if (num_segments == 0) { - return; - } - const double r_num_segments = 1.0 / num_segments; - - maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, from_t, - from_pt, to_t, to_pt); - - sink(to_pt, to_t, TAIL_SAMPLE); + if (m_controlPoints.empty()) { + return; + } + + double max_sqdist_to_spline = params.maxDistFromSpline; + if (max_sqdist_to_spline != NumericTraits::max()) { + max_sqdist_to_spline *= params.maxDistFromSpline; + } + + double max_sqdist_between_samples = params.maxDistBetweenSamples; + if (max_sqdist_between_samples != NumericTraits::max()) { + max_sqdist_between_samples *= params.maxDistBetweenSamples; + } + + const QPointF from_pt(pointAt(from_t)); + const QPointF to_pt(pointAt(to_t)); + sink(from_pt, from_t, HEAD_SAMPLE); + + const int num_segments = numSegments(); + if (num_segments == 0) { + return; + } + const double r_num_segments = 1.0 / num_segments; + + maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, from_t, + from_pt, to_t, to_pt); + + sink(to_pt, to_t, TAIL_SAMPLE); } // XSpline::sample void XSpline::maybeAddMoreSamples(const VirtualFunction& sink, @@ -220,509 +219,509 @@ void XSpline::maybeAddMoreSamples(const VirtualFunction 0) + && ((nearby_junction_t - next_t) * (prev_t - next_t) > 0)) { + mid_t = nearby_junction_t; + flags = JUNCTION_SAMPLE; + } + + const QPointF mid_pt(pointAt(mid_t)); + + if (flags != JUNCTION_SAMPLE) { + const QPointF projection(ToLineProjector(QLineF(prev_pt, next_pt)).projectionPoint(mid_pt)); + + if (prev_next_sqdist <= max_sqdist_between_samples) { + if (Vec2d(mid_pt - projection).squaredNorm() <= max_sqdist_to_spline) { return; + } } + } - SampleFlags flags = DEFAULT_SAMPLE; - double mid_t = 0.5 * (prev_t + next_t); - const double nearby_junction_t = std::floor(mid_t * num_segments + 0.5) * r_num_segments; - - // If nearby_junction_t is between prev_t and next_t, make it our mid_t. - if (((nearby_junction_t - prev_t) * (next_t - prev_t) > 0) - && ((nearby_junction_t - next_t) * (prev_t - next_t) > 0)) { - mid_t = nearby_junction_t; - flags = JUNCTION_SAMPLE; - } + maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, prev_t, + prev_pt, mid_t, mid_pt); - const QPointF mid_pt(pointAt(mid_t)); + sink(mid_pt, mid_t, flags); - if (flags != JUNCTION_SAMPLE) { - const QPointF projection(ToLineProjector(QLineF(prev_pt, next_pt)).projectionPoint(mid_pt)); - - if (prev_next_sqdist <= max_sqdist_between_samples) { - if (Vec2d(mid_pt - projection).squaredNorm() <= max_sqdist_to_spline) { - return; - } - } - } - - maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, prev_t, - prev_pt, mid_t, mid_pt); - - sink(mid_pt, mid_t, flags); - - maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, mid_t, - mid_pt, next_t, next_pt); + maybeAddMoreSamples(sink, max_sqdist_to_spline, max_sqdist_between_samples, num_segments, r_num_segments, mid_t, + mid_pt, next_t, next_pt); } // XSpline::maybeAddMoreSamples void XSpline::linearCombinationAt(double t, std::vector& coeffs) const { - assert(t >= 0 && t <= 1); - - const int num_segments = numSegments(); - assert(num_segments > 0); - - int num_coeffs = 4; - coeffs.resize(num_coeffs); - LinearCoefficient static_coeffs[4]; - - if (t == 1.0) { - // If we went with the branch below, we would end up with - // segment == num_segments, which is an error. - num_coeffs = linearCombinationFor(static_coeffs, num_segments - 1, 1.0); - } else { - const double t2 = t * num_segments; - const double segment = std::floor(t2); - num_coeffs = linearCombinationFor(static_coeffs, (int) segment, t2 - segment); - } - - for (int i = 0; i < num_coeffs; ++i) { - coeffs[i] = static_coeffs[i]; - } - coeffs.resize(num_coeffs); + assert(t >= 0 && t <= 1); + + const int num_segments = numSegments(); + assert(num_segments > 0); + + int num_coeffs = 4; + coeffs.resize(num_coeffs); + LinearCoefficient static_coeffs[4]; + + if (t == 1.0) { + // If we went with the branch below, we would end up with + // segment == num_segments, which is an error. + num_coeffs = linearCombinationFor(static_coeffs, num_segments - 1, 1.0); + } else { + const double t2 = t * num_segments; + const double segment = std::floor(t2); + num_coeffs = linearCombinationFor(static_coeffs, (int) segment, t2 - segment); + } + + for (int i = 0; i < num_coeffs; ++i) { + coeffs[i] = static_coeffs[i]; + } + coeffs.resize(num_coeffs); } int XSpline::linearCombinationFor(LinearCoefficient* coeffs, int segment, double t) const { - assert(segment >= 0 && segment < (int) m_controlPoints.size() - 1); - assert(t >= 0 && t <= 1); - - int idxs[4]; - idxs[0] = std::max(0, segment - 1); - idxs[1] = segment; - idxs[2] = segment + 1; - idxs[3] = std::min(segment + 2, static_cast(m_controlPoints.size() - 1)); - - ControlPoint pts[4]; - for (int i = 0; i < 4; ++i) { - pts[i] = m_controlPoints[idxs[i]]; - } - - const TensionDerivedParams tdp(pts[1].tension, pts[2].tension); - - Vec4d A; - - if (t <= tdp.T0p) { - A[0] = GBlendFunc(tdp.q[0], tdp.p[0]).value((t - tdp.T0p) / (tdp.t0 - tdp.T0p)); - } else { - A[0] = HBlendFunc(tdp.q[0]).value((t - tdp.T0p) / (tdp.t0 - tdp.T0p)); - } - - A[1] = GBlendFunc(tdp.q[1], tdp.p[1]).value((t - tdp.T1p) / (tdp.t1 - tdp.T1p)); - A[2] = GBlendFunc(tdp.q[2], tdp.p[2]).value((t - tdp.T2m) / (tdp.t2 - tdp.T2m)); - - if (t >= tdp.T3m) { - A[3] = GBlendFunc(tdp.q[3], tdp.p[3]).value((t - tdp.T3m) / (tdp.t3 - tdp.T3m)); - } else { - A[3] = HBlendFunc(tdp.q[3]).value((t - tdp.T3m) / (tdp.t3 - tdp.T3m)); - } - - A /= A.sum(); - - int out_idx = 0; - - if (idxs[0] == idxs[1]) { - coeffs[out_idx++] = LinearCoefficient(idxs[0], A[0] + A[1]); - } else { - coeffs[out_idx++] = LinearCoefficient(idxs[0], A[0]); - coeffs[out_idx++] = LinearCoefficient(idxs[1], A[1]); - } - - if (idxs[2] == idxs[3]) { - coeffs[out_idx++] = LinearCoefficient(idxs[2], A[2] + A[3]); - } else { - coeffs[out_idx++] = LinearCoefficient(idxs[2], A[2]); - coeffs[out_idx++] = LinearCoefficient(idxs[3], A[3]); - } - - return out_idx; + assert(segment >= 0 && segment < (int) m_controlPoints.size() - 1); + assert(t >= 0 && t <= 1); + + int idxs[4]; + idxs[0] = std::max(0, segment - 1); + idxs[1] = segment; + idxs[2] = segment + 1; + idxs[3] = std::min(segment + 2, static_cast(m_controlPoints.size() - 1)); + + ControlPoint pts[4]; + for (int i = 0; i < 4; ++i) { + pts[i] = m_controlPoints[idxs[i]]; + } + + const TensionDerivedParams tdp(pts[1].tension, pts[2].tension); + + Vec4d A; + + if (t <= tdp.T0p) { + A[0] = GBlendFunc(tdp.q[0], tdp.p[0]).value((t - tdp.T0p) / (tdp.t0 - tdp.T0p)); + } else { + A[0] = HBlendFunc(tdp.q[0]).value((t - tdp.T0p) / (tdp.t0 - tdp.T0p)); + } + + A[1] = GBlendFunc(tdp.q[1], tdp.p[1]).value((t - tdp.T1p) / (tdp.t1 - tdp.T1p)); + A[2] = GBlendFunc(tdp.q[2], tdp.p[2]).value((t - tdp.T2m) / (tdp.t2 - tdp.T2m)); + + if (t >= tdp.T3m) { + A[3] = GBlendFunc(tdp.q[3], tdp.p[3]).value((t - tdp.T3m) / (tdp.t3 - tdp.T3m)); + } else { + A[3] = HBlendFunc(tdp.q[3]).value((t - tdp.T3m) / (tdp.t3 - tdp.T3m)); + } + + A /= A.sum(); + + int out_idx = 0; + + if (idxs[0] == idxs[1]) { + coeffs[out_idx++] = LinearCoefficient(idxs[0], A[0] + A[1]); + } else { + coeffs[out_idx++] = LinearCoefficient(idxs[0], A[0]); + coeffs[out_idx++] = LinearCoefficient(idxs[1], A[1]); + } + + if (idxs[2] == idxs[3]) { + coeffs[out_idx++] = LinearCoefficient(idxs[2], A[2] + A[3]); + } else { + coeffs[out_idx++] = LinearCoefficient(idxs[2], A[2]); + coeffs[out_idx++] = LinearCoefficient(idxs[3], A[3]); + } + + return out_idx; } // XSpline::linearCombinationFor XSpline::PointAndDerivs XSpline::pointAndDtsAt(double t) const { - assert(t >= 0 && t <= 1); - PointAndDerivs pd; - - const DecomposedDerivs derivs(decomposedDerivs(t)); - for (int i = 0; i < derivs.numControlPoints; ++i) { - const QPointF& cp = m_controlPoints[derivs.controlPoints[i]].pos; - pd.point += cp * derivs.zeroDerivCoeffs[i]; - pd.firstDeriv += cp * derivs.firstDerivCoeffs[i]; - pd.secondDeriv += cp * derivs.secondDerivCoeffs[i]; - } - - return pd; + assert(t >= 0 && t <= 1); + PointAndDerivs pd; + + const DecomposedDerivs derivs(decomposedDerivs(t)); + for (int i = 0; i < derivs.numControlPoints; ++i) { + const QPointF& cp = m_controlPoints[derivs.controlPoints[i]].pos; + pd.point += cp * derivs.zeroDerivCoeffs[i]; + pd.firstDeriv += cp * derivs.firstDerivCoeffs[i]; + pd.secondDeriv += cp * derivs.secondDerivCoeffs[i]; + } + + return pd; } XSpline::DecomposedDerivs XSpline::decomposedDerivs(const double t) const { - assert(t >= 0 && t <= 1); + assert(t >= 0 && t <= 1); - const int num_segments = numSegments(); - assert(num_segments > 0); + const int num_segments = numSegments(); + assert(num_segments > 0); - if (t == 1.0) { - // If we went with the branch below, we would end up with - // segment == num_segments, which is an error. - return decomposedDerivsImpl(num_segments - 1, 1.0); - } else { - const double t2 = t * num_segments; - const double segment = std::floor(t2); + if (t == 1.0) { + // If we went with the branch below, we would end up with + // segment == num_segments, which is an error. + return decomposedDerivsImpl(num_segments - 1, 1.0); + } else { + const double t2 = t * num_segments; + const double segment = std::floor(t2); - return decomposedDerivsImpl((int) segment, t2 - segment); - } + return decomposedDerivsImpl((int) segment, t2 - segment); + } } XSpline::DecomposedDerivs XSpline::decomposedDerivsImpl(const int segment, const double t) const { - assert(segment >= 0 && segment < (int) m_controlPoints.size() - 1); - assert(t >= 0 && t <= 1); - - DecomposedDerivs derivs{}; - - derivs.numControlPoints = 4; // May be modified later in this function. - derivs.controlPoints[0] = std::max(0, segment - 1); - derivs.controlPoints[1] = segment; - derivs.controlPoints[2] = segment + 1; - derivs.controlPoints[3] = std::min(segment + 2, static_cast(m_controlPoints.size() - 1)); - - ControlPoint pts[4]; - for (int i = 0; i < 4; ++i) { - pts[i] = m_controlPoints[derivs.controlPoints[i]]; - } - - const TensionDerivedParams tdp(pts[1].tension, pts[2].tension); - - // Note that we don't want the derivate with respect to t that's - // passed to us (ranging from 0 to 1 within a segment). - // Rather we want it with respect to the t that's passed to - // decomposedDerivs(), ranging from 0 to 1 across all segments. - // Let's call the latter capital T. Their relationship is: - // t = T*num_segments - C - // dt/dT = num_segments - const double dtdT = numSegments(); - - Vec4d A; - Vec4d dA; // First derivatives with respect to T. - Vec4d ddA; // Second derivatives with respect to T. - // Control point 0. - { - // u = (t - tdp.T0p) / (tdp.t0 - tdp.T0p) - const double ta = 1.0 / (tdp.t0 - tdp.T0p); - const double tb = -tdp.T0p * ta; - const double u = ta * t + tb; - if (t <= tdp.T0p) { - // u(t) = ta * tt + tb - // u'(t) = ta - // g(t) = g(u(t), ) - GBlendFunc g(tdp.q[0], tdp.p[0]); - A[0] = g.value(u); - - // g'(u(t(T))) = g'(u)*u'(t)*t'(T) - dA[0] = g.firstDerivative(u) * (ta * dtdT); - // Note that u'(t) and t'(T) are constant functions. - // g"(u(t(T))) = g"(u)*u'(t)*t'(T)*u'(t)*t'(T) - ddA[0] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } else { - HBlendFunc h(tdp.q[0]); - A[0] = h.value(u); - dA[0] = h.firstDerivative(u) * (ta * dtdT); - ddA[0] = h.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } - } - // Control point 1. - { - // u = (t - tdp.T1p) / (tdp.t1 - tdp.T1p) - const double ta = 1.0 / (tdp.t1 - tdp.T1p); - const double tb = -tdp.T1p * ta; - const double u = ta * t + tb; - GBlendFunc g(tdp.q[1], tdp.p[1]); - A[1] = g.value(u); - dA[1] = g.firstDerivative(u) * (ta * dtdT); - ddA[1] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } - - // Control point 2. - { - // u = (t - tdp.T2m) / (tdp.t2 - tdp.T2m) - const double ta = 1.0 / (tdp.t2 - tdp.T2m); - const double tb = -tdp.T2m * ta; - const double u = ta * t + tb; - GBlendFunc g(tdp.q[2], tdp.p[2]); - A[2] = g.value(u); - dA[2] = g.firstDerivative(u) * (ta * dtdT); - ddA[2] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } - // Control point 3. - { - // u = (t - tdp.T3m) / (tdp.t3 - tdp.T3m) - const double ta = 1.0 / (tdp.t3 - tdp.T3m); - const double tb = -tdp.T3m * ta; - const double u = ta * t + tb; - if (t >= tdp.T3m) { - GBlendFunc g(tdp.q[3], tdp.p[3]); - A[3] = g.value(u); - dA[3] = g.firstDerivative(u) * (ta * dtdT); - ddA[3] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } else { - HBlendFunc h(tdp.q[3]); - A[3] = h.value(u); - dA[3] = h.firstDerivative(u) * (ta * dtdT); - ddA[3] = h.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); - } - } - - const double sum = A.sum(); - const double sum2 = sum * sum; - const double sum4 = sum2 * sum2; - const double d_sum = dA.sum(); - const double dd_sum = ddA.sum(); - - for (int i = 0; i < 4; ++i) { - derivs.zeroDerivCoeffs[i] = A[i] / sum; - - const double d1 = dA[i] * sum - A[i] * d_sum; - derivs.firstDerivCoeffs[i] = d1 / sum2; // Derivative of: A[i] / sum - // Derivative of: dA[i] * sum - const double dd1 = ddA[i] * sum + dA[i] * d_sum; - - // Derivative of: A[i] * d_sum - const double dd2 = dA[i] * d_sum + A[i] * dd_sum; - - // Derivative of (dA[i] * sum - A[i] * d_sum) / sum2 - const double dd3 = ((dd1 - dd2) * sum2 - d1 * (2 * sum * d_sum)) / sum4; - derivs.secondDerivCoeffs[i] = dd3; - } - // Merge / throw away some control points. - int write_idx = 0; - int merge_idx = 0; - int read_idx = 1; - const int end = 4; - for (;;) { - assert(merge_idx != read_idx); - for (; read_idx != end && derivs.controlPoints[read_idx] == derivs.controlPoints[merge_idx]; ++read_idx) { - // Merge - derivs.zeroDerivCoeffs[merge_idx] += derivs.zeroDerivCoeffs[read_idx]; - derivs.firstDerivCoeffs[merge_idx] += derivs.firstDerivCoeffs[read_idx]; - derivs.secondDerivCoeffs[merge_idx] += derivs.secondDerivCoeffs[read_idx]; - } - - if (derivs.hasNonZeroCoeffs(merge_idx)) { - // Copy - derivs.zeroDerivCoeffs[write_idx] = derivs.zeroDerivCoeffs[merge_idx]; - derivs.firstDerivCoeffs[write_idx] = derivs.firstDerivCoeffs[merge_idx]; - derivs.secondDerivCoeffs[write_idx] = derivs.secondDerivCoeffs[merge_idx]; - derivs.controlPoints[write_idx] = derivs.controlPoints[merge_idx]; - ++write_idx; - } - - if (read_idx == end) { - break; - } - - merge_idx = read_idx; - ++read_idx; - } - derivs.numControlPoints = write_idx; - - return derivs; + assert(segment >= 0 && segment < (int) m_controlPoints.size() - 1); + assert(t >= 0 && t <= 1); + + DecomposedDerivs derivs{}; + + derivs.numControlPoints = 4; // May be modified later in this function. + derivs.controlPoints[0] = std::max(0, segment - 1); + derivs.controlPoints[1] = segment; + derivs.controlPoints[2] = segment + 1; + derivs.controlPoints[3] = std::min(segment + 2, static_cast(m_controlPoints.size() - 1)); + + ControlPoint pts[4]; + for (int i = 0; i < 4; ++i) { + pts[i] = m_controlPoints[derivs.controlPoints[i]]; + } + + const TensionDerivedParams tdp(pts[1].tension, pts[2].tension); + + // Note that we don't want the derivate with respect to t that's + // passed to us (ranging from 0 to 1 within a segment). + // Rather we want it with respect to the t that's passed to + // decomposedDerivs(), ranging from 0 to 1 across all segments. + // Let's call the latter capital T. Their relationship is: + // t = T*num_segments - C + // dt/dT = num_segments + const double dtdT = numSegments(); + + Vec4d A; + Vec4d dA; // First derivatives with respect to T. + Vec4d ddA; // Second derivatives with respect to T. + // Control point 0. + { + // u = (t - tdp.T0p) / (tdp.t0 - tdp.T0p) + const double ta = 1.0 / (tdp.t0 - tdp.T0p); + const double tb = -tdp.T0p * ta; + const double u = ta * t + tb; + if (t <= tdp.T0p) { + // u(t) = ta * tt + tb + // u'(t) = ta + // g(t) = g(u(t), ) + GBlendFunc g(tdp.q[0], tdp.p[0]); + A[0] = g.value(u); + + // g'(u(t(T))) = g'(u)*u'(t)*t'(T) + dA[0] = g.firstDerivative(u) * (ta * dtdT); + // Note that u'(t) and t'(T) are constant functions. + // g"(u(t(T))) = g"(u)*u'(t)*t'(T)*u'(t)*t'(T) + ddA[0] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } else { + HBlendFunc h(tdp.q[0]); + A[0] = h.value(u); + dA[0] = h.firstDerivative(u) * (ta * dtdT); + ddA[0] = h.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } + } + // Control point 1. + { + // u = (t - tdp.T1p) / (tdp.t1 - tdp.T1p) + const double ta = 1.0 / (tdp.t1 - tdp.T1p); + const double tb = -tdp.T1p * ta; + const double u = ta * t + tb; + GBlendFunc g(tdp.q[1], tdp.p[1]); + A[1] = g.value(u); + dA[1] = g.firstDerivative(u) * (ta * dtdT); + ddA[1] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } + + // Control point 2. + { + // u = (t - tdp.T2m) / (tdp.t2 - tdp.T2m) + const double ta = 1.0 / (tdp.t2 - tdp.T2m); + const double tb = -tdp.T2m * ta; + const double u = ta * t + tb; + GBlendFunc g(tdp.q[2], tdp.p[2]); + A[2] = g.value(u); + dA[2] = g.firstDerivative(u) * (ta * dtdT); + ddA[2] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } + // Control point 3. + { + // u = (t - tdp.T3m) / (tdp.t3 - tdp.T3m) + const double ta = 1.0 / (tdp.t3 - tdp.T3m); + const double tb = -tdp.T3m * ta; + const double u = ta * t + tb; + if (t >= tdp.T3m) { + GBlendFunc g(tdp.q[3], tdp.p[3]); + A[3] = g.value(u); + dA[3] = g.firstDerivative(u) * (ta * dtdT); + ddA[3] = g.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } else { + HBlendFunc h(tdp.q[3]); + A[3] = h.value(u); + dA[3] = h.firstDerivative(u) * (ta * dtdT); + ddA[3] = h.secondDerivative(u) * (ta * dtdT) * (ta * dtdT); + } + } + + const double sum = A.sum(); + const double sum2 = sum * sum; + const double sum4 = sum2 * sum2; + const double d_sum = dA.sum(); + const double dd_sum = ddA.sum(); + + for (int i = 0; i < 4; ++i) { + derivs.zeroDerivCoeffs[i] = A[i] / sum; + + const double d1 = dA[i] * sum - A[i] * d_sum; + derivs.firstDerivCoeffs[i] = d1 / sum2; // Derivative of: A[i] / sum + // Derivative of: dA[i] * sum + const double dd1 = ddA[i] * sum + dA[i] * d_sum; + + // Derivative of: A[i] * d_sum + const double dd2 = dA[i] * d_sum + A[i] * dd_sum; + + // Derivative of (dA[i] * sum - A[i] * d_sum) / sum2 + const double dd3 = ((dd1 - dd2) * sum2 - d1 * (2 * sum * d_sum)) / sum4; + derivs.secondDerivCoeffs[i] = dd3; + } + // Merge / throw away some control points. + int write_idx = 0; + int merge_idx = 0; + int read_idx = 1; + const int end = 4; + while (true) { + assert(merge_idx != read_idx); + for (; read_idx != end && derivs.controlPoints[read_idx] == derivs.controlPoints[merge_idx]; ++read_idx) { + // Merge + derivs.zeroDerivCoeffs[merge_idx] += derivs.zeroDerivCoeffs[read_idx]; + derivs.firstDerivCoeffs[merge_idx] += derivs.firstDerivCoeffs[read_idx]; + derivs.secondDerivCoeffs[merge_idx] += derivs.secondDerivCoeffs[read_idx]; + } + + if (derivs.hasNonZeroCoeffs(merge_idx)) { + // Copy + derivs.zeroDerivCoeffs[write_idx] = derivs.zeroDerivCoeffs[merge_idx]; + derivs.firstDerivCoeffs[write_idx] = derivs.firstDerivCoeffs[merge_idx]; + derivs.secondDerivCoeffs[write_idx] = derivs.secondDerivCoeffs[merge_idx]; + derivs.controlPoints[write_idx] = derivs.controlPoints[merge_idx]; + ++write_idx; + } + + if (read_idx == end) { + break; + } + + merge_idx = read_idx; + ++read_idx; + } + derivs.numControlPoints = write_idx; + + return derivs; } // XSpline::decomposedDerivsImpl QuadraticFunction XSpline::controlPointsAttractionForce() const { - return controlPointsAttractionForce(0, numSegments()); + return controlPointsAttractionForce(0, numSegments()); } QuadraticFunction XSpline::controlPointsAttractionForce(int seg_begin, int seg_end) const { - using namespace adiff; + using namespace adiff; - assert(seg_begin >= 0 && seg_begin <= numSegments()); - assert(seg_end >= 0 && seg_end <= numSegments()); - assert(seg_begin <= seg_end); + assert(seg_begin >= 0 && seg_begin <= numSegments()); + assert(seg_end >= 0 && seg_end <= numSegments()); + assert(seg_begin <= seg_end); - const int num_control_points = numControlPoints(); + const int num_control_points = numControlPoints(); - SparseMap<2> sparse_map(num_control_points * 2); - sparse_map.markAllNonZero(); + SparseMap<2> sparse_map(num_control_points * 2); + sparse_map.markAllNonZero(); - Function<2> force(sparse_map); - if (seg_begin != seg_end) { - Function<2> prev_x(seg_begin * 2, m_controlPoints[seg_begin].pos.x(), sparse_map); - Function<2> prev_y(seg_begin * 2 + 1, m_controlPoints[seg_begin].pos.y(), sparse_map); + Function<2> force(sparse_map); + if (seg_begin != seg_end) { + Function<2> prev_x(seg_begin * 2, m_controlPoints[seg_begin].pos.x(), sparse_map); + Function<2> prev_y(seg_begin * 2 + 1, m_controlPoints[seg_begin].pos.y(), sparse_map); - for (int i = seg_begin + 1; i <= seg_end; ++i) { - Function<2> next_x(i * 2, m_controlPoints[i].pos.x(), sparse_map); - Function<2> next_y(i * 2 + 1, m_controlPoints[i].pos.y(), sparse_map); + for (int i = seg_begin + 1; i <= seg_end; ++i) { + Function<2> next_x(i * 2, m_controlPoints[i].pos.x(), sparse_map); + Function<2> next_y(i * 2 + 1, m_controlPoints[i].pos.y(), sparse_map); - const Function<2> dx(next_x - prev_x); - const Function<2> dy(next_y - prev_y); - force += dx * dx + dy * dy; + const Function<2> dx(next_x - prev_x); + const Function<2> dy(next_y - prev_y); + force += dx * dx + dy * dy; - next_x.swap(prev_x); - next_y.swap(prev_y); - } + next_x.swap(prev_x); + next_y.swap(prev_y); } + } - QuadraticFunction f(num_control_points * 2); - f.A = 0.5 * force.hessian(sparse_map); - f.b = force.gradient(sparse_map); - f.c = force.value; + QuadraticFunction f(num_control_points * 2); + f.A = 0.5 * force.hessian(sparse_map); + f.b = force.gradient(sparse_map); + f.c = force.value; - return f; + return f; } // XSpline::controlPointsAttractionForce QuadraticFunction XSpline::junctionPointsAttractionForce() const { - return junctionPointsAttractionForce(0, numSegments()); + return junctionPointsAttractionForce(0, numSegments()); } QuadraticFunction XSpline::junctionPointsAttractionForce(int seg_begin, int seg_end) const { - using namespace adiff; - - assert(seg_begin >= 0 && seg_begin <= numSegments()); - assert(seg_end >= 0 && seg_end <= numSegments()); - assert(seg_begin <= seg_end); - - const int num_control_points = numControlPoints(); - - SparseMap<2> sparse_map(num_control_points * 2); - sparse_map.markAllNonZero(); - - Function<2> force(sparse_map); - - if (seg_begin != seg_end) { - QPointF pt(pointAt(controlPointIndexToT(seg_begin))); - std::vector coeffs; - Function<2> prev_x(0); - Function<2> prev_y(0); - - for (int i = seg_begin; i <= seg_end; ++i) { - Function<2> next_x(sparse_map); - Function<2> next_y(sparse_map); - - linearCombinationAt(controlPointIndexToT(i), coeffs); - for (const LinearCoefficient& coeff : coeffs) { - const QPointF cp(m_controlPoints[coeff.controlPointIdx].pos); - Function<2> x(coeff.controlPointIdx * 2, cp.x(), sparse_map); - Function<2> y(coeff.controlPointIdx * 2 + 1, cp.y(), sparse_map); - x *= coeff.coeff; - y *= coeff.coeff; - next_x += x; - next_y += y; - } - - if (i != seg_begin) { - const Function<2> dx(next_x - prev_x); - const Function<2> dy(next_y - prev_y); - force += dx * dx + dy * dy; - } - - next_x.swap(prev_x); - next_y.swap(prev_y); - } - } + using namespace adiff; - QuadraticFunction f(num_control_points * 2); - f.A = 0.5 * force.hessian(sparse_map); - f.b = force.gradient(sparse_map); - f.c = force.value; + assert(seg_begin >= 0 && seg_begin <= numSegments()); + assert(seg_end >= 0 && seg_end <= numSegments()); + assert(seg_begin <= seg_end); - return f; -} // XSpline::junctionPointsAttractionForce + const int num_control_points = numControlPoints(); -QPointF XSpline::pointClosestTo(const QPointF to, double* t, double accuracy) const { - if (m_controlPoints.empty()) { - if (t) { - *t = 0; - } + SparseMap<2> sparse_map(num_control_points * 2); + sparse_map.markAllNonZero(); - return QPointF(); - } + Function<2> force(sparse_map); - const int num_segments = numSegments(); - if (num_segments == 0) { - if (t) { - *t = 0; - } + if (seg_begin != seg_end) { + QPointF pt(pointAt(controlPointIndexToT(seg_begin))); + std::vector coeffs; + Function<2> prev_x(0); + Function<2> prev_y(0); - return m_controlPoints.front().pos; - } + for (int i = seg_begin; i <= seg_end; ++i) { + Function<2> next_x(sparse_map); + Function<2> next_y(sparse_map); - QPointF prev_pt(pointAtImpl(0, 0)); - QPointF next_pt; - // Find the closest segment. - int best_segment = 0; - double best_sqdist = Vec2d(to - prev_pt).squaredNorm(); - for (int seg = 0; seg < num_segments; ++seg, prev_pt = next_pt) { - next_pt = pointAtImpl(seg, 1.0); - - const double sqdist = sqDistToLine(to, QLineF(prev_pt, next_pt)); - if (sqdist < best_sqdist) { - best_segment = seg; - best_sqdist = sqdist; - } - } - // Continue with a binary search. - const double sq_accuracy = accuracy * accuracy; - double prev_t = 0; - double next_t = 1; - prev_pt = pointAtImpl(best_segment, prev_t); - next_pt = pointAtImpl(best_segment, next_t); - - while (Vec2d(prev_pt - next_pt).squaredNorm() > sq_accuracy) { - const double mid_t = 0.5 * (prev_t + next_t); - const QPointF mid_pt(pointAtImpl(best_segment, mid_t)); - - const ToLineProjector projector(QLineF(prev_pt, next_pt)); - const double pt = projector.projectionScalar(to); - const double pm = projector.projectionScalar(mid_pt); - if (pt < pm) { - next_t = mid_t; - next_pt = mid_pt; - } else { - prev_t = mid_t; - prev_pt = mid_pt; - } + linearCombinationAt(controlPointIndexToT(i), coeffs); + for (const LinearCoefficient& coeff : coeffs) { + const QPointF cp(m_controlPoints[coeff.controlPointIdx].pos); + Function<2> x(coeff.controlPointIdx * 2, cp.x(), sparse_map); + Function<2> y(coeff.controlPointIdx * 2 + 1, cp.y(), sparse_map); + x *= coeff.coeff; + y *= coeff.coeff; + next_x += x; + next_y += y; + } + + if (i != seg_begin) { + const Function<2> dx(next_x - prev_x); + const Function<2> dy(next_y - prev_y); + force += dx * dx + dy * dy; + } + + next_x.swap(prev_x); + next_y.swap(prev_y); } + } + + QuadraticFunction f(num_control_points * 2); + f.A = 0.5 * force.hessian(sparse_map); + f.b = force.gradient(sparse_map); + f.c = force.value; - // Take the closest of prev_pt and next_pt. - if (Vec2d(to - prev_pt).squaredNorm() < Vec2d(to - next_pt).squaredNorm()) { - if (t) { - *t = (best_segment + prev_t) / num_segments; - } + return f; +} // XSpline::junctionPointsAttractionForce - return prev_pt; +QPointF XSpline::pointClosestTo(const QPointF to, double* t, double accuracy) const { + if (m_controlPoints.empty()) { + if (t) { + *t = 0; + } + + return QPointF(); + } + + const int num_segments = numSegments(); + if (num_segments == 0) { + if (t) { + *t = 0; + } + + return m_controlPoints.front().pos; + } + + QPointF prev_pt(pointAtImpl(0, 0)); + QPointF next_pt; + // Find the closest segment. + int best_segment = 0; + double best_sqdist = Vec2d(to - prev_pt).squaredNorm(); + for (int seg = 0; seg < num_segments; ++seg, prev_pt = next_pt) { + next_pt = pointAtImpl(seg, 1.0); + + const double sqdist = sqDistToLine(to, QLineF(prev_pt, next_pt)); + if (sqdist < best_sqdist) { + best_segment = seg; + best_sqdist = sqdist; + } + } + // Continue with a binary search. + const double sq_accuracy = accuracy * accuracy; + double prev_t = 0; + double next_t = 1; + prev_pt = pointAtImpl(best_segment, prev_t); + next_pt = pointAtImpl(best_segment, next_t); + + while (Vec2d(prev_pt - next_pt).squaredNorm() > sq_accuracy) { + const double mid_t = 0.5 * (prev_t + next_t); + const QPointF mid_pt(pointAtImpl(best_segment, mid_t)); + + const ToLineProjector projector(QLineF(prev_pt, next_pt)); + const double pt = projector.projectionScalar(to); + const double pm = projector.projectionScalar(mid_pt); + if (pt < pm) { + next_t = mid_t; + next_pt = mid_pt; } else { - if (t) { - *t = (best_segment + next_t) / num_segments; - } + prev_t = mid_t; + prev_pt = mid_pt; + } + } + + // Take the closest of prev_pt and next_pt. + if (Vec2d(to - prev_pt).squaredNorm() < Vec2d(to - next_pt).squaredNorm()) { + if (t) { + *t = (best_segment + prev_t) / num_segments; + } - return next_pt; + return prev_pt; + } else { + if (t) { + *t = (best_segment + next_t) / num_segments; } + + return next_pt; + } } // XSpline::pointClosestTo QPointF XSpline::pointClosestTo(const QPointF to, double accuracy) const { - return pointClosestTo(to, nullptr, accuracy); + return pointClosestTo(to, nullptr, accuracy); } std::vector XSpline::toPolyline(const SamplingParams& params, double from_t, double to_t) const { - std::vector polyline; + std::vector polyline; - auto sink = [&polyline](const QPointF& pt, double, SampleFlags) { polyline.push_back(pt); }; - sample(ProxyFunction(sink), params, from_t, to_t); + auto sink = [&polyline](const QPointF& pt, double, SampleFlags) { polyline.push_back(pt); }; + sample(ProxyFunction(sink), params, from_t, to_t); - return polyline; + return polyline; } double XSpline::sqDistToLine(const QPointF& pt, const QLineF& line) { - const ToLineProjector projector(line); - const double p = projector.projectionScalar(pt); - QPointF ppt; - if (p <= 0) { - ppt = line.p1(); - } else if (p >= 1) { - ppt = line.p2(); - } else { - ppt = line.pointAt(p); - } - - return Vec2d(pt - ppt).squaredNorm(); + const ToLineProjector projector(line); + const double p = projector.projectionScalar(pt); + QPointF ppt; + if (p <= 0) { + ppt = line.p1(); + } else if (p >= 1) { + ppt = line.p2(); + } else { + ppt = line.pointAt(p); + } + + return Vec2d(pt - ppt).squaredNorm(); } /*===================== TensionDerivedParams =====================*/ @@ -734,109 +733,107 @@ const double XSpline::TensionDerivedParams::t2 = 1; const double XSpline::TensionDerivedParams::t3 = 2; static double square(double v) { - return v * v; + return v * v; } XSpline::TensionDerivedParams::TensionDerivedParams(const double tension1, const double tension2) { - // tension1, tension2 lie in [-1 .. 1] - - // Tk+ = t(k+1) + s(k+1) - // Tk- = t(k-1) - s(k-1) - const double s1 = std::max(tension1, 0); - const double s2 = std::max(tension2, 0); - T0p = t1 + s1; - T1p = t2 + s2; - T2m = t1 - s1; - T3m = t2 - s2; - - // q's lie in [0 .. 0.5] - const double s_1 = -0.5 * std::min(tension1, 0); - const double s_2 = -0.5 * std::min(tension2, 0); - q[0] = s_1; - q[1] = s_2; - q[2] = s_1; - q[3] = s_2; - // Formula 17 in [1]: - p[0] = 2.0 * square(t0 - T0p); - p[1] = 2.0 * square(t1 - T1p); - p[2] = 2.0 * square(t2 - T2m); - p[3] = 2.0 * square(t3 - T3m); + // tension1, tension2 lie in [-1 .. 1] + + // Tk+ = t(k+1) + s(k+1) + // Tk- = t(k-1) - s(k-1) + const double s1 = std::max(tension1, 0); + const double s2 = std::max(tension2, 0); + T0p = t1 + s1; + T1p = t2 + s2; + T2m = t1 - s1; + T3m = t2 - s2; + + // q's lie in [0 .. 0.5] + const double s_1 = -0.5 * std::min(tension1, 0); + const double s_2 = -0.5 * std::min(tension2, 0); + q[0] = s_1; + q[1] = s_2; + q[2] = s_1; + q[3] = s_2; + // Formula 17 in [1]: + p[0] = 2.0 * square(t0 - T0p); + p[1] = 2.0 * square(t1 - T1p); + p[2] = 2.0 * square(t2 - T2m); + p[3] = 2.0 * square(t3 - T3m); } /*========================== GBlendFunc ==========================*/ XSpline::GBlendFunc::GBlendFunc(double q, double p) - : m_c1(q), - // See formula 20 in [1]. - m_c2(2 * q), - m_c3(10 - 12 * q - p), - m_c4(2 * p + 14 * q - 15), - m_c5(6 - 5 * q - p) { -} + : m_c1(q), + // See formula 20 in [1]. + m_c2(2 * q), + m_c3(10 - 12 * q - p), + m_c4(2 * p + 14 * q - 15), + m_c5(6 - 5 * q - p) {} double XSpline::GBlendFunc::value(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; - const double u4 = u3 * u; - const double u5 = u4 * u; + const double u2 = u * u; + const double u3 = u2 * u; + const double u4 = u3 * u; + const double u5 = u4 * u; - return m_c1 * u + m_c2 * u2 + m_c3 * u3 + m_c4 * u4 + m_c5 * u5; + return m_c1 * u + m_c2 * u2 + m_c3 * u3 + m_c4 * u4 + m_c5 * u5; } double XSpline::GBlendFunc::firstDerivative(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; - const double u4 = u3 * u; + const double u2 = u * u; + const double u3 = u2 * u; + const double u4 = u3 * u; - return m_c1 + 2 * m_c2 * u + 3 * m_c3 * u2 + 4 * m_c4 * u3 + 5 * m_c5 * u4; + return m_c1 + 2 * m_c2 * u + 3 * m_c3 * u2 + 4 * m_c4 * u3 + 5 * m_c5 * u4; } double XSpline::GBlendFunc::secondDerivative(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; + const double u2 = u * u; + const double u3 = u2 * u; - return 2 * m_c2 + 6 * m_c3 * u + 12 * m_c4 * u2 + 20 * m_c5 * u3; + return 2 * m_c2 + 6 * m_c3 * u + 12 * m_c4 * u2 + 20 * m_c5 * u3; } /*========================== HBlendFunc ==========================*/ XSpline::HBlendFunc::HBlendFunc(double q) - : m_c1(q), - // See formula 20 in [1]. - m_c2(2 * q), - m_c4(-2 * q), - m_c5(-q) { -} + : m_c1(q), + // See formula 20 in [1]. + m_c2(2 * q), + m_c4(-2 * q), + m_c5(-q) {} double XSpline::HBlendFunc::value(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; - const double u4 = u3 * u; - const double u5 = u4 * u; + const double u2 = u * u; + const double u3 = u2 * u; + const double u4 = u3 * u; + const double u5 = u4 * u; - return m_c1 * u + m_c2 * u2 + m_c4 * u4 + m_c5 * u5; + return m_c1 * u + m_c2 * u2 + m_c4 * u4 + m_c5 * u5; } double XSpline::HBlendFunc::firstDerivative(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; - const double u4 = u3 * u; + const double u2 = u * u; + const double u3 = u2 * u; + const double u4 = u3 * u; - return m_c1 + 2 * m_c2 * u + 4 * m_c4 * u3 + 5 * m_c5 * u4; + return m_c1 + 2 * m_c2 * u + 4 * m_c4 * u3 + 5 * m_c5 * u4; } double XSpline::HBlendFunc::secondDerivative(double u) const { - const double u2 = u * u; - const double u3 = u2 * u; + const double u2 = u * u; + const double u3 = u2 * u; - return 2 * m_c2 + 12 * m_c4 * u2 + 20 * m_c5 * u3; + return 2 * m_c2 + 12 * m_c4 * u2 + 20 * m_c5 * u3; } /*======================== PointAndDerivs ========================*/ double XSpline::PointAndDerivs::signedCurvature() const { - const double cross = firstDeriv.x() * secondDeriv.y() - firstDeriv.y() * secondDeriv.x(); - double tlen = std::sqrt(firstDeriv.x() * firstDeriv.x() + firstDeriv.y() * firstDeriv.y()); + const double cross = firstDeriv.x() * secondDeriv.y() - firstDeriv.y() * secondDeriv.x(); + double tlen = std::sqrt(firstDeriv.x() * firstDeriv.x() + firstDeriv.y() * firstDeriv.y()); - return cross / (tlen * tlen * tlen); + return cross / (tlen * tlen * tlen); } diff --git a/math/XSpline.h b/math/XSpline.h index 534b49af0..c81b922c1 100644 --- a/math/XSpline.h +++ b/math/XSpline.h @@ -19,13 +19,13 @@ #ifndef XSPLINE_H_ #define XSPLINE_H_ -#include "spfit/FittableSpline.h" -#include "QuadraticFunction.h" -#include "VirtualFunction.h" -#include "NumericTraits.h" -#include #include +#include #include +#include "NumericTraits.h" +#include "QuadraticFunction.h" +#include "VirtualFunction.h" +#include "spfit/FittableSpline.h" /** * \brief An open X-Spline. @@ -34,197 +34,193 @@ * http://scholar.google.com/scholar?cluster=2002168279173394147&hl=en&as_sdt=0,5 */ class XSpline : public spfit::FittableSpline { -public: - struct PointAndDerivs { - QPointF point; - - /**< Point on a spline. */ - QPointF firstDeriv; - - /**< First derivative with respect to t. */ - QPointF secondDeriv; /**< Second derivative with respect to t. */ - - /** - * \brief Curvature at a given point on the spline. - * - * The sign indicates curving direction. Positive signs normally - * indicate anti-clockwise direction, though in 2D computer graphics - * it's usually the other way around, as the Y axis points down. - * In other words, if you rotate your coordinate system so that - * the X axis aligns with the tangent vector, curvature will be - * positive if the spline curves towards the positive Y direction. - */ - double signedCurvature() const; - }; - - int numControlPoints() const override; + public: + struct PointAndDerivs { + QPointF point; - /** - * Returns the number of segments, that is spans between adjacent control points. - * Because this class only deals with open splines, the number of segments - * will always be max(0, numControlPoints() - 1). - */ - int numSegments() const; + /**< Point on a spline. */ + QPointF firstDeriv; - double controlPointIndexToT(int idx) const; + /**< First derivative with respect to t. */ + QPointF secondDeriv; /**< Second derivative with respect to t. */ /** - * \brief Appends a control point to the end of the spline. + * \brief Curvature at a given point on the spline. * - * Tension values lie in the range of [-1, 1]. - * \li tension < 0 produces interpolating patches. - * \li tension == 0 produces sharp angle interpolating patches. - * \li tension > 0 produces approximating patches. + * The sign indicates curving direction. Positive signs normally + * indicate anti-clockwise direction, though in 2D computer graphics + * it's usually the other way around, as the Y axis points down. + * In other words, if you rotate your coordinate system so that + * the X axis aligns with the tangent vector, curvature will be + * positive if the spline curves towards the positive Y direction. */ - void appendControlPoint(const QPointF& pos, double tension); + double signedCurvature() const; + }; + + int numControlPoints() const override; + + /** + * Returns the number of segments, that is spans between adjacent control points. + * Because this class only deals with open splines, the number of segments + * will always be max(0, numControlPoints() - 1). + */ + int numSegments() const; + + double controlPointIndexToT(int idx) const; + + /** + * \brief Appends a control point to the end of the spline. + * + * Tension values lie in the range of [-1, 1]. + * \li tension < 0 produces interpolating patches. + * \li tension == 0 produces sharp angle interpolating patches. + * \li tension > 0 produces approximating patches. + */ + void appendControlPoint(const QPointF& pos, double tension); + + /** + * \brief Inserts a control at a specified position. + * + * \p idx is the position where the new control point will end up in. + * The following control points will be shifted. + */ + void insertControlPoint(int idx, const QPointF& pos, double tension); + + void eraseControlPoint(int idx); + + QPointF controlPointPosition(int idx) const override; + + void moveControlPoint(int idx, const QPointF& pos) override; + + double controlPointTension(int idx) const; + + void setControlPointTension(int idx, double tension); + + /** + * \brief Calculates a point on the spline at position t. + * + * \param t Position on a spline in the range of [0, 1]. + * \return Point on a spline. + * + * \note Calling this function with less then 2 control points + * leads to undefined behaviour. + */ + QPointF pointAt(double t) const; + + /** + * \brief Calculates a point on the spline plus the first and the second derivatives at position t. + */ + PointAndDerivs pointAndDtsAt(double t) const; + + /** \see spfit::FittableSpline::linearCombinationAt() */ + void linearCombinationAt(double t, std::vector& coeffs) const override; + + /** + * Returns a function equivalent to: + * \code + * sum((cp[i].x - cp[i - 1].x)^2 + (cp[i].y - cp[i - 1].y)^2) + * \endcode + * Except the returned function is a function of control point displacements, + * not positions. + * The sum is calculated across all segments. + */ + QuadraticFunction controlPointsAttractionForce() const; + + /** + * Same as the above one, but you provide a range of segments to consider. + * The range is half-closed: [seg_begin, seg_end) + */ + QuadraticFunction controlPointsAttractionForce(int seg_begin, int seg_end) const; + + /** + * Returns a function equivalent to: + * \code + * sum(Vec2d(pointAt(controlPointIndexToT(i)) - pointAt(controlPointIndexToT(i - 1))).squaredNorm()) + * \endcode + * Except the returned function is a function of control point displacements, + * not positions. + * The sum is calculated across all segments. + */ + QuadraticFunction junctionPointsAttractionForce() const; + + /** + * Same as the above one, but you provide a range of segments to consider. + * The range is half-closed: [seg_begin, seg_end) + */ + QuadraticFunction junctionPointsAttractionForce(int seg_begin, int seg_end) const; + + /** + * \brief Finds a point on the spline that's closest to a given point. + * + * \param to The point which we trying to minimize the distance to. + * \param t If provided, the t value corresponding to the found point will be written there. + * \param accuracy The maximum distance from the found point to the spline. + * \return The closest point found. + */ + QPointF pointClosestTo(QPointF to, double* t, double accuracy = 0.2) const; + + QPointF pointClosestTo(QPointF to, double accuracy = 0.2) const; + + /** \see spfit::FittableSpline::sample() */ + void sample(const VirtualFunction& sink, + const SamplingParams& params = SamplingParams(), + double from_t = 0.0, + double to_t = 1.0) const override; + + std::vector toPolyline(const SamplingParams& params = SamplingParams(), + double from_t = 0.0, + double to_t = 1.0) const; + + void swap(XSpline& other) { m_controlPoints.swap(other.m_controlPoints); } + + private: + struct ControlPoint { + QPointF pos; /** - * \brief Inserts a control at a specified position. - * - * \p idx is the position where the new control point will end up in. - * The following control points will be shifted. + * Tension is in range of [-1 .. 1] and corresponds to sk as defined in section 5 of [1], + * not to be confused with sk defined in section 4, which has a range of [0 .. 1]. */ - void insertControlPoint(int idx, const QPointF& pos, double tension); - - void eraseControlPoint(int idx); - - QPointF controlPointPosition(int idx) const override; - - void moveControlPoint(int idx, const QPointF& pos) override; - - double controlPointTension(int idx) const; - - void setControlPointTension(int idx, double tension); - - /** - * \brief Calculates a point on the spline at position t. - * - * \param t Position on a spline in the range of [0, 1]. - * \return Point on a spline. - * - * \note Calling this function with less then 2 control points - * leads to undefined behaviour. - */ - QPointF pointAt(double t) const; - - /** - * \brief Calculates a point on the spline plus the first and the second derivatives at position t. - */ - PointAndDerivs pointAndDtsAt(double t) const; - - /** \see spfit::FittableSpline::linearCombinationAt() */ - void linearCombinationAt(double t, std::vector& coeffs) const override; - - /** - * Returns a function equivalent to: - * \code - * sum((cp[i].x - cp[i - 1].x)^2 + (cp[i].y - cp[i - 1].y)^2) - * \endcode - * Except the returned function is a function of control point displacements, - * not positions. - * The sum is calculated across all segments. - */ - QuadraticFunction controlPointsAttractionForce() const; - - /** - * Same as the above one, but you provide a range of segments to consider. - * The range is half-closed: [seg_begin, seg_end) - */ - QuadraticFunction controlPointsAttractionForce(int seg_begin, int seg_end) const; - - /** - * Returns a function equivalent to: - * \code - * sum(Vec2d(pointAt(controlPointIndexToT(i)) - pointAt(controlPointIndexToT(i - 1))).squaredNorm()) - * \endcode - * Except the returned function is a function of control point displacements, - * not positions. - * The sum is calculated across all segments. - */ - QuadraticFunction junctionPointsAttractionForce() const; - - /** - * Same as the above one, but you provide a range of segments to consider. - * The range is half-closed: [seg_begin, seg_end) - */ - QuadraticFunction junctionPointsAttractionForce(int seg_begin, int seg_end) const; - - /** - * \brief Finds a point on the spline that's closest to a given point. - * - * \param to The point which we trying to minimize the distance to. - * \param t If provided, the t value corresponding to the found point will be written there. - * \param accuracy The maximum distance from the found point to the spline. - * \return The closest point found. - */ - QPointF pointClosestTo(QPointF to, double* t, double accuracy = 0.2) const; - - QPointF pointClosestTo(QPointF to, double accuracy = 0.2) const; - - /** \see spfit::FittableSpline::sample() */ - void sample(const VirtualFunction& sink, - const SamplingParams& params = SamplingParams(), - double from_t = 0.0, - double to_t = 1.0) const override; - - std::vector toPolyline(const SamplingParams& params = SamplingParams(), - double from_t = 0.0, - double to_t = 1.0) const; - - void swap(XSpline& other) { - m_controlPoints.swap(other.m_controlPoints); - } - -private: - struct ControlPoint { - QPointF pos; - - /** - * Tension is in range of [-1 .. 1] and corresponds to sk as defined in section 5 of [1], - * not to be confused with sk defined in section 4, which has a range of [0 .. 1]. - */ - double tension; + double tension; - ControlPoint() : tension(0) { - } + ControlPoint() : tension(0) {} - ControlPoint(const QPointF& p, double tns) : pos(p), tension(tns) { - } - }; + ControlPoint(const QPointF& p, double tns) : pos(p), tension(tns) {} + }; - struct TensionDerivedParams; + struct TensionDerivedParams; - class GBlendFunc; - class HBlendFunc; + class GBlendFunc; + class HBlendFunc; - struct DecomposedDerivs; + struct DecomposedDerivs; - QPointF pointAtImpl(int segment, double t) const; + QPointF pointAtImpl(int segment, double t) const; - int linearCombinationFor(LinearCoefficient* coeffs, int segment, double t) const; + int linearCombinationFor(LinearCoefficient* coeffs, int segment, double t) const; - DecomposedDerivs decomposedDerivs(double t) const; + DecomposedDerivs decomposedDerivs(double t) const; - DecomposedDerivs decomposedDerivsImpl(int segment, double t) const; + DecomposedDerivs decomposedDerivsImpl(int segment, double t) const; - void maybeAddMoreSamples(const VirtualFunction& sink, - double max_sqdist_to_spline, - double max_sqdist_between_samples, - double num_segments, - double r_num_segments, - double prev_t, - const QPointF& prev_pt, - double next_t, - const QPointF& next_pt) const; + void maybeAddMoreSamples(const VirtualFunction& sink, + double max_sqdist_to_spline, + double max_sqdist_between_samples, + double num_segments, + double r_num_segments, + double prev_t, + const QPointF& prev_pt, + double next_t, + const QPointF& next_pt) const; - static double sqDistToLine(const QPointF& pt, const QLineF& line); + static double sqDistToLine(const QPointF& pt, const QLineF& line); - std::vector m_controlPoints; + std::vector m_controlPoints; }; inline void swap(XSpline& o1, XSpline& o2) { - o1.swap(o2); + o1.swap(o2); } #endif // ifndef XSPLINE_H_ diff --git a/math/adiff/Function.cpp b/math/adiff/Function.cpp index e1bb58394..c5833e3e3 100644 --- a/math/adiff/Function.cpp +++ b/math/adiff/Function.cpp @@ -25,218 +25,216 @@ namespace adiff { // 2. sparse_map(i, j) corresponds to l(i, j) in [1]. Function<2>::Function(size_t num_non_zero_vars) - : value(), firstDerivs(num_non_zero_vars), secondDerivs(num_non_zero_vars) { -} + : value(), firstDerivs(num_non_zero_vars), secondDerivs(num_non_zero_vars) {} Function<2>::Function(const SparseMap<2>& sparse_map) - : value(), firstDerivs(sparse_map.numNonZeroElements()), secondDerivs(sparse_map.numNonZeroElements()) { -} + : value(), firstDerivs(sparse_map.numNonZeroElements()), secondDerivs(sparse_map.numNonZeroElements()) {} Function<2>::Function(size_t arg_idx, double val, const SparseMap<2>& sparse_map) - : value(val), firstDerivs(sparse_map.numNonZeroElements()), secondDerivs(sparse_map.numNonZeroElements()) { - // An argument Xi is represented as a function: - // f(X1, X2, ..., Xi, ...) = Xi - // Derivatives are calculated accordingly. - - const size_t num_vars = sparse_map.numVars(); - - // arg_idx row - for (size_t i = 0; i < num_vars; ++i) { - const size_t u = sparse_map.nonZeroElementIdx(arg_idx, i); - if (u != sparse_map.ZERO_ELEMENT) { - firstDerivs[u] = 1.0; - } + : value(val), firstDerivs(sparse_map.numNonZeroElements()), secondDerivs(sparse_map.numNonZeroElements()) { + // An argument Xi is represented as a function: + // f(X1, X2, ..., Xi, ...) = Xi + // Derivatives are calculated accordingly. + + const size_t num_vars = sparse_map.numVars(); + + // arg_idx row + for (size_t i = 0; i < num_vars; ++i) { + const size_t u = sparse_map.nonZeroElementIdx(arg_idx, i); + if (u != sparse_map.ZERO_ELEMENT) { + firstDerivs[u] = 1.0; } - // arg_idx column - for (size_t i = 0; i < num_vars; ++i) { - const size_t u = sparse_map.nonZeroElementIdx(i, arg_idx); - if (u != sparse_map.ZERO_ELEMENT) { - firstDerivs[u] = 1.0; - } + } + // arg_idx column + for (size_t i = 0; i < num_vars; ++i) { + const size_t u = sparse_map.nonZeroElementIdx(i, arg_idx); + if (u != sparse_map.ZERO_ELEMENT) { + firstDerivs[u] = 1.0; } + } } VecT Function<2>::gradient(const SparseMap<2>& sparse_map) const { - const size_t num_vars = sparse_map.numVars(); - VecT grad(num_vars); + const size_t num_vars = sparse_map.numVars(); + VecT grad(num_vars); - for (size_t i = 0; i < num_vars; ++i) { - const size_t u = sparse_map.nonZeroElementIdx(i, i); - if (u != sparse_map.ZERO_ELEMENT) { - grad[i] = firstDerivs[u]; - } + for (size_t i = 0; i < num_vars; ++i) { + const size_t u = sparse_map.nonZeroElementIdx(i, i); + if (u != sparse_map.ZERO_ELEMENT) { + grad[i] = firstDerivs[u]; } + } - return grad; + return grad; } MatT Function<2>::hessian(const SparseMap<2>& sparse_map) const { - const size_t num_vars = sparse_map.numVars(); - MatT hess(num_vars, num_vars); - - for (size_t i = 0; i < num_vars; ++i) { - for (size_t j = 0; j < num_vars; ++j) { - double Fij = 0; - const size_t ij = sparse_map.nonZeroElementIdx(i, j); - if (ij != sparse_map.ZERO_ELEMENT) { - if (i == j) { - Fij = secondDerivs[ij]; - } else { - const size_t ii = sparse_map.nonZeroElementIdx(i, i); - const size_t jj = sparse_map.nonZeroElementIdx(j, j); - assert(ii != sparse_map.ZERO_ELEMENT && jj != sparse_map.ZERO_ELEMENT); - Fij = 0.5 * (secondDerivs[ij] - (secondDerivs[ii] + secondDerivs[jj])); - } - } - hess(i, j) = Fij; + const size_t num_vars = sparse_map.numVars(); + MatT hess(num_vars, num_vars); + + for (size_t i = 0; i < num_vars; ++i) { + for (size_t j = 0; j < num_vars; ++j) { + double Fij = 0; + const size_t ij = sparse_map.nonZeroElementIdx(i, j); + if (ij != sparse_map.ZERO_ELEMENT) { + if (i == j) { + Fij = secondDerivs[ij]; + } else { + const size_t ii = sparse_map.nonZeroElementIdx(i, i); + const size_t jj = sparse_map.nonZeroElementIdx(j, j); + assert(ii != sparse_map.ZERO_ELEMENT && jj != sparse_map.ZERO_ELEMENT); + Fij = 0.5 * (secondDerivs[ij] - (secondDerivs[ii] + secondDerivs[jj])); } + } + hess(i, j) = Fij; } + } - return hess; + return hess; } void Function<2>::swap(Function<2>& other) { - std::swap(value, other.value); - firstDerivs.swap(other.firstDerivs); - secondDerivs.swap(other.secondDerivs); + std::swap(value, other.value); + firstDerivs.swap(other.firstDerivs); + secondDerivs.swap(other.secondDerivs); } Function<2>& Function<2>::operator+=(const Function<2>& other) { - const size_t p = firstDerivs.size(); - assert(secondDerivs.size() == p); - assert(other.firstDerivs.size() == p); - assert(other.secondDerivs.size() == p); + const size_t p = firstDerivs.size(); + assert(secondDerivs.size() == p); + assert(other.firstDerivs.size() == p); + assert(other.secondDerivs.size() == p); - value += other.value; + value += other.value; - for (size_t u = 0; u < p; ++u) { - firstDerivs[u] += other.firstDerivs[u]; - secondDerivs[u] += other.secondDerivs[u]; - } + for (size_t u = 0; u < p; ++u) { + firstDerivs[u] += other.firstDerivs[u]; + secondDerivs[u] += other.secondDerivs[u]; + } - return *this; + return *this; } Function<2>& Function<2>::operator-=(const Function<2>& other) { - const size_t p = firstDerivs.size(); - assert(secondDerivs.size() == p); - assert(other.firstDerivs.size() == p); - assert(other.secondDerivs.size() == p); + const size_t p = firstDerivs.size(); + assert(secondDerivs.size() == p); + assert(other.firstDerivs.size() == p); + assert(other.secondDerivs.size() == p); - value -= other.value; + value -= other.value; - for (size_t u = 0; u < p; ++u) { - firstDerivs[u] -= other.firstDerivs[u]; - secondDerivs[u] -= other.secondDerivs[u]; - } + for (size_t u = 0; u < p; ++u) { + firstDerivs[u] -= other.firstDerivs[u]; + secondDerivs[u] -= other.secondDerivs[u]; + } - return *this; + return *this; } Function<2>& Function<2>::operator*=(double scalar) { - const size_t p = firstDerivs.size(); - value *= scalar; + const size_t p = firstDerivs.size(); + value *= scalar; - for (size_t u = 0; u < p; ++u) { - firstDerivs[u] *= scalar; - } + for (size_t u = 0; u < p; ++u) { + firstDerivs[u] *= scalar; + } - return *this; + return *this; } Function<2> operator+(const Function<2>& f1, const Function<2>& f2) { - const size_t p = f1.firstDerivs.size(); - assert(f1.secondDerivs.size() == p); - assert(f2.firstDerivs.size() == p); - assert(f2.secondDerivs.size() == p); + const size_t p = f1.firstDerivs.size(); + assert(f1.secondDerivs.size() == p); + assert(f2.firstDerivs.size() == p); + assert(f2.secondDerivs.size() == p); - Function<2> res(p); - res.value = f1.value + f2.value; + Function<2> res(p); + res.value = f1.value + f2.value; - for (size_t u = 0; u < p; ++u) { - res.firstDerivs[u] = f1.firstDerivs[u] + f2.firstDerivs[u]; - res.secondDerivs[u] = f1.secondDerivs[u] + f2.secondDerivs[u]; - } + for (size_t u = 0; u < p; ++u) { + res.firstDerivs[u] = f1.firstDerivs[u] + f2.firstDerivs[u]; + res.secondDerivs[u] = f1.secondDerivs[u] + f2.secondDerivs[u]; + } - return res; + return res; } Function<2> operator-(const Function<2>& f1, const Function<2>& f2) { - const size_t p = f1.firstDerivs.size(); - assert(f1.secondDerivs.size() == p); - assert(f2.firstDerivs.size() == p); - assert(f2.secondDerivs.size() == p); + const size_t p = f1.firstDerivs.size(); + assert(f1.secondDerivs.size() == p); + assert(f2.firstDerivs.size() == p); + assert(f2.secondDerivs.size() == p); - Function<2> res(p); - res.value = f1.value - f2.value; + Function<2> res(p); + res.value = f1.value - f2.value; - for (size_t u = 0; u < p; ++u) { - res.firstDerivs[u] = f1.firstDerivs[u] - f2.firstDerivs[u]; - res.secondDerivs[u] = f1.secondDerivs[u] - f2.secondDerivs[u]; - } + for (size_t u = 0; u < p; ++u) { + res.firstDerivs[u] = f1.firstDerivs[u] - f2.firstDerivs[u]; + res.secondDerivs[u] = f1.secondDerivs[u] - f2.secondDerivs[u]; + } - return res; + return res; } Function<2> operator*(const Function<2>& f1, const Function<2>& f2) { - const size_t p = f1.firstDerivs.size(); - assert(f1.secondDerivs.size() == p); - assert(f2.firstDerivs.size() == p); - assert(f2.secondDerivs.size() == p); - - Function<2> res(p); - res.value = f1.value * f2.value; - - for (size_t u = 0; u < p; ++u) { - res.firstDerivs[u] = f1.firstDerivs[u] * f2.value + f1.value * f2.firstDerivs[u]; - res.secondDerivs[u] = f1.secondDerivs[u] * f2.value + 2.0 * f1.firstDerivs[u] * f2.firstDerivs[u] - + f1.value * f2.secondDerivs[u]; - } + const size_t p = f1.firstDerivs.size(); + assert(f1.secondDerivs.size() == p); + assert(f2.firstDerivs.size() == p); + assert(f2.secondDerivs.size() == p); - return res; + Function<2> res(p); + res.value = f1.value * f2.value; + + for (size_t u = 0; u < p; ++u) { + res.firstDerivs[u] = f1.firstDerivs[u] * f2.value + f1.value * f2.firstDerivs[u]; + res.secondDerivs[u] + = f1.secondDerivs[u] * f2.value + 2.0 * f1.firstDerivs[u] * f2.firstDerivs[u] + f1.value * f2.secondDerivs[u]; + } + + return res; } Function<2> operator*(const Function<2>& f, double scalar) { - Function<2> res(f); - res *= scalar; + Function<2> res(f); + res *= scalar; - return res; + return res; } Function<2> operator*(double scalar, const Function<2>& f) { - Function<2> res(f); - res *= scalar; + Function<2> res(f); + res *= scalar; - return res; + return res; } Function<2> operator/(const Function<2>& num, const Function<2>& den) { - const size_t p = num.firstDerivs.size(); - assert(num.secondDerivs.size() == p); - assert(den.firstDerivs.size() == p); - assert(den.secondDerivs.size() == p); + const size_t p = num.firstDerivs.size(); + assert(num.secondDerivs.size() == p); + assert(den.firstDerivs.size() == p); + assert(den.secondDerivs.size() == p); - Function<2> res(p); - res.value = num.value / den.value; + Function<2> res(p); + res.value = num.value / den.value; - const double den2 = den.value * den.value; - const double den4 = den2 * den2; + const double den2 = den.value * den.value; + const double den4 = den2 * den2; - for (size_t u = 0; u < p; ++u) { - // Derivative of: (num.value / den.value) - const double d1 = num.firstDerivs[u] * den.value - num.value * den.firstDerivs[u]; - res.firstDerivs[u] = d1 / den2; + for (size_t u = 0; u < p; ++u) { + // Derivative of: (num.value / den.value) + const double d1 = num.firstDerivs[u] * den.value - num.value * den.firstDerivs[u]; + res.firstDerivs[u] = d1 / den2; - // Derivative of: (num.firstDerivs[u] * den.value - num.value * den.firstDerivs[u]) - const double d2 = num.secondDerivs[u] * den.value - num.value * den.secondDerivs[u]; + // Derivative of: (num.firstDerivs[u] * den.value - num.value * den.firstDerivs[u]) + const double d2 = num.secondDerivs[u] * den.value - num.value * den.secondDerivs[u]; - // Derivative of: den2 - const double d3 = 2.0 * den.value * den.firstDerivs[u]; + // Derivative of: den2 + const double d3 = 2.0 * den.value * den.firstDerivs[u]; - // Derivative of: (d1 / den2) - res.secondDerivs[u] = (d2 * den2 - d1 * d3) / den4; - } + // Derivative of: (d1 / den2) + res.secondDerivs[u] = (d2 * den2 - d1 * d3) / den4; + } - return res; + return res; } } // namespace adiff \ No newline at end of file diff --git a/math/adiff/Function.h b/math/adiff/Function.h index 5958e5d30..1e5290aac 100644 --- a/math/adiff/Function.h +++ b/math/adiff/Function.h @@ -19,10 +19,10 @@ #ifndef ADIFF_FUNCTION_H_ #define ADIFF_FUNCTION_H_ -#include "SparseMap.h" +#include #include "MatT.h" +#include "SparseMap.h" #include "VecT.h" -#include namespace adiff { /** @@ -31,63 +31,63 @@ namespace adiff { * It would be nice to have a generic version, * but for now it's only specialized for ORD == 2. */ -template +template class Function; -template<> +template <> class Function<2> { - // Member-wise copying is OK. -public: - /** The value of the function. */ - double value; + // Member-wise copying is OK. + public: + /** The value of the function. */ + double value; - /** - * First directional derivatives in the direction - * of u = i + j for every non-zero Hessian element at i, j. - */ - VecT firstDerivs; + /** + * First directional derivatives in the direction + * of u = i + j for every non-zero Hessian element at i, j. + */ + VecT firstDerivs; - /** - * Second directional derivatives in the direction - * of u = i + j for every non-zero Hessian element at i, j. - */ - VecT secondDerivs; + /** + * Second directional derivatives in the direction + * of u = i + j for every non-zero Hessian element at i, j. + */ + VecT secondDerivs; - /** - * Constructs the "f(x1, x2, ...) = 0" function. - */ - explicit Function(size_t num_non_zero_vars); + /** + * Constructs the "f(x1, x2, ...) = 0" function. + */ + explicit Function(size_t num_non_zero_vars); - /** - * Constructs the "f(x1, x2, ...) = 0" function. - */ - explicit Function(const SparseMap<2>& sparse_map); + /** + * Constructs the "f(x1, x2, ...) = 0" function. + */ + explicit Function(const SparseMap<2>& sparse_map); - /** - * Constructs a function representing an argument. - * - * \param arg_idx Argument number. - * \param val Argument value. - * \param sparse_map Tells which derivatives to compute. - */ - Function(size_t arg_idx, double val, const SparseMap<2>& sparse_map); + /** + * Constructs a function representing an argument. + * + * \param arg_idx Argument number. + * \param val Argument value. + * \param sparse_map Tells which derivatives to compute. + */ + Function(size_t arg_idx, double val, const SparseMap<2>& sparse_map); - VecT gradient(const SparseMap<2>& sparse_map) const; + VecT gradient(const SparseMap<2>& sparse_map) const; - MatT hessian(const SparseMap<2>& sparse_map) const; + MatT hessian(const SparseMap<2>& sparse_map) const; - void swap(Function& other); + void swap(Function& other); - Function& operator+=(const Function& other); + Function& operator+=(const Function& other); - Function& operator-=(const Function& other); + Function& operator-=(const Function& other); - Function& operator*=(double scalar); + Function& operator*=(double scalar); }; inline void swap(Function<2>& f1, Function<2>& f2) { - f1.swap(f2); + f1.swap(f2); } Function<2> operator+(const Function<2>& f1, const Function<2>& f2); diff --git a/math/adiff/SparseMap.cpp b/math/adiff/SparseMap.cpp index 755a17162..95ddc850f 100644 --- a/math/adiff/SparseMap.cpp +++ b/math/adiff/SparseMap.cpp @@ -22,26 +22,25 @@ namespace adiff { const size_t SparseMap<2>::ZERO_ELEMENT = ~size_t(0); SparseMap<2>::SparseMap(size_t num_vars) - : m_numVars(num_vars), m_numNonZeroElements(0), m_map(num_vars, num_vars, ZERO_ELEMENT) { -} + : m_numVars(num_vars), m_numNonZeroElements(0), m_map(num_vars, num_vars, ZERO_ELEMENT) {} void SparseMap<2>::markNonZero(size_t i, size_t j) { - size_t& el = m_map(i, j); - if (el == ZERO_ELEMENT) { - el = m_numNonZeroElements; - ++m_numNonZeroElements; - } + size_t& el = m_map(i, j); + if (el == ZERO_ELEMENT) { + el = m_numNonZeroElements; + ++m_numNonZeroElements; + } } void SparseMap<2>::markAllNonZero() { - for (size_t i = 0; i < m_numVars; ++i) { - for (size_t j = 0; j < m_numVars; ++j) { - markNonZero(i, j); - } + for (size_t i = 0; i < m_numVars; ++i) { + for (size_t j = 0; j < m_numVars; ++j) { + markNonZero(i, j); } + } } size_t SparseMap<2>::nonZeroElementIdx(size_t i, size_t j) const { - return m_map(i, j); + return m_map(i, j); } } // namespace adiff \ No newline at end of file diff --git a/math/adiff/SparseMap.h b/math/adiff/SparseMap.h index c3f39a234..1835c0734 100644 --- a/math/adiff/SparseMap.h +++ b/math/adiff/SparseMap.h @@ -19,74 +19,70 @@ #ifndef ADIFF_SPARSITY_H_ #define ADIFF_SPARSITY_H_ -#include "MatT.h" #include +#include "MatT.h" namespace adiff { /** * Specifies which derivatives are non-zero and therefore need to be calculated. * Each such non-zero derivative is assigned an index in [0, total_nonzero_derivs). */ -template +template class SparseMap; /** * The second order sparse map specified which elements of the Hessian * matrix are non-zero. */ -template<> +template <> class SparseMap<2> { - // Member-wise copying is OK. -public: - static const size_t ZERO_ELEMENT; + // Member-wise copying is OK. + public: + static const size_t ZERO_ELEMENT; - /** - * Creates a structure for a (num_vars)x(num_vars) Hessian - * with all elements being initially considered as zero. - */ - explicit SparseMap(size_t num_vars); + /** + * Creates a structure for a (num_vars)x(num_vars) Hessian + * with all elements being initially considered as zero. + */ + explicit SparseMap(size_t num_vars); - /** - * Returns N for an NxN Hessian. - */ - size_t numVars() const { - return m_numVars; - } + /** + * Returns N for an NxN Hessian. + */ + size_t numVars() const { return m_numVars; } - /** - * \brief Marks an element at (i, j) as non-zero. - * - * Calling this function on an element already marked non-zero - * has no effect. - */ - void markNonZero(size_t i, size_t j); + /** + * \brief Marks an element at (i, j) as non-zero. + * + * Calling this function on an element already marked non-zero + * has no effect. + */ + void markNonZero(size_t i, size_t j); - /** - * \brief Marks all elements as non-zero. - * - * The caller shouldn't assume any particular pattern of index - * assignment when using this function. - */ - void markAllNonZero(); + /** + * \brief Marks all elements as non-zero. + * + * The caller shouldn't assume any particular pattern of index + * assignment when using this function. + */ + void markAllNonZero(); - /** - * Returns the number of elements marked as non-zero. - */ - size_t numNonZeroElements() const { - return m_numNonZeroElements; - } + /** + * Returns the number of elements marked as non-zero. + */ + size_t numNonZeroElements() const { return m_numNonZeroElements; } - /** - * Returns an index in the range of [0, numNonZeroElements) - * associated with the element, or ZERO_ELEMENT, if the element - * wasn't marked non-zero. - */ - size_t nonZeroElementIdx(size_t i, size_t j) const; + /** + * Returns an index in the range of [0, numNonZeroElements) + * associated with the element, or ZERO_ELEMENT, if the element + * wasn't marked non-zero. + */ + size_t nonZeroElementIdx(size_t i, size_t j) const; -private: - size_t m_numVars; - size_t m_numNonZeroElements; - MatT m_map; + private: + size_t m_numVars; + size_t m_numNonZeroElements; + MatT m_map; }; } // namespace adiff #endif // ifndef ADIFF_SPARSITY_H_ diff --git a/math/adiff/tests/CMakeLists.txt b/math/adiff/tests/CMakeLists.txt index 5450faa9e..e1490c56f 100644 --- a/math/adiff/tests/CMakeLists.txt +++ b/math/adiff/tests/CMakeLists.txt @@ -1,26 +1,26 @@ -INCLUDE_DIRECTORIES(BEFORE ..) +include_directories(BEFORE ..) -SET( - sources - ${CMAKE_SOURCE_DIR}/tests/main.cpp - TestHessians.cpp +set( + sources + ${CMAKE_SOURCE_DIR}/tests/main.cpp + TestHessians.cpp ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) -SET( - libs - math ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} - ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} +set( + libs + math ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} ) -ADD_EXECUTABLE(adiff_tests ${sources}) -TARGET_LINK_LIBRARIES(adiff_tests ${libs}) +add_executable(adiff_tests ${sources}) +target_link_libraries(adiff_tests ${libs}) # We want the executable located where we copy all the DLLs. -SET_TARGET_PROPERTIES( - adiff_tests PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +set_target_properties( + adiff_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -ADD_TEST(NAME adiff_tests COMMAND adiff_tests --log_level=message) +add_test(NAME adiff_tests COMMAND adiff_tests --log_level=message) diff --git a/math/adiff/tests/TestHessians.cpp b/math/adiff/tests/TestHessians.cpp index 16270ac9b..7bfdcee03 100644 --- a/math/adiff/tests/TestHessians.cpp +++ b/math/adiff/tests/TestHessians.cpp @@ -16,94 +16,94 @@ along with this program. If not, see . */ -#include "Function.h" #include #include -#include #include +#include +#include "Function.h" namespace adiff { namespace tests { BOOST_AUTO_TEST_SUITE(AutomaticDifferentiationTestSuite); BOOST_AUTO_TEST_CASE(test1) { - // F(x) = x^2 | x = 3 + // F(x) = x^2 | x = 3 - SparseMap<2> sparse_map(1); - sparse_map.markAllNonZero(); + SparseMap<2> sparse_map(1); + sparse_map.markAllNonZero(); - const Function<2> x(0, 3, sparse_map); - const Function<2> res(x * x); + const Function<2> x(0, 3, sparse_map); + const Function<2> res(x * x); - const VecT gradient(res.gradient(sparse_map)); - const MatT hessian(res.hessian(sparse_map)); + const VecT gradient(res.gradient(sparse_map)); + const MatT hessian(res.hessian(sparse_map)); - // F = 9 - // Fx = 2 * x = 6 - // Fxx = 2 + // F = 9 + // Fx = 2 * x = 6 + // Fxx = 2 - BOOST_REQUIRE_CLOSE(res.value, 9, 1e-06); - BOOST_REQUIRE_CLOSE(gradient[0], 6, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(0, 0), 2, 1e-06); + BOOST_REQUIRE_CLOSE(res.value, 9, 1e-06); + BOOST_REQUIRE_CLOSE(gradient[0], 6, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(0, 0), 2, 1e-06); } BOOST_AUTO_TEST_CASE(test2) { - // F(x, y) = x^2 | x = 3 - - SparseMap<2> sparse_map(2); - sparse_map.markAllNonZero(); - - const Function<2> x(0, 3, sparse_map); - const Function<2> res(x * x); - - const VecT gradient(res.gradient(sparse_map)); - const MatT hessian(res.hessian(sparse_map)); - - // F = 9 - // Fx = 2 * x = 6 - // Fy = 0 - // Fxx = 2 - // Fyy = 0 - // Fxy = 0 - - BOOST_REQUIRE_CLOSE(res.value, 9, 1e-06); - BOOST_REQUIRE_CLOSE(gradient[0], 6, 1e-06); - BOOST_REQUIRE_CLOSE(gradient[1], 0, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(0, 0), 2, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(1, 0), 0, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(0, 1), 0, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(1, 1), 0, 1e-06); + // F(x, y) = x^2 | x = 3 + + SparseMap<2> sparse_map(2); + sparse_map.markAllNonZero(); + + const Function<2> x(0, 3, sparse_map); + const Function<2> res(x * x); + + const VecT gradient(res.gradient(sparse_map)); + const MatT hessian(res.hessian(sparse_map)); + + // F = 9 + // Fx = 2 * x = 6 + // Fy = 0 + // Fxx = 2 + // Fyy = 0 + // Fxy = 0 + + BOOST_REQUIRE_CLOSE(res.value, 9, 1e-06); + BOOST_REQUIRE_CLOSE(gradient[0], 6, 1e-06); + BOOST_REQUIRE_CLOSE(gradient[1], 0, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(0, 0), 2, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(1, 0), 0, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(0, 1), 0, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(1, 1), 0, 1e-06); } BOOST_AUTO_TEST_CASE(test3) { - // F(x, y) = x^3 * y^2 | x = 2, y = 3 - - SparseMap<2> sparse_map(2); - sparse_map.markAllNonZero(); - - const Function<2> x(0, 2, sparse_map); - const Function<2> y(1, 3, sparse_map); - const Function<2> xxx(x * x * x); - const Function<2> yy(y * y); - const Function<2> res(xxx * yy); - - const VecT gradient(res.gradient(sparse_map)); - const MatT hessian(res.hessian(sparse_map)); - - // F = 72 - // Fx = 3 * x^2 * y^2 = 108 - // Fy = 2 * y * x^3 = 48 - // Fxx = 6 * x * y^2 = 108 - // Fyy = 2 * x^3 = 16 - // Fyx = 6 * y * x^2 = 72 - - BOOST_REQUIRE_CLOSE(res.value, 72, 1e-06); - BOOST_REQUIRE_CLOSE(gradient[0], 108, 1e-06); - BOOST_REQUIRE_CLOSE(gradient[1], 48, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(0, 0), 108, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(0, 1), 72, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(1, 0), 72, 1e-06); - BOOST_REQUIRE_CLOSE(hessian(1, 1), 16, 1e-06); + // F(x, y) = x^3 * y^2 | x = 2, y = 3 + + SparseMap<2> sparse_map(2); + sparse_map.markAllNonZero(); + + const Function<2> x(0, 2, sparse_map); + const Function<2> y(1, 3, sparse_map); + const Function<2> xxx(x * x * x); + const Function<2> yy(y * y); + const Function<2> res(xxx * yy); + + const VecT gradient(res.gradient(sparse_map)); + const MatT hessian(res.hessian(sparse_map)); + + // F = 72 + // Fx = 3 * x^2 * y^2 = 108 + // Fy = 2 * y * x^3 = 48 + // Fxx = 6 * x * y^2 = 108 + // Fyy = 2 * x^3 = 16 + // Fyx = 6 * y * x^2 = 72 + + BOOST_REQUIRE_CLOSE(res.value, 72, 1e-06); + BOOST_REQUIRE_CLOSE(gradient[0], 108, 1e-06); + BOOST_REQUIRE_CLOSE(gradient[1], 48, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(0, 0), 108, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(0, 1), 72, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(1, 0), 72, 1e-06); + BOOST_REQUIRE_CLOSE(hessian(1, 1), 16, 1e-06); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/math/spfit/ConstraintSet.cpp b/math/spfit/ConstraintSet.cpp index f99eb668f..2373ff7e1 100644 --- a/math/spfit/ConstraintSet.cpp +++ b/math/spfit/ConstraintSet.cpp @@ -17,122 +17,122 @@ */ #include "ConstraintSet.h" -#include "FittableSpline.h" #include +#include "FittableSpline.h" namespace spfit { -ConstraintSet::ConstraintSet(const FittableSpline* spline) : m_pSpline(spline) { - assert(spline); +ConstraintSet::ConstraintSet(const FittableSpline* spline) : m_spline(spline) { + assert(spline); } void ConstraintSet::constrainControlPoint(int cp_idx, const QPointF& pos) { - assert(cp_idx >= 0 && cp_idx < m_pSpline->numControlPoints()); - const QPointF cp(m_pSpline->controlPointPosition(cp_idx)); - - // Fix x coordinate. - LinearFunction f(m_pSpline->numControlPoints() * 2); - f.a[cp_idx * 2] = 1; - f.b = cp.x() - pos.x(); - m_constraints.push_back(f); - // Fix y coordinate. - f.a[cp_idx * 2] = 0; - f.a[cp_idx * 2 + 1] = 1; - f.b = cp.y() - pos.y(); - m_constraints.push_back(f); + assert(cp_idx >= 0 && cp_idx < m_spline->numControlPoints()); + const QPointF cp(m_spline->controlPointPosition(cp_idx)); + + // Fix x coordinate. + LinearFunction f(m_spline->numControlPoints() * 2); + f.a[cp_idx * 2] = 1; + f.b = cp.x() - pos.x(); + m_constraints.push_back(f); + // Fix y coordinate. + f.a[cp_idx * 2] = 0; + f.a[cp_idx * 2 + 1] = 1; + f.b = cp.y() - pos.y(); + m_constraints.push_back(f); } void ConstraintSet::constrainSplinePoint(double t, const QPointF& pos) { - std::vector coeffs; - m_pSpline->linearCombinationAt(t, coeffs); - - // Fix the x coordinate. - LinearFunction f(m_pSpline->numControlPoints() * 2); - f.b = -pos.x(); - for (const FittableSpline::LinearCoefficient& coeff : coeffs) { - const int cp_idx = coeff.controlPointIdx; - f.a[cp_idx * 2] = coeff.coeff; - - // Because we want a function from control point displacements, not positions. - f.b += m_pSpline->controlPointPosition(cp_idx).x() * coeff.coeff; - } - m_constraints.push_back(f); - - // Fix the y coordinate. - f.a.fill(0); - f.b = -pos.y(); - for (const FittableSpline::LinearCoefficient& coeff : coeffs) { - const int cp_idx = coeff.controlPointIdx; - f.a[cp_idx * 2 + 1] = coeff.coeff; - // Because we want a function from control point displacements, not positions. - f.b += m_pSpline->controlPointPosition(cp_idx).y() * coeff.coeff; - } - m_constraints.push_back(f); + std::vector coeffs; + m_spline->linearCombinationAt(t, coeffs); + + // Fix the x coordinate. + LinearFunction f(m_spline->numControlPoints() * 2); + f.b = -pos.x(); + for (const FittableSpline::LinearCoefficient& coeff : coeffs) { + const int cp_idx = coeff.controlPointIdx; + f.a[cp_idx * 2] = coeff.coeff; + + // Because we want a function from control point displacements, not positions. + f.b += m_spline->controlPointPosition(cp_idx).x() * coeff.coeff; + } + m_constraints.push_back(f); + + // Fix the y coordinate. + f.a.fill(0); + f.b = -pos.y(); + for (const FittableSpline::LinearCoefficient& coeff : coeffs) { + const int cp_idx = coeff.controlPointIdx; + f.a[cp_idx * 2 + 1] = coeff.coeff; + // Because we want a function from control point displacements, not positions. + f.b += m_spline->controlPointPosition(cp_idx).y() * coeff.coeff; + } + m_constraints.push_back(f); } void ConstraintSet::constrainControlPoint(int cp_idx, const QLineF& line) { - assert(cp_idx >= 0 && cp_idx < m_pSpline->numControlPoints()); + assert(cp_idx >= 0 && cp_idx < m_spline->numControlPoints()); - if (line.p1() == line.p2()) { - constrainControlPoint(cp_idx, line.p1()); + if (line.p1() == line.p2()) { + constrainControlPoint(cp_idx, line.p1()); - return; - } + return; + } - const double dx = line.p2().x() - line.p1().x(); - const double dy = line.p2().y() - line.p1().y(); + const double dx = line.p2().x() - line.p1().x(); + const double dy = line.p2().y() - line.p1().y(); - // Lx(cp) = p1.x + t * dx - // Ly(cp) = p1.y + t * dy - // Lx(cp) * dy = p1.x * dy + t * dx * dy - // Ly(cp) * dx = p1.y * dx + t * dx * dy - // Lx(cp) * dy - Ly(cp) * dx = p1.x * dy - p1.y * dx - // L(cp) = Lx(cp) * dy - Ly(cp) * dx - // L(cp) + (p1.y * dx - p1.x * dy) = 0 + // Lx(cp) = p1.x + t * dx + // Ly(cp) = p1.y + t * dy + // Lx(cp) * dy = p1.x * dy + t * dx * dy + // Ly(cp) * dx = p1.y * dx + t * dx * dy + // Lx(cp) * dy - Ly(cp) * dx = p1.x * dy - p1.y * dx + // L(cp) = Lx(cp) * dy - Ly(cp) * dx + // L(cp) + (p1.y * dx - p1.x * dy) = 0 - LinearFunction f(m_pSpline->numControlPoints() * 2); - f.a[cp_idx * 2] = dy; - f.a[cp_idx * 2 + 1] = -dx; - f.b = line.p1().y() * dx - line.p1().x() * dy; + LinearFunction f(m_spline->numControlPoints() * 2); + f.a[cp_idx * 2] = dy; + f.a[cp_idx * 2 + 1] = -dx; + f.b = line.p1().y() * dx - line.p1().x() * dy; - // Make it a function of control point displacements, not control points themselves. - const QPointF cp(m_pSpline->controlPointPosition(cp_idx)); - f.b += cp.x() * dy; - f.b += cp.y() * dx; + // Make it a function of control point displacements, not control points themselves. + const QPointF cp(m_spline->controlPointPosition(cp_idx)); + f.b += cp.x() * dy; + f.b += cp.y() * dx; - m_constraints.push_back(f); + m_constraints.push_back(f); } // ConstraintSet::constrainControlPoint void ConstraintSet::constrainSplinePoint(double t, const QLineF& line) { - if (line.p1() == line.p2()) { - constrainSplinePoint(t, line.p1()); - - return; - } - - std::vector coeffs; - m_pSpline->linearCombinationAt(t, coeffs); - - const double dx = line.p2().x() - line.p1().x(); - const double dy = line.p2().y() - line.p1().y(); - - // Lx(cp) = p1.x + t * dx - // Ly(cp) = p1.y + t * dy - // Lx(cp) * dy = p1.x * dy + t * dx * dy - // Ly(cp) * dx = p1.y * dx + t * dx * dy - // Lx(cp) * dy - Ly(cp) * dx = p1.x * dy - p1.y * dx - // L(cp) = Lx(cp) * dy - Ly(cp) * dx - // L(cp) + (p1.y * dx - p1.x * dy) = 0 - - LinearFunction f(m_pSpline->numControlPoints() * 2); - f.b = line.p1().y() * dx - line.p1().x() * dy; - for (const FittableSpline::LinearCoefficient& coeff : coeffs) { - f.a[coeff.controlPointIdx * 2] = coeff.coeff * dy; - f.a[coeff.controlPointIdx * 2 + 1] = -coeff.coeff * dx; - - // Because we want a function from control point displacements, not positions. - const QPointF cp(m_pSpline->controlPointPosition(coeff.controlPointIdx)); - f.b += cp.x() * coeff.coeff * dy - cp.y() * coeff.coeff * dx; - } - m_constraints.push_back(f); + if (line.p1() == line.p2()) { + constrainSplinePoint(t, line.p1()); + + return; + } + + std::vector coeffs; + m_spline->linearCombinationAt(t, coeffs); + + const double dx = line.p2().x() - line.p1().x(); + const double dy = line.p2().y() - line.p1().y(); + + // Lx(cp) = p1.x + t * dx + // Ly(cp) = p1.y + t * dy + // Lx(cp) * dy = p1.x * dy + t * dx * dy + // Ly(cp) * dx = p1.y * dx + t * dx * dy + // Lx(cp) * dy - Ly(cp) * dx = p1.x * dy - p1.y * dx + // L(cp) = Lx(cp) * dy - Ly(cp) * dx + // L(cp) + (p1.y * dx - p1.x * dy) = 0 + + LinearFunction f(m_spline->numControlPoints() * 2); + f.b = line.p1().y() * dx - line.p1().x() * dy; + for (const FittableSpline::LinearCoefficient& coeff : coeffs) { + f.a[coeff.controlPointIdx * 2] = coeff.coeff * dy; + f.a[coeff.controlPointIdx * 2 + 1] = -coeff.coeff * dx; + + // Because we want a function from control point displacements, not positions. + const QPointF cp(m_spline->controlPointPosition(coeff.controlPointIdx)); + f.b += cp.x() * coeff.coeff * dy - cp.y() * coeff.coeff * dx; + } + m_constraints.push_back(f); } // ConstraintSet::constrainSplinePoint } // namespace spfit \ No newline at end of file diff --git a/math/spfit/ConstraintSet.h b/math/spfit/ConstraintSet.h index bec79c9b9..1116056a6 100644 --- a/math/spfit/ConstraintSet.h +++ b/math/spfit/ConstraintSet.h @@ -19,35 +19,33 @@ #ifndef SPFIT_CONSTRAINT_SET_H_ #define SPFIT_CONSTRAINT_SET_H_ -#include "LinearFunction.h" -#include #include -#include +#include #include +#include +#include "LinearFunction.h" namespace spfit { class FittableSpline; class ConstraintSet { - // Member-wise copying is OK. -public: - explicit ConstraintSet(const FittableSpline* spline); + // Member-wise copying is OK. + public: + explicit ConstraintSet(const FittableSpline* spline); - const std::list& constraints() const { - return m_constraints; - } + const std::list& constraints() const { return m_constraints; } - void constrainControlPoint(int cp_idx, const QPointF& pos); + void constrainControlPoint(int cp_idx, const QPointF& pos); - void constrainControlPoint(int cp_idx, const QLineF& line); + void constrainControlPoint(int cp_idx, const QLineF& line); - void constrainSplinePoint(double t, const QPointF& pos); + void constrainSplinePoint(double t, const QPointF& pos); - void constrainSplinePoint(double t, const QLineF& line); + void constrainSplinePoint(double t, const QLineF& line); -private: - const FittableSpline* m_pSpline; - std::list m_constraints; + private: + const FittableSpline* m_spline; + std::list m_constraints; }; } // namespace spfit #endif // ifndef SPFIT_CONSTRAINT_SET_H_ diff --git a/math/spfit/FittableSpline.h b/math/spfit/FittableSpline.h index d890a200d..9fbfe7022 100644 --- a/math/spfit/FittableSpline.h +++ b/math/spfit/FittableSpline.h @@ -19,98 +19,95 @@ #ifndef SPFIT_FITTABLE_SPLINE_H_ #define SPFIT_FITTABLE_SPLINE_H_ -#include "VirtualFunction.h" -#include "NumericTraits.h" -#include "FlagOps.h" #include #include +#include "FlagOps.h" +#include "NumericTraits.h" +#include "VirtualFunction.h" namespace spfit { /** * \brief Implementing this interface allows a spline to be fitted to a polyline. */ class FittableSpline { -public: - enum SampleFlags { - DEFAULT_SAMPLE = 0, - HEAD_SAMPLE = 1 << 0, /**< Start point of an open spline. */ - TAIL_SAMPLE = 1 << 1, /**< End point of an open spline. */ - JUNCTION_SAMPLE = 1 << 2 /**< Point on the boundary of two segments. */ - }; - - /** - * For a spline to be fittable, any point on a spline must be representable - * as a linear combination of spline's control points. The linear coefficients - * will of course depend on parameter t, and this dependency doesn't have to be - * linear. - * - * This class represents a single linear coefficient assiciated with - * a particular control point. - */ - struct LinearCoefficient { - double coeff; - int controlPointIdx; - - LinearCoefficient() : coeff(0), controlPointIdx(-1) { - } - - LinearCoefficient(int cp_idx, double cf) : coeff(cf), controlPointIdx(cp_idx) { - } - }; - - struct SamplingParams { - /** - * The maximum distance from any point on the polyline that's the - * result of sampling to the spline. - */ - double maxDistFromSpline; - - /** - * The maximum distance between two adjacent samples. - */ - double maxDistBetweenSamples; - - explicit SamplingParams(double max_dist_from_spline = 0.2, - double max_dist_between_samples = NumericTraits::max()) - : maxDistFromSpline(max_dist_from_spline), maxDistBetweenSamples(max_dist_between_samples) { - } - }; - - virtual ~FittableSpline() = default; - - virtual int numControlPoints() const = 0; - - virtual QPointF controlPointPosition(int idx) const = 0; - - virtual void moveControlPoint(int idx, const QPointF& pos) = 0; - + public: + enum SampleFlags { + DEFAULT_SAMPLE = 0, + HEAD_SAMPLE = 1 << 0, /**< Start point of an open spline. */ + TAIL_SAMPLE = 1 << 1, /**< End point of an open spline. */ + JUNCTION_SAMPLE = 1 << 2 /**< Point on the boundary of two segments. */ + }; + + /** + * For a spline to be fittable, any point on a spline must be representable + * as a linear combination of spline's control points. The linear coefficients + * will of course depend on parameter t, and this dependency doesn't have to be + * linear. + * + * This class represents a single linear coefficient assiciated with + * a particular control point. + */ + struct LinearCoefficient { + double coeff; + int controlPointIdx; + + LinearCoefficient() : coeff(0), controlPointIdx(-1) {} + + LinearCoefficient(int cp_idx, double cf) : coeff(cf), controlPointIdx(cp_idx) {} + }; + + struct SamplingParams { /** - * \brief For a given t, calculates a linear combination of control points that result - * in a point on the spline corresponding to the given t. - * - * \param t Position on the spline. The range of t is [0, 1]. - * \param coeffs The vector to write linear coefficients into. Existing contents - * (if any) will be discarded. Implementations must make sure that at most - * one coefficient is being produced for each control point. + * The maximum distance from any point on the polyline that's the + * result of sampling to the spline. */ - virtual void linearCombinationAt(double t, std::vector& coeffs) const = 0; + double maxDistFromSpline; /** - * \brief Generates an ordered set of points on a spline. - * - * \p sink will be called with the following arguments: - * -# Point on the spline. - * -# t value corresponding to that point. - * -# SampleFlags for the point. - * - * \note No matter the values of from_t and to_t, samples - * corresponding to them will be marked with HEAD_SAMPLE - * and TAIL_SAMPLE respectably. + * The maximum distance between two adjacent samples. */ - virtual void sample(const VirtualFunction& sink, - const SamplingParams& params, - double from_t = 0.0, - double to_t = 1.0) const = 0; + double maxDistBetweenSamples; + + explicit SamplingParams(double max_dist_from_spline = 0.2, + double max_dist_between_samples = NumericTraits::max()) + : maxDistFromSpline(max_dist_from_spline), maxDistBetweenSamples(max_dist_between_samples) {} + }; + + virtual ~FittableSpline() = default; + + virtual int numControlPoints() const = 0; + + virtual QPointF controlPointPosition(int idx) const = 0; + + virtual void moveControlPoint(int idx, const QPointF& pos) = 0; + + /** + * \brief For a given t, calculates a linear combination of control points that result + * in a point on the spline corresponding to the given t. + * + * \param t Position on the spline. The range of t is [0, 1]. + * \param coeffs The vector to write linear coefficients into. Existing contents + * (if any) will be discarded. Implementations must make sure that at most + * one coefficient is being produced for each control point. + */ + virtual void linearCombinationAt(double t, std::vector& coeffs) const = 0; + + /** + * \brief Generates an ordered set of points on a spline. + * + * \p sink will be called with the following arguments: + * -# Point on the spline. + * -# t value corresponding to that point. + * -# SampleFlags for the point. + * + * \note No matter the values of from_t and to_t, samples + * corresponding to them will be marked with HEAD_SAMPLE + * and TAIL_SAMPLE respectably. + */ + virtual void sample(const VirtualFunction& sink, + const SamplingParams& params, + double from_t = 0.0, + double to_t = 1.0) const = 0; }; diff --git a/math/spfit/FrenetFrame.cpp b/math/spfit/FrenetFrame.cpp index d983db4bc..fd5efad9a 100644 --- a/math/spfit/FrenetFrame.cpp +++ b/math/spfit/FrenetFrame.cpp @@ -16,21 +16,21 @@ along with this program. If not, see . */ -#include #include "FrenetFrame.h" +#include namespace spfit { FrenetFrame::FrenetFrame(const Vec2d& origin, const Vec2d& tangent_vector, YAxisDirection ydir) : m_origin(origin) { - const double sqlen = tangent_vector.squaredNorm(); - if (sqlen > 1e-6) { - m_unitTangent = tangent_vector / std::sqrt(sqlen); - if (ydir == Y_POINTS_UP) { - m_unitNormal[0] = -m_unitTangent[1]; - m_unitNormal[1] = m_unitTangent[0]; - } else { - m_unitNormal[0] = m_unitTangent[1]; - m_unitNormal[1] = -m_unitTangent[0]; - } + const double sqlen = tangent_vector.squaredNorm(); + if (sqlen > 1e-6) { + m_unitTangent = tangent_vector / std::sqrt(sqlen); + if (ydir == Y_POINTS_UP) { + m_unitNormal[0] = -m_unitTangent[1]; + m_unitNormal[1] = m_unitTangent[0]; + } else { + m_unitNormal[0] = m_unitTangent[1]; + m_unitNormal[1] = -m_unitTangent[0]; } + } } } // namespace spfit diff --git a/math/spfit/FrenetFrame.h b/math/spfit/FrenetFrame.h index b5e4b42fe..6e6a25402 100644 --- a/math/spfit/FrenetFrame.h +++ b/math/spfit/FrenetFrame.h @@ -26,39 +26,33 @@ namespace spfit { * Origin + unit tangent + unit normal vectors. */ class FrenetFrame { - // Member-wise copying is OK. -public: - enum YAxisDirection { Y_POINTS_UP, Y_POINTS_DOWN }; - - /** - * \brief Builds a Frenet frame from an origin and a (non-unit) tangent vector. - * - * The direction of the normal vector is choosen according to \p ydir, - * considering the tangent vector to be pointing to the right. The normal direction - * does matter, as we want the unit normal vector divided by signed curvature give - * us the center of the curvature. For that to be the case, normal vector's direction - * relative to the unit vector's direction must be the same as the Y axis direction - * relative to the X axis direction in the coordinate system from which we derive - * the curvature. For 2D computer graphics, the right direction is Y_POINTS_DOWN. - */ - FrenetFrame(const Vec2d& origin, const Vec2d& tangent_vector, YAxisDirection ydir = Y_POINTS_DOWN); - - const Vec2d& origin() const { - return m_origin; - } - - const Vec2d& unitTangent() const { - return m_unitTangent; - } - - const Vec2d& unitNormal() const { - return m_unitNormal; - } - -private: - Vec2d m_origin; - Vec2d m_unitTangent; - Vec2d m_unitNormal; + // Member-wise copying is OK. + public: + enum YAxisDirection { Y_POINTS_UP, Y_POINTS_DOWN }; + + /** + * \brief Builds a Frenet frame from an origin and a (non-unit) tangent vector. + * + * The direction of the normal vector is choosen according to \p ydir, + * considering the tangent vector to be pointing to the right. The normal direction + * does matter, as we want the unit normal vector divided by signed curvature give + * us the center of the curvature. For that to be the case, normal vector's direction + * relative to the unit vector's direction must be the same as the Y axis direction + * relative to the X axis direction in the coordinate system from which we derive + * the curvature. For 2D computer graphics, the right direction is Y_POINTS_DOWN. + */ + FrenetFrame(const Vec2d& origin, const Vec2d& tangent_vector, YAxisDirection ydir = Y_POINTS_DOWN); + + const Vec2d& origin() const { return m_origin; } + + const Vec2d& unitTangent() const { return m_unitTangent; } + + const Vec2d& unitNormal() const { return m_unitNormal; } + + private: + Vec2d m_origin; + Vec2d m_unitTangent; + Vec2d m_unitNormal; }; } // namespace spfit #endif // ifndef SPFIT_FRENET_FRAME_H_ diff --git a/math/spfit/LinearForceBalancer.cpp b/math/spfit/LinearForceBalancer.cpp index f3e5b6376..1d19c9153 100644 --- a/math/spfit/LinearForceBalancer.cpp +++ b/math/spfit/LinearForceBalancer.cpp @@ -21,51 +21,50 @@ namespace spfit { LinearForceBalancer::LinearForceBalancer(double internal_external_ratio) - : m_currentRatio(internal_external_ratio), - m_targetRatio(internal_external_ratio), - m_rateOfChange(0), - m_iterationsToTarget(0) { -} + : m_currentRatio(internal_external_ratio), + m_targetRatio(internal_external_ratio), + m_rateOfChange(0), + m_iterationsToTarget(0) {} void LinearForceBalancer::setCurrentRatio(double internal_external_ratio) { - m_currentRatio = internal_external_ratio; - recalcRateOfChange(); + m_currentRatio = internal_external_ratio; + recalcRateOfChange(); } void LinearForceBalancer::setTargetRatio(double internal_external_ratio) { - m_targetRatio = internal_external_ratio; - recalcRateOfChange(); + m_targetRatio = internal_external_ratio; + recalcRateOfChange(); } void LinearForceBalancer::setIterationsToTarget(int iterations) { - m_iterationsToTarget = iterations; - recalcRateOfChange(); + m_iterationsToTarget = iterations; + recalcRateOfChange(); } double LinearForceBalancer::calcInternalForceWeight(double internal_force, double external_force) const { - // (internal * lambda) / external = ratio - // internal * lambda = external * ratio - double lambda = 0; - if (std::fabs(internal_force) > 1e-6) { - lambda = m_currentRatio * external_force / internal_force; - } + // (internal * lambda) / external = ratio + // internal * lambda = external * ratio + double lambda = 0; + if (std::fabs(internal_force) > 1e-6) { + lambda = m_currentRatio * external_force / internal_force; + } - return lambda; + return lambda; } void LinearForceBalancer::nextIteration() { - if (m_iterationsToTarget > 0) { - --m_iterationsToTarget; - m_currentRatio += m_rateOfChange; - } + if (m_iterationsToTarget > 0) { + --m_iterationsToTarget; + m_currentRatio += m_rateOfChange; + } } void LinearForceBalancer::recalcRateOfChange() { - if (m_iterationsToTarget <= 0) { - // Already there. - m_rateOfChange = 0; - } else { - m_rateOfChange = (m_targetRatio - m_currentRatio) / m_iterationsToTarget; - } + if (m_iterationsToTarget <= 0) { + // Already there. + m_rateOfChange = 0; + } else { + m_rateOfChange = (m_targetRatio - m_currentRatio) / m_iterationsToTarget; + } } } // namespace spfit \ No newline at end of file diff --git a/math/spfit/LinearForceBalancer.h b/math/spfit/LinearForceBalancer.h index a47b18617..7e7deab70 100644 --- a/math/spfit/LinearForceBalancer.h +++ b/math/spfit/LinearForceBalancer.h @@ -27,48 +27,44 @@ namespace spfit { * over time. */ class LinearForceBalancer { - // Member-wise copying is OK. -public: - /** - * Sets both the current and the target ratio, so that it doesn't change over time. - */ - explicit LinearForceBalancer(double internal_external_ratio); + // Member-wise copying is OK. + public: + /** + * Sets both the current and the target ratio, so that it doesn't change over time. + */ + explicit LinearForceBalancer(double internal_external_ratio); - double currentRatio() const { - return m_currentRatio; - } + double currentRatio() const { return m_currentRatio; } - void setCurrentRatio(double internal_external_ratio); + void setCurrentRatio(double internal_external_ratio); - double targetRatio() const { - return m_targetRatio; - } + double targetRatio() const { return m_targetRatio; } - void setTargetRatio(double internal_external_ratio); + void setTargetRatio(double internal_external_ratio); - /** - * Sets the number of iterations after which the internal / external force - * ratio reaches its target value. This method doesn't change the - * current ratio. - */ - void setIterationsToTarget(int iterations); + /** + * Sets the number of iterations after which the internal / external force + * ratio reaches its target value. This method doesn't change the + * current ratio. + */ + void setIterationsToTarget(int iterations); - double calcInternalForceWeight(double internal_force, double external_force) const; + double calcInternalForceWeight(double internal_force, double external_force) const; - /** - * Returns the current internal / external force ratio, then moves - * it towards its target value. After it reaches its target value, - * further nextIteration() calls will keep returning the same value. - */ - void nextIteration(); + /** + * Returns the current internal / external force ratio, then moves + * it towards its target value. After it reaches its target value, + * further nextIteration() calls will keep returning the same value. + */ + void nextIteration(); -private: - void recalcRateOfChange(); + private: + void recalcRateOfChange(); - double m_currentRatio; - double m_targetRatio; - double m_rateOfChange; - int m_iterationsToTarget; + double m_currentRatio; + double m_targetRatio; + double m_rateOfChange; + int m_iterationsToTarget; }; } // namespace spfit #endif // ifndef SPFIT_LINEAR_FORCE_BALANCER_H_ diff --git a/math/spfit/ModelShape.h b/math/spfit/ModelShape.h index d33f2ecf6..44c5e80ea 100644 --- a/math/spfit/ModelShape.h +++ b/math/spfit/ModelShape.h @@ -19,9 +19,9 @@ #ifndef SPFIT_MODEL_SHAPE_H_ #define SPFIT_MODEL_SHAPE_H_ -#include "SqDistApproximant.h" -#include "FittableSpline.h" #include +#include "FittableSpline.h" +#include "SqDistApproximant.h" namespace spfit { /** @@ -30,14 +30,14 @@ namespace spfit { * Could be a polyline or maybe a point cloud. */ class ModelShape { -public: - virtual ~ModelShape() = default; + public: + virtual ~ModelShape() = default; - /** - * Returns a function that approximates the squared distance to the model. - * The function is only accurate in the neighbourhood of \p pt. - */ - virtual SqDistApproximant localSqDistApproximant(const QPointF& pt, FittableSpline::SampleFlags flags) const = 0; + /** + * Returns a function that approximates the squared distance to the model. + * The function is only accurate in the neighbourhood of \p pt. + */ + virtual SqDistApproximant localSqDistApproximant(const QPointF& pt, FittableSpline::SampleFlags flags) const = 0; }; } // namespace spfit #endif diff --git a/math/spfit/OptimizationResult.cpp b/math/spfit/OptimizationResult.cpp index a9be8eeb1..671b3287e 100644 --- a/math/spfit/OptimizationResult.cpp +++ b/math/spfit/OptimizationResult.cpp @@ -21,15 +21,15 @@ namespace spfit { OptimizationResult::OptimizationResult(double force_before, double force_after) - : m_forceBefore(std::max(force_before, 0)), m_forceAfter(std::max(force_after, 0)) { - // In theory, these distances can't be negative, but in practice they can. - // We are going to treat negative ones as they are zeros. + : m_forceBefore(std::max(force_before, 0)), m_forceAfter(std::max(force_after, 0)) { + // In theory, these distances can't be negative, but in practice they can. + // We are going to treat negative ones as they are zeros. } double OptimizationResult::improvementPercentage() const { - double improvement = m_forceBefore - m_forceAfter; - improvement /= (m_forceBefore + std::numeric_limits::epsilon()); + double improvement = m_forceBefore - m_forceAfter; + improvement /= (m_forceBefore + std::numeric_limits::epsilon()); - return improvement * 100; // Convert to percents. + return improvement * 100; // Convert to percents. } } // namespace spfit diff --git a/math/spfit/OptimizationResult.h b/math/spfit/OptimizationResult.h index 6244ed5d8..fc9f09242 100644 --- a/math/spfit/OptimizationResult.h +++ b/math/spfit/OptimizationResult.h @@ -23,31 +23,27 @@ namespace spfit { class OptimizationResult { -public: - OptimizationResult(double force_before, double force_after); - - double forceBefore() const { - return m_forceBefore; - } - - double forceAfter() const { - return m_forceAfter; - } - - /** - * \brief Returns force decrease in percents. - * - * Force decrease can theoretically be negative. - * - * \note Improvements from different optimization runs can't be compared, - * as the absolute force values depend on the number of samples, - * which varies from one optimization iteration to another. - */ - double improvementPercentage() const; - -private: - double m_forceBefore; - double m_forceAfter; + public: + OptimizationResult(double force_before, double force_after); + + double forceBefore() const { return m_forceBefore; } + + double forceAfter() const { return m_forceAfter; } + + /** + * \brief Returns force decrease in percents. + * + * Force decrease can theoretically be negative. + * + * \note Improvements from different optimization runs can't be compared, + * as the absolute force values depend on the number of samples, + * which varies from one optimization iteration to another. + */ + double improvementPercentage() const; + + private: + double m_forceBefore; + double m_forceAfter; }; } // namespace spfit #endif diff --git a/math/spfit/Optimizer.cpp b/math/spfit/Optimizer.cpp index 3b4f5865f..de98b7b4f 100644 --- a/math/spfit/Optimizer.cpp +++ b/math/spfit/Optimizer.cpp @@ -17,126 +17,125 @@ */ #include "Optimizer.h" -#include "MatrixCalc.h" #include +#include "MatrixCalc.h" namespace spfit { Optimizer::Optimizer(size_t num_vars) - : m_numVars(num_vars), - m_A(num_vars, num_vars), - m_b(num_vars), - m_x(num_vars), - m_externalForce(num_vars), - m_internalForce(num_vars) { -} + : m_numVars(num_vars), + m_A(num_vars, num_vars), + m_b(num_vars), + m_x(num_vars), + m_externalForce(num_vars), + m_internalForce(num_vars) {} void Optimizer::setConstraints(const std::list& constraints) { - const size_t num_constraints = constraints.size(); - const size_t num_dimensions = m_numVars + num_constraints; - - MatT A(num_dimensions, num_dimensions); - VecT b(num_dimensions); - // Matrix A and vector b will have the following layout: - // |N N N L L| |-D| - // |N N N L L| |-D| - // A = |N N N L L| b = |-D| - // |C C C 0 0| |-J| - // |C C C 0 0| |-J| - // N: non-constant part of the gradient of the function we are minimizing. - // C: non-constant part of constraint functions (one per line). - // L: coefficients of Lagrange multipliers. These happen to be equal - // to the symmetric C values. - // D: constant part of the gradient of the function we are optimizing. - // J: constant part of constraint functions. - - auto ctr(constraints.begin()); - for (size_t i = m_numVars; i < num_dimensions; ++i, ++ctr) { - b[i] = -ctr->b; - for (size_t j = 0; j < m_numVars; ++j) { - A(i, j) = A(j, i) = ctr->a[j]; - } + const size_t num_constraints = constraints.size(); + const size_t num_dimensions = m_numVars + num_constraints; + + MatT A(num_dimensions, num_dimensions); + VecT b(num_dimensions); + // Matrix A and vector b will have the following layout: + // |N N N L L| |-D| + // |N N N L L| |-D| + // A = |N N N L L| b = |-D| + // |C C C 0 0| |-J| + // |C C C 0 0| |-J| + // N: non-constant part of the gradient of the function we are minimizing. + // C: non-constant part of constraint functions (one per line). + // L: coefficients of Lagrange multipliers. These happen to be equal + // to the symmetric C values. + // D: constant part of the gradient of the function we are optimizing. + // J: constant part of constraint functions. + + auto ctr(constraints.begin()); + for (size_t i = m_numVars; i < num_dimensions; ++i, ++ctr) { + b[i] = -ctr->b; + for (size_t j = 0; j < m_numVars; ++j) { + A(i, j) = A(j, i) = ctr->a[j]; } + } - VecT(num_dimensions).swap(m_x); - m_A.swap(A); - m_b.swap(b); + VecT(num_dimensions).swap(m_x); + m_A.swap(A); + m_b.swap(b); } // Optimizer::setConstraints void Optimizer::addExternalForce(const QuadraticFunction& force) { - m_externalForce += force; + m_externalForce += force; } void Optimizer::addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map) { - const size_t num_vars = force.numVars(); - for (size_t i = 0; i < num_vars; ++i) { - const int ii = sparse_map[i]; - for (size_t j = 0; j < num_vars; ++j) { - const int jj = sparse_map[j]; - m_externalForce.A(ii, jj) += force.A(i, j); - } - m_externalForce.b[ii] += force.b[i]; + const size_t num_vars = force.numVars(); + for (size_t i = 0; i < num_vars; ++i) { + const int ii = sparse_map[i]; + for (size_t j = 0; j < num_vars; ++j) { + const int jj = sparse_map[j]; + m_externalForce.A(ii, jj) += force.A(i, j); } - m_externalForce.c += force.c; + m_externalForce.b[ii] += force.b[i]; + } + m_externalForce.c += force.c; } void Optimizer::addInternalForce(const QuadraticFunction& force) { - m_internalForce += force; + m_internalForce += force; } void Optimizer::addInternalForce(const QuadraticFunction& force, const std::vector& sparse_map) { - const size_t num_vars = force.numVars(); - for (size_t i = 0; i < num_vars; ++i) { - const int ii = sparse_map[i]; - for (size_t j = 0; j < num_vars; ++j) { - const int jj = sparse_map[j]; - m_internalForce.A(ii, jj) += force.A(i, j); - } - m_internalForce.b[ii] += force.b[i]; + const size_t num_vars = force.numVars(); + for (size_t i = 0; i < num_vars; ++i) { + const int ii = sparse_map[i]; + for (size_t j = 0; j < num_vars; ++j) { + const int jj = sparse_map[j]; + m_internalForce.A(ii, jj) += force.A(i, j); } - m_internalForce.c += force.c; + m_internalForce.b[ii] += force.b[i]; + } + m_internalForce.c += force.c; } OptimizationResult Optimizer::optimize(double internal_force_weight) { - // Note: because we are supposed to reset the forces anyway, - // we re-use m_internalForce to store the cummulative force. - m_internalForce *= internal_force_weight; - m_internalForce += m_externalForce; - - // For the layout of m_A and m_b, see setConstraints() - const QuadraticFunction::Gradient grad(m_internalForce.gradient()); - for (size_t i = 0; i < m_numVars; ++i) { - m_b[i] = -grad.b[i]; - for (size_t j = 0; j < m_numVars; ++j) { - m_A(i, j) = grad.A(i, j); - } + // Note: because we are supposed to reset the forces anyway, + // we re-use m_internalForce to store the cummulative force. + m_internalForce *= internal_force_weight; + m_internalForce += m_externalForce; + + // For the layout of m_A and m_b, see setConstraints() + const QuadraticFunction::Gradient grad(m_internalForce.gradient()); + for (size_t i = 0; i < m_numVars; ++i) { + m_b[i] = -grad.b[i]; + for (size_t j = 0; j < m_numVars; ++j) { + m_A(i, j) = grad.A(i, j); } + } - const double total_force_before = m_internalForce.c; - DynamicMatrixCalc mc; + const double total_force_before = m_internalForce.c; + DynamicMatrixCalc mc; - try { - mc(m_A).solve(mc(m_b)).write(m_x.data()); - } catch (const std::runtime_error&) { - m_externalForce.reset(); - m_internalForce.reset(); - m_x.fill(0); // To make undoLastStep() work as expected. + try { + mc(m_A).solve(mc(m_b)).write(m_x.data()); + } catch (const std::runtime_error&) { + m_externalForce.reset(); + m_internalForce.reset(); + m_x.fill(0); // To make undoLastStep() work as expected. - return OptimizationResult(total_force_before, total_force_before); - } + return OptimizationResult(total_force_before, total_force_before); + } - const double total_force_after = m_internalForce.evaluate(m_x.data()); - m_externalForce.reset(); // Now it's finally safe to reset these. - m_internalForce.reset(); - // The last thing remaining is to adjust constraints, - // as they depend on the current variables. - adjustConstraints(1.0); + const double total_force_after = m_internalForce.evaluate(m_x.data()); + m_externalForce.reset(); // Now it's finally safe to reset these. + m_internalForce.reset(); + // The last thing remaining is to adjust constraints, + // as they depend on the current variables. + adjustConstraints(1.0); - return OptimizationResult(total_force_before, total_force_after); + return OptimizationResult(total_force_before, total_force_after); } // Optimizer::optimize void Optimizer::undoLastStep() { - adjustConstraints(-1.0); - m_x.fill(0); + adjustConstraints(-1.0); + m_x.fill(0); } /** @@ -144,24 +143,24 @@ void Optimizer::undoLastStep() { * direction == -1 is used for undoing the last step. */ void Optimizer::adjustConstraints(double direction) { - const size_t num_dimensions = m_b.size(); - for (size_t i = m_numVars; i < num_dimensions; ++i) { - // See setConstraints() for more information - // on the layout of m_A and m_b. - double c = 0; - for (size_t j = 0; j < m_numVars; ++j) { - c += m_A(i, j) * m_x[j]; - } - m_b[i] -= c * direction; + const size_t num_dimensions = m_b.size(); + for (size_t i = m_numVars; i < num_dimensions; ++i) { + // See setConstraints() for more information + // on the layout of m_A and m_b. + double c = 0; + for (size_t j = 0; j < m_numVars; ++j) { + c += m_A(i, j) * m_x[j]; } + m_b[i] -= c * direction; + } } void Optimizer::swap(Optimizer& other) { - m_A.swap(other.m_A); - m_b.swap(other.m_b); - m_x.swap(other.m_x); - m_externalForce.swap(other.m_externalForce); - m_internalForce.swap(other.m_internalForce); - std::swap(m_numVars, other.m_numVars); + m_A.swap(other.m_A); + m_b.swap(other.m_b); + m_x.swap(other.m_x); + m_externalForce.swap(other.m_externalForce); + m_internalForce.swap(other.m_internalForce); + std::swap(m_numVars, other.m_numVars); } } // namespace spfit \ No newline at end of file diff --git a/math/spfit/Optimizer.h b/math/spfit/Optimizer.h index b678e9804..c4e728b89 100644 --- a/math/spfit/Optimizer.h +++ b/math/spfit/Optimizer.h @@ -19,90 +19,82 @@ #ifndef SPFIT_OPTIMIZER_H_ #define SPFIT_OPTIMIZER_H_ -#include "OptimizationResult.h" +#include +#include #include "FittableSpline.h" +#include "LinearFunction.h" +#include "MatT.h" +#include "OptimizationResult.h" +#include "QuadraticFunction.h" #include "SqDistApproximant.h" -#include "VirtualFunction.h" #include "VecNT.h" -#include "MatT.h" #include "VecT.h" -#include "LinearFunction.h" -#include "QuadraticFunction.h" -#include -#include +#include "VirtualFunction.h" namespace spfit { class Optimizer { - // Member-wise copying is OK. -public: - explicit Optimizer(size_t num_vars = 0); - - /** - * Sets linear constraints in the form of b^T * x + c = 0 - * Note that x in the above formula is not a vector of coordinates - * but a vector of their displacements. That is, the constraints - * to be passed here depend on the current positions of control points. - * That doesn't mean you have to provide updated constraints - * on very iteration though, as optimize() will update them for you. - */ - void setConstraints(const std::list& constraints); - - void addExternalForce(const QuadraticFunction& force); - - void addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map); - - void addInternalForce(const QuadraticFunction& force); - - void addInternalForce(const QuadraticFunction& force, const std::vector& sparse_map); - - size_t numVars() const { - return m_numVars; - } - - /** - * Get the external force accumulated from calls to addAttractionForce(). - * Note that optimize() will reset all forces. - */ - double externalForce() const { - return m_externalForce.c; - } - - /** - * Get the internal force accumulated from calls to addInternalForce(). - * Note that optimize() will reset all forces. - */ - double internalForce() const { - return m_internalForce.c; - } - - OptimizationResult optimize(double internal_external_ratio); - - const double* displacementVector() const { - return m_x.data(); - } - - /** - * Rolls back the very last adjustment to constraints done by optimize() - * and sets the displacement vector to all zeros. - */ - void undoLastStep(); - - void swap(Optimizer& other); - -private: - void adjustConstraints(double direction); - - size_t m_numVars; - MatT m_A; - VecT m_b; - VecT m_x; - QuadraticFunction m_externalForce; - QuadraticFunction m_internalForce; + // Member-wise copying is OK. + public: + explicit Optimizer(size_t num_vars = 0); + + /** + * Sets linear constraints in the form of b^T * x + c = 0 + * Note that x in the above formula is not a vector of coordinates + * but a vector of their displacements. That is, the constraints + * to be passed here depend on the current positions of control points. + * That doesn't mean you have to provide updated constraints + * on very iteration though, as optimize() will update them for you. + */ + void setConstraints(const std::list& constraints); + + void addExternalForce(const QuadraticFunction& force); + + void addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map); + + void addInternalForce(const QuadraticFunction& force); + + void addInternalForce(const QuadraticFunction& force, const std::vector& sparse_map); + + size_t numVars() const { return m_numVars; } + + /** + * Get the external force accumulated from calls to addAttractionForce(). + * Note that optimize() will reset all forces. + */ + double externalForce() const { return m_externalForce.c; } + + /** + * Get the internal force accumulated from calls to addInternalForce(). + * Note that optimize() will reset all forces. + */ + double internalForce() const { return m_internalForce.c; } + + OptimizationResult optimize(double internal_external_ratio); + + const double* displacementVector() const { return m_x.data(); } + + /** + * Rolls back the very last adjustment to constraints done by optimize() + * and sets the displacement vector to all zeros. + */ + void undoLastStep(); + + void swap(Optimizer& other); + + private: + void adjustConstraints(double direction); + + size_t m_numVars; + MatT m_A; + VecT m_b; + VecT m_x; + QuadraticFunction m_externalForce; + QuadraticFunction m_internalForce; }; inline void swap(Optimizer& o1, Optimizer& o2) { - o1.swap(o2); + o1.swap(o2); } } // namespace spfit #endif // ifndef SPFIT_OPTIMIZER_H_ diff --git a/math/spfit/PolylineModelShape.cpp b/math/spfit/PolylineModelShape.cpp index c4ba8fb67..7c36d5b64 100644 --- a/math/spfit/PolylineModelShape.cpp +++ b/math/spfit/PolylineModelShape.cpp @@ -17,107 +17,107 @@ */ #include "PolylineModelShape.h" +#include #include "FrenetFrame.h" #include "ToLineProjector.h" -#include namespace spfit { PolylineModelShape::PolylineModelShape(const std::vector& polyline) { - if (polyline.size() <= 1) { - throw std::invalid_argument("PolylineModelShape: polyline must have at least 2 vertices"); - } - - // We build an interpolating X-spline with control points at the vertices - // of our polyline. We'll use it to calculate curvature at polyline vertices. - XSpline spline; - - for (const QPointF& pt : polyline) { - spline.appendControlPoint(pt, -1); - } - - const int num_control_points = spline.numControlPoints(); - const double scale = 1.0 / (num_control_points - 1); - for (int i = 0; i < num_control_points; ++i) { - m_vertices.push_back(spline.pointAndDtsAt(i * scale)); - } + if (polyline.size() <= 1) { + throw std::invalid_argument("PolylineModelShape: polyline must have at least 2 vertices"); + } + + // We build an interpolating X-spline with control points at the vertices + // of our polyline. We'll use it to calculate curvature at polyline vertices. + XSpline spline; + + for (const QPointF& pt : polyline) { + spline.appendControlPoint(pt, -1); + } + + const int num_control_points = spline.numControlPoints(); + const double scale = 1.0 / (num_control_points - 1); + for (int i = 0; i < num_control_points; ++i) { + m_vertices.push_back(spline.pointAndDtsAt(i * scale)); + } } SqDistApproximant PolylineModelShape::localSqDistApproximant(const QPointF& pt, FittableSpline::SampleFlags sample_flags) const { - if (m_vertices.empty()) { - return SqDistApproximant(); - } - - // First, find the point on the polyline closest to pt. - QPointF best_foot_point; - double best_sqdist = NumericTraits::max(); - double segment_t = -1; - int segment_idx = -1; // If best_foot_point is on a segment, its index goes here. - int vertex_idx = -1; // If best_foot_point is a vertex, its index goes here. - // Project pt to each segment. - const int num_segments = int(m_vertices.size()) - 1; - for (int i = 0; i < num_segments; ++i) { - const QPointF pt1(m_vertices[i].point); - const QPointF pt2(m_vertices[i + 1].point); - const QLineF segment(pt1, pt2); - const double s = ToLineProjector(segment).projectionScalar(pt); - if ((s > 0) && (s < 1)) { - const QPointF foot_point(segment.pointAt(s)); - const Vec2d vec(pt - foot_point); - const double sqdist = vec.squaredNorm(); - if (sqdist < best_sqdist) { - best_sqdist = sqdist; - best_foot_point = foot_point; - segment_idx = i; - segment_t = s; - vertex_idx = -1; - } - } + if (m_vertices.empty()) { + return SqDistApproximant(); + } + + // First, find the point on the polyline closest to pt. + QPointF best_foot_point; + double best_sqdist = NumericTraits::max(); + double segment_t = -1; + int segment_idx = -1; // If best_foot_point is on a segment, its index goes here. + int vertex_idx = -1; // If best_foot_point is a vertex, its index goes here. + // Project pt to each segment. + const int num_segments = int(m_vertices.size()) - 1; + for (int i = 0; i < num_segments; ++i) { + const QPointF pt1(m_vertices[i].point); + const QPointF pt2(m_vertices[i + 1].point); + const QLineF segment(pt1, pt2); + const double s = ToLineProjector(segment).projectionScalar(pt); + if ((s > 0) && (s < 1)) { + const QPointF foot_point(segment.pointAt(s)); + const Vec2d vec(pt - foot_point); + const double sqdist = vec.squaredNorm(); + if (sqdist < best_sqdist) { + best_sqdist = sqdist; + best_foot_point = foot_point; + segment_idx = i; + segment_t = s; + vertex_idx = -1; + } } - // Check if pt is closer to a vertex than to any segment. - const auto num_points = static_cast(m_vertices.size()); - for (int i = 0; i < num_points; ++i) { - const QPointF vtx(m_vertices[i].point); - const Vec2d vec(pt - vtx); - const double sqdist = vec.squaredNorm(); - if (sqdist < best_sqdist) { - best_sqdist = sqdist; - best_foot_point = vtx; - vertex_idx = i; - segment_idx = -1; - } + } + // Check if pt is closer to a vertex than to any segment. + const auto num_points = static_cast(m_vertices.size()); + for (int i = 0; i < num_points; ++i) { + const QPointF vtx(m_vertices[i].point); + const Vec2d vec(pt - vtx); + const double sqdist = vec.squaredNorm(); + if (sqdist < best_sqdist) { + best_sqdist = sqdist; + best_foot_point = vtx; + vertex_idx = i; + segment_idx = -1; } + } - if (segment_idx != -1) { - // The foot point is on a line segment. - assert(segment_t >= 0 && segment_t <= 1); - - const XSpline::PointAndDerivs& pd1 = m_vertices[segment_idx]; - const XSpline::PointAndDerivs& pd2 = m_vertices[segment_idx + 1]; - const FrenetFrame frenet_frame(best_foot_point, pd2.point - pd1.point); + if (segment_idx != -1) { + // The foot point is on a line segment. + assert(segment_t >= 0 && segment_t <= 1); - const double k1 = pd1.signedCurvature(); - const double k2 = pd2.signedCurvature(); - const double weighted_k = k1 + segment_t * (k2 - k1); + const XSpline::PointAndDerivs& pd1 = m_vertices[segment_idx]; + const XSpline::PointAndDerivs& pd2 = m_vertices[segment_idx + 1]; + const FrenetFrame frenet_frame(best_foot_point, pd2.point - pd1.point); - return calcApproximant(pt, sample_flags, DEFAULT_FLAGS, frenet_frame, weighted_k); - } else { - // The foot point is a vertex of the polyline. - assert(vertex_idx != -1); + const double k1 = pd1.signedCurvature(); + const double k2 = pd2.signedCurvature(); + const double weighted_k = k1 + segment_t * (k2 - k1); - const XSpline::PointAndDerivs& pd = m_vertices[vertex_idx]; - const FrenetFrame frenet_frame(best_foot_point, pd.firstDeriv); + return calcApproximant(pt, sample_flags, DEFAULT_FLAGS, frenet_frame, weighted_k); + } else { + // The foot point is a vertex of the polyline. + assert(vertex_idx != -1); - Flags polyline_flags = DEFAULT_FLAGS; - if (vertex_idx == 0) { - polyline_flags |= POLYLINE_FRONT; - } - if (vertex_idx == int(m_vertices.size()) - 1) { - polyline_flags |= POLYLINE_BACK; - } + const XSpline::PointAndDerivs& pd = m_vertices[vertex_idx]; + const FrenetFrame frenet_frame(best_foot_point, pd.firstDeriv); - return calcApproximant(pt, sample_flags, polyline_flags, frenet_frame, pd.signedCurvature()); + Flags polyline_flags = DEFAULT_FLAGS; + if (vertex_idx == 0) { + polyline_flags |= POLYLINE_FRONT; } + if (vertex_idx == int(m_vertices.size()) - 1) { + polyline_flags |= POLYLINE_BACK; + } + + return calcApproximant(pt, sample_flags, polyline_flags, frenet_frame, pd.signedCurvature()); + } } // PolylineModelShape::localSqDistApproximant SqDistApproximant PolylineModelShape::calcApproximant(const QPointF& pt, @@ -125,10 +125,10 @@ SqDistApproximant PolylineModelShape::calcApproximant(const QPointF& pt, const Flags polyline_flags, const FrenetFrame& frenet_frame, const double signed_curvature) const { - if (sample_flags & (FittableSpline::HEAD_SAMPLE | FittableSpline::TAIL_SAMPLE)) { - return SqDistApproximant::pointDistance(frenet_frame.origin()); - } else { - return SqDistApproximant::curveDistance(pt, frenet_frame, signed_curvature); - } + if (sample_flags & (FittableSpline::HEAD_SAMPLE | FittableSpline::TAIL_SAMPLE)) { + return SqDistApproximant::pointDistance(frenet_frame.origin()); + } else { + return SqDistApproximant::curveDistance(pt, frenet_frame, signed_curvature); + } } } // namespace spfit \ No newline at end of file diff --git a/math/spfit/PolylineModelShape.h b/math/spfit/PolylineModelShape.h index d834b3531..b720e5e08 100644 --- a/math/spfit/PolylineModelShape.h +++ b/math/spfit/PolylineModelShape.h @@ -19,36 +19,35 @@ #ifndef SPFIT_POLYLINE_MODEL_SHAPE_H_ #define SPFIT_POLYLINE_MODEL_SHAPE_H_ -#include "NonCopyable.h" +#include +#include +#include "FlagOps.h" #include "ModelShape.h" +#include "NonCopyable.h" #include "SqDistApproximant.h" -#include "XSpline.h" #include "VecNT.h" -#include "FlagOps.h" -#include -#include +#include "XSpline.h" namespace spfit { class PolylineModelShape : public ModelShape { - DECLARE_NON_COPYABLE(PolylineModelShape) + DECLARE_NON_COPYABLE(PolylineModelShape) -public: - enum Flags { DEFAULT_FLAGS = 0, POLYLINE_FRONT = 1 << 0, POLYLINE_BACK = 1 << 1 }; + public: + enum Flags { DEFAULT_FLAGS = 0, POLYLINE_FRONT = 1 << 0, POLYLINE_BACK = 1 << 1 }; - explicit PolylineModelShape(const std::vector& polyline); + explicit PolylineModelShape(const std::vector& polyline); - SqDistApproximant localSqDistApproximant(const QPointF& pt, - FittableSpline::SampleFlags sample_flags) const override; + SqDistApproximant localSqDistApproximant(const QPointF& pt, FittableSpline::SampleFlags sample_flags) const override; -protected: - virtual SqDistApproximant calcApproximant(const QPointF& pt, - FittableSpline::SampleFlags sample_flags, - Flags polyline_flags, - const FrenetFrame& frenet_frame, - double signed_curvature) const; + protected: + virtual SqDistApproximant calcApproximant(const QPointF& pt, + FittableSpline::SampleFlags sample_flags, + Flags polyline_flags, + const FrenetFrame& frenet_frame, + double signed_curvature) const; -private: - std::vector m_vertices; + private: + std::vector m_vertices; }; diff --git a/math/spfit/SplineFitter.cpp b/math/spfit/SplineFitter.cpp index 09c1cb58e..5f3fa902a 100644 --- a/math/spfit/SplineFitter.cpp +++ b/math/spfit/SplineFitter.cpp @@ -17,146 +17,145 @@ */ #include "SplineFitter.h" +#include #include "ConstraintSet.h" #include "ModelShape.h" -#include namespace spfit { -SplineFitter::SplineFitter(FittableSpline* spline) : m_pSpline(spline), m_optimizer(spline->numControlPoints() * 2) { - // Each control point is a pair of (x, y) varaiables. +SplineFitter::SplineFitter(FittableSpline* spline) : m_spline(spline), m_optimizer(spline->numControlPoints() * 2) { + // Each control point is a pair of (x, y) varaiables. } void SplineFitter::splineModified() { - Optimizer(m_pSpline->numControlPoints() * 2).swap(m_optimizer); + Optimizer(m_spline->numControlPoints() * 2).swap(m_optimizer); } void SplineFitter::setConstraints(const ConstraintSet& constraints) { - m_optimizer.setConstraints(constraints.constraints()); + m_optimizer.setConstraints(constraints.constraints()); } void SplineFitter::setSamplingParams(const FittableSpline::SamplingParams& params) { - m_samplingParams = params; + m_samplingParams = params; } void SplineFitter::addAttractionForce(const Vec2d& spline_point, const std::vector& coeffs, const SqDistApproximant& sqdist_approx) { - const auto num_coeffs = static_cast(coeffs.size()); - const int num_vars = num_coeffs * 2; - QuadraticFunction f(num_vars); - - // Right now we basically have F(x) = Q(L(x)), - // where Q is a quadratic function represented by sqdist_approx, - // and L is a vector of linear combinations of control points, - // represented by coeffs. L[0] = is a linear combination of x coordinats - // of control points and L[1] is a linear combination of y coordinates of - // control points. What we are after is F(x) = Q(x), that is a quadratic - // function of control points. We consider control points to be a flat - // vector of variables, with the following layout: [x0 y0 x1 y1 x2 y2 ...] - - // First deal with the quadratic portion of our function. - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < 2; ++j) { - // Here we have Li * Aij * Lj, which gives us a product of - // two linear functions times a constant. - // L0 is a linear combination of x components: L0 = [c1 0 c2 0 ...] - // L1 is a linear combination of y components: L1 = [ 0 c1 0 c2 ...] - // Note that the same coefficients are indeed present in both L0 and L1. - const double a = sqdist_approx.A(i, j); - - // Now let's multiply Li by Lj - for (int m = 0; m < num_coeffs; ++m) { - const double c1 = coeffs[m].coeff; - const int Li_idx = m * 2 + i; - for (int n = 0; n < num_coeffs; ++n) { - const double c2 = coeffs[n].coeff; - const int Lj_idx = n * 2 + j; - f.A(Li_idx, Lj_idx) += a * c1 * c2; - } - } - } - } - - // Moving on to the linear part of the function. - for (int i = 0; i < 2; ++i) { - // Here we have Li * Bi, that is a linear function times a constant. - const double b = sqdist_approx.b[i]; - for (int m = 0; m < num_coeffs; ++m) { - const int Li_idx = m * 2 + i; - f.b[Li_idx] += b * coeffs[m].coeff; + const auto num_coeffs = static_cast(coeffs.size()); + const int num_vars = num_coeffs * 2; + QuadraticFunction f(num_vars); + + // Right now we basically have F(x) = Q(L(x)), + // where Q is a quadratic function represented by sqdist_approx, + // and L is a vector of linear combinations of control points, + // represented by coeffs. L[0] = is a linear combination of x coordinats + // of control points and L[1] is a linear combination of y coordinates of + // control points. What we are after is F(x) = Q(x), that is a quadratic + // function of control points. We consider control points to be a flat + // vector of variables, with the following layout: [x0 y0 x1 y1 x2 y2 ...] + + // First deal with the quadratic portion of our function. + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + // Here we have Li * Aij * Lj, which gives us a product of + // two linear functions times a constant. + // L0 is a linear combination of x components: L0 = [c1 0 c2 0 ...] + // L1 is a linear combination of y components: L1 = [ 0 c1 0 c2 ...] + // Note that the same coefficients are indeed present in both L0 and L1. + const double a = sqdist_approx.A(i, j); + + // Now let's multiply Li by Lj + for (int m = 0; m < num_coeffs; ++m) { + const double c1 = coeffs[m].coeff; + const int Li_idx = m * 2 + i; + for (int n = 0; n < num_coeffs; ++n) { + const double c2 = coeffs[n].coeff; + const int Lj_idx = n * 2 + j; + f.A(Li_idx, Lj_idx) += a * c1 * c2; } + } } - - // The constant part is easy. - f.c = sqdist_approx.c; - // What we've got at this point is a function of control point positions. - // What we need however, is a function from control point displacements. - m_tempVars.resize(num_vars); - for (int i = 0; i < num_coeffs; ++i) { - const int cp_idx = coeffs[i].controlPointIdx; - const QPointF cp(m_pSpline->controlPointPosition(cp_idx)); - m_tempVars[i * 2] = cp.x(); - m_tempVars[i * 2 + 1] = cp.y(); - } - f.recalcForTranslatedArguments(num_vars ? &m_tempVars[0] : nullptr); - // What remains is a mapping from the reduced set of variables to the full set. - m_tempSparseMap.resize(num_vars); - for (int i = 0; i < num_coeffs; ++i) { - m_tempSparseMap[i * 2] = coeffs[i].controlPointIdx * 2; - m_tempSparseMap[i * 2 + 1] = coeffs[i].controlPointIdx * 2 + 1; + } + + // Moving on to the linear part of the function. + for (int i = 0; i < 2; ++i) { + // Here we have Li * Bi, that is a linear function times a constant. + const double b = sqdist_approx.b[i]; + for (int m = 0; m < num_coeffs; ++m) { + const int Li_idx = m * 2 + i; + f.b[Li_idx] += b * coeffs[m].coeff; } - - m_optimizer.addExternalForce(f, m_tempSparseMap); + } + + // The constant part is easy. + f.c = sqdist_approx.c; + // What we've got at this point is a function of control point positions. + // What we need however, is a function from control point displacements. + m_tempVars.resize(num_vars); + for (int i = 0; i < num_coeffs; ++i) { + const int cp_idx = coeffs[i].controlPointIdx; + const QPointF cp(m_spline->controlPointPosition(cp_idx)); + m_tempVars[i * 2] = cp.x(); + m_tempVars[i * 2 + 1] = cp.y(); + } + f.recalcForTranslatedArguments(num_vars ? &m_tempVars[0] : nullptr); + // What remains is a mapping from the reduced set of variables to the full set. + m_tempSparseMap.resize(num_vars); + for (int i = 0; i < num_coeffs; ++i) { + m_tempSparseMap[i * 2] = coeffs[i].controlPointIdx * 2; + m_tempSparseMap[i * 2 + 1] = coeffs[i].controlPointIdx * 2 + 1; + } + + m_optimizer.addExternalForce(f, m_tempSparseMap); } // SplineFitter::addAttractionForce void SplineFitter::addAttractionForces(const ModelShape& model_shape, double from_t, double to_t) { - auto sample_processor = [this, &model_shape](const QPointF& pt, double t, FittableSpline::SampleFlags flags) { - m_pSpline->linearCombinationAt(t, m_tempCoeffs); - const SqDistApproximant approx(model_shape.localSqDistApproximant(pt, flags)); - addAttractionForce(pt, m_tempCoeffs, approx); - }; - - m_pSpline->sample( - ProxyFunction( - sample_processor), - m_samplingParams, from_t, to_t); + auto sample_processor = [this, &model_shape](const QPointF& pt, double t, FittableSpline::SampleFlags flags) { + m_spline->linearCombinationAt(t, m_tempCoeffs); + const SqDistApproximant approx(model_shape.localSqDistApproximant(pt, flags)); + addAttractionForce(pt, m_tempCoeffs, approx); + }; + + m_spline->sample(ProxyFunction( + sample_processor), + m_samplingParams, from_t, to_t); } void SplineFitter::addExternalForce(const QuadraticFunction& force) { - m_optimizer.addExternalForce(force); + m_optimizer.addExternalForce(force); } void SplineFitter::addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map) { - m_optimizer.addExternalForce(force, sparse_map); + m_optimizer.addExternalForce(force, sparse_map); } void SplineFitter::addInternalForce(const QuadraticFunction& force) { - m_optimizer.addInternalForce(force); + m_optimizer.addInternalForce(force); } void SplineFitter::addInternalForce(const QuadraticFunction& force, const std::vector& sparse_map) { - m_optimizer.addInternalForce(force, sparse_map); + m_optimizer.addInternalForce(force, sparse_map); } OptimizationResult SplineFitter::optimize(double internal_force_weight) { - const OptimizationResult res(m_optimizer.optimize(internal_force_weight)); + const OptimizationResult res(m_optimizer.optimize(internal_force_weight)); - const int num_control_points = m_pSpline->numControlPoints(); - for (int i = 0; i < num_control_points; ++i) { - const Vec2d delta(m_optimizer.displacementVector() + i * 2); - m_pSpline->moveControlPoint(i, m_pSpline->controlPointPosition(i) + delta); - } + const int num_control_points = m_spline->numControlPoints(); + for (int i = 0; i < num_control_points; ++i) { + const Vec2d delta(m_optimizer.displacementVector() + i * 2); + m_spline->moveControlPoint(i, m_spline->controlPointPosition(i) + delta); + } - return res; + return res; } void SplineFitter::undoLastStep() { - const int num_control_points = m_pSpline->numControlPoints(); - for (int i = 0; i < num_control_points; ++i) { - const Vec2d delta(m_optimizer.displacementVector() + i * 2); - m_pSpline->moveControlPoint(i, m_pSpline->controlPointPosition(i) - delta); - } + const int num_control_points = m_spline->numControlPoints(); + for (int i = 0; i < num_control_points; ++i) { + const Vec2d delta(m_optimizer.displacementVector() + i * 2); + m_spline->moveControlPoint(i, m_spline->controlPointPosition(i) - delta); + } - m_optimizer.undoLastStep(); // Zeroes the displacement vector among other things. + m_optimizer.undoLastStep(); // Zeroes the displacement vector among other things. } } // namespace spfit \ No newline at end of file diff --git a/math/spfit/SplineFitter.h b/math/spfit/SplineFitter.h index 705c61785..3710ac7ff 100644 --- a/math/spfit/SplineFitter.h +++ b/math/spfit/SplineFitter.h @@ -19,11 +19,11 @@ #ifndef SPFIT_SPLINE_FITTER_H_ #define SPFIT_SPLINE_FITTER_H_ -#include "NonCopyable.h" +#include #include "FittableSpline.h" +#include "NonCopyable.h" #include "Optimizer.h" #include "VecNT.h" -#include namespace spfit { class ConstraintSet; @@ -34,57 +34,53 @@ struct SqDistApproximant; class OptimizationResult; class SplineFitter { - DECLARE_NON_COPYABLE(SplineFitter) + DECLARE_NON_COPYABLE(SplineFitter) -public: - explicit SplineFitter(FittableSpline* spline); + public: + explicit SplineFitter(FittableSpline* spline); - /** - * To be called after adding / moving / removing any of spline's control points. - * This will reset the optimizer, which means the current set of constraints - * is lost. Any forces accumulated since the last optimize() call are lost as well. - */ - void splineModified(); + /** + * To be called after adding / moving / removing any of spline's control points. + * This will reset the optimizer, which means the current set of constraints + * is lost. Any forces accumulated since the last optimize() call are lost as well. + */ + void splineModified(); - void setConstraints(const ConstraintSet& constraints); + void setConstraints(const ConstraintSet& constraints); - void setSamplingParams(const FittableSpline::SamplingParams& sampling_params); + void setSamplingParams(const FittableSpline::SamplingParams& sampling_params); - void addAttractionForce(const Vec2d& spline_point, - const std::vector& coeffs, - const SqDistApproximant& sqdist_approx); + void addAttractionForce(const Vec2d& spline_point, + const std::vector& coeffs, + const SqDistApproximant& sqdist_approx); - void addAttractionForces(const ModelShape& model_shape, double from_t = 0.0, double to_t = 1.0); + void addAttractionForces(const ModelShape& model_shape, double from_t = 0.0, double to_t = 1.0); - void addExternalForce(const QuadraticFunction& force); + void addExternalForce(const QuadraticFunction& force); - void addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map); + void addExternalForce(const QuadraticFunction& force, const std::vector& sparse_map); - void addInternalForce(const QuadraticFunction& force); + void addInternalForce(const QuadraticFunction& force); - void addInternalForce(const QuadraticFunction& force, const std::vector& sparce_map); + void addInternalForce(const QuadraticFunction& force, const std::vector& sparce_map); - /** \see Optimizer::externalForce() */ - double externalForce() const { - return m_optimizer.externalForce(); - } + /** \see Optimizer::externalForce() */ + double externalForce() const { return m_optimizer.externalForce(); } - /** \see Optimizer::internalForce() */ - double internalForce() const { - return m_optimizer.internalForce(); - } + /** \see Optimizer::internalForce() */ + double internalForce() const { return m_optimizer.internalForce(); } - OptimizationResult optimize(double internal_force_weight); + OptimizationResult optimize(double internal_force_weight); - void undoLastStep(); + void undoLastStep(); -private: - FittableSpline* m_pSpline; - Optimizer m_optimizer; - FittableSpline::SamplingParams m_samplingParams; - std::vector m_tempVars; - std::vector m_tempSparseMap; - std::vector m_tempCoeffs; + private: + FittableSpline* m_spline; + Optimizer m_optimizer; + FittableSpline::SamplingParams m_samplingParams; + std::vector m_tempVars; + std::vector m_tempSparseMap; + std::vector m_tempCoeffs; }; } // namespace spfit #endif // ifndef SPFIT_SPLINE_FITTER_H_ diff --git a/math/spfit/SqDistApproximant.cpp b/math/spfit/SqDistApproximant.cpp index 5b666e46f..1c873a2cb 100644 --- a/math/spfit/SqDistApproximant.cpp +++ b/math/spfit/SqDistApproximant.cpp @@ -22,91 +22,91 @@ namespace spfit { SqDistApproximant::SqDistApproximant(const Vec2d& origin, const Vec2d& u, const Vec2d& v, double m, double n) { - assert(std::fabs(u.squaredNorm() - 1.0) < 1e-06 && "u is not normalized"); - assert(std::fabs(v.squaredNorm() - 1.0) < 1e-06 && "v is not normalized"); - assert(std::fabs(u.dot(v)) < 1e-06 && "u and v are not orthogonal"); - - // Consider the following equation: - // w = R*x + t - // w: vector in the local coordinate system. - // R: rotation matrix. Actually the inverse of [u v]. - // x: vector in the global coordinate system. - // t: translation component. - - // R = | u1 u2 | - // | v1 v2 | - Mat22d R; - R(0, 0) = u[0]; - R(0, 1) = u[1]; - R(1, 0) = v[0]; - R(1, 1) = v[1]; - - StaticMatrixCalc mc; - Vec2d t; // Translation component. - (-(mc(R) * mc(origin, 2, 1))).write(t); - - A(0, 0) = m * R(0, 0) * R(0, 0) + n * R(1, 0) * R(1, 0); - A(0, 1) = A(1, 0) = m * R(0, 0) * R(0, 1) + n * R(1, 0) * R(1, 1); - A(1, 1) = m * R(0, 1) * R(0, 1) + n * R(1, 1) * R(1, 1); - b[0] = 2 * (m * t[0] * R(0, 0) + n * t[1] * R(1, 0)); - b[1] = 2 * (m * t[0] * R(0, 1) + n * t[1] * R(1, 1)); - c = m * t[0] * t[0] + n * t[1] * t[1]; + assert(std::fabs(u.squaredNorm() - 1.0) < 1e-06 && "u is not normalized"); + assert(std::fabs(v.squaredNorm() - 1.0) < 1e-06 && "v is not normalized"); + assert(std::fabs(u.dot(v)) < 1e-06 && "u and v are not orthogonal"); + + // Consider the following equation: + // w = R*x + t + // w: vector in the local coordinate system. + // R: rotation matrix. Actually the inverse of [u v]. + // x: vector in the global coordinate system. + // t: translation component. + + // R = | u1 u2 | + // | v1 v2 | + Mat22d R; + R(0, 0) = u[0]; + R(0, 1) = u[1]; + R(1, 0) = v[0]; + R(1, 1) = v[1]; + + StaticMatrixCalc mc; + Vec2d t; // Translation component. + (-(mc(R) * mc(origin, 2, 1))).write(t); + + A(0, 0) = m * R(0, 0) * R(0, 0) + n * R(1, 0) * R(1, 0); + A(0, 1) = A(1, 0) = m * R(0, 0) * R(0, 1) + n * R(1, 0) * R(1, 1); + A(1, 1) = m * R(0, 1) * R(0, 1) + n * R(1, 1) * R(1, 1); + b[0] = 2 * (m * t[0] * R(0, 0) + n * t[1] * R(1, 0)); + b[1] = 2 * (m * t[0] * R(0, 1) + n * t[1] * R(1, 1)); + c = m * t[0] * t[0] + n * t[1] * t[1]; } SqDistApproximant SqDistApproximant::pointDistance(const Vec2d& pt) { - return weightedPointDistance(pt, 1); + return weightedPointDistance(pt, 1); } SqDistApproximant SqDistApproximant::weightedPointDistance(const Vec2d& pt, double weight) { - return SqDistApproximant(pt, Vec2d(1, 0), Vec2d(0, 1), weight, weight); + return SqDistApproximant(pt, Vec2d(1, 0), Vec2d(0, 1), weight, weight); } SqDistApproximant SqDistApproximant::lineDistance(const QLineF& line) { - return weightedLineDistance(line, 1); + return weightedLineDistance(line, 1); } SqDistApproximant SqDistApproximant::weightedLineDistance(const QLineF& line, double weight) { - Vec2d u(line.p2() - line.p1()); - const double sqlen = u.squaredNorm(); - if (sqlen > 1e-6) { - u /= std::sqrt(sqlen); - } else { - return pointDistance(line.p1()); - } - - // Unit normal to line. - const Vec2d v(-u[1], u[0]); - - return SqDistApproximant(line.p1(), u, v, 0, weight); + Vec2d u(line.p2() - line.p1()); + const double sqlen = u.squaredNorm(); + if (sqlen > 1e-6) { + u /= std::sqrt(sqlen); + } else { + return pointDistance(line.p1()); + } + + // Unit normal to line. + const Vec2d v(-u[1], u[0]); + + return SqDistApproximant(line.p1(), u, v, 0, weight); } SqDistApproximant SqDistApproximant::curveDistance(const Vec2d& reference_point, const FrenetFrame& frenet_frame, double signed_curvature) { - return weightedCurveDistance(reference_point, frenet_frame, signed_curvature, 1); + return weightedCurveDistance(reference_point, frenet_frame, signed_curvature, 1); } SqDistApproximant SqDistApproximant::weightedCurveDistance(const Vec2d& reference_point, const FrenetFrame& frenet_frame, const double signed_curvature, const double weight) { - const double abs_curvature = std::fabs(signed_curvature); - double m = 0; - - if (abs_curvature > std::numeric_limits::epsilon()) { - const Vec2d to_reference_point(reference_point - frenet_frame.origin()); - const double p = 1.0 / abs_curvature; - const double d = std::fabs(frenet_frame.unitNormal().dot(to_reference_point)); - m = d / (d + p); // Formula 7 in [2]. - } - - return SqDistApproximant(frenet_frame.origin(), frenet_frame.unitTangent(), frenet_frame.unitNormal(), m * weight, - weight); + const double abs_curvature = std::fabs(signed_curvature); + double m = 0; + + if (abs_curvature > std::numeric_limits::epsilon()) { + const Vec2d to_reference_point(reference_point - frenet_frame.origin()); + const double p = 1.0 / abs_curvature; + const double d = std::fabs(frenet_frame.unitNormal().dot(to_reference_point)); + m = d / (d + p); // Formula 7 in [2]. + } + + return SqDistApproximant(frenet_frame.origin(), frenet_frame.unitTangent(), frenet_frame.unitNormal(), m * weight, + weight); } double SqDistApproximant::evaluate(const Vec2d& pt) const { - StaticMatrixCalc mc; + StaticMatrixCalc mc; - return (mc(pt, 1, 2) * mc(A) * mc(pt, 2, 1) + mc(b, 1, 2) * mc(pt, 2, 1)).rawData()[0] + c; + return (mc(pt, 1, 2) * mc(A) * mc(pt, 2, 1) + mc(b, 1, 2) * mc(pt, 2, 1)).rawData()[0] + c; } } // namespace spfit \ No newline at end of file diff --git a/math/spfit/SqDistApproximant.h b/math/spfit/SqDistApproximant.h index 876e43362..f0f5f232e 100644 --- a/math/spfit/SqDistApproximant.h +++ b/math/spfit/SqDistApproximant.h @@ -19,9 +19,9 @@ #ifndef SQDIST_APPROXIMANT_H_ #define SQDIST_APPROXIMANT_H_ -#include "VecNT.h" -#include "MatMNT.h" #include +#include "MatMNT.h" +#include "VecNT.h" namespace spfit { class FrenetFrame; @@ -43,51 +43,50 @@ class FrenetFrame; * \see Eq 8 in [1], Fig 4, 5 in [2]. */ struct SqDistApproximant { - Mat22d A; - Vec2d b; - double c; - - /** - * Constructs a distance function that always evaluates to zero. - * Passing it to Optimizer::addSample() will have no effect. - */ - SqDistApproximant() : c(0) { - } - - /** - * \brief The general case constructor. - * - * We have a coordinate system at \p origin with orthonormal basis formed - * by vectors \p u and \p v. Given a point p in the global coordinate system, - * the appoximant will evaluate to: - * \code - * sqdist = m * i^2 + n * j^2; - * // Where i and j are projections onto u and v respectively. - * // More precisely: - * i = (p - origin) . u; - * j = (p - origin) . v; - * \endcode - */ - SqDistApproximant(const Vec2d& origin, const Vec2d& u, const Vec2d& v, double m, double n); - - static SqDistApproximant pointDistance(const Vec2d& pt); - - static SqDistApproximant weightedPointDistance(const Vec2d& pt, double weight); - - static SqDistApproximant lineDistance(const QLineF& line); - - static SqDistApproximant weightedLineDistance(const QLineF& line, double weight); - - static SqDistApproximant curveDistance(const Vec2d& reference_point, - const FrenetFrame& frenet_frame, - double signed_curvature); - - static SqDistApproximant weightedCurveDistance(const Vec2d& reference_point, - const FrenetFrame& frenet_frame, - double signed_curvature, - double weight); - - double evaluate(const Vec2d& pt) const; + Mat22d A; + Vec2d b; + double c; + + /** + * Constructs a distance function that always evaluates to zero. + * Passing it to Optimizer::addSample() will have no effect. + */ + SqDistApproximant() : c(0) {} + + /** + * \brief The general case constructor. + * + * We have a coordinate system at \p origin with orthonormal basis formed + * by vectors \p u and \p v. Given a point p in the global coordinate system, + * the appoximant will evaluate to: + * \code + * sqdist = m * i^2 + n * j^2; + * // Where i and j are projections onto u and v respectively. + * // More precisely: + * i = (p - origin) . u; + * j = (p - origin) . v; + * \endcode + */ + SqDistApproximant(const Vec2d& origin, const Vec2d& u, const Vec2d& v, double m, double n); + + static SqDistApproximant pointDistance(const Vec2d& pt); + + static SqDistApproximant weightedPointDistance(const Vec2d& pt, double weight); + + static SqDistApproximant lineDistance(const QLineF& line); + + static SqDistApproximant weightedLineDistance(const QLineF& line, double weight); + + static SqDistApproximant curveDistance(const Vec2d& reference_point, + const FrenetFrame& frenet_frame, + double signed_curvature); + + static SqDistApproximant weightedCurveDistance(const Vec2d& reference_point, + const FrenetFrame& frenet_frame, + double signed_curvature, + double weight); + + double evaluate(const Vec2d& pt) const; }; } // namespace spfit #endif // ifndef SQDIST_APPROXIMANT_H_ diff --git a/math/spfit/tests/CMakeLists.txt b/math/spfit/tests/CMakeLists.txt index ebb7820ed..50baa8bf0 100644 --- a/math/spfit/tests/CMakeLists.txt +++ b/math/spfit/tests/CMakeLists.txt @@ -1,28 +1,28 @@ -INCLUDE_DIRECTORIES(BEFORE ..) +include_directories(BEFORE ..) -SET( - sources - ${CMAKE_SOURCE_DIR}/tests/main.cpp - TestSqDistApproximant.cpp +set( + sources + ${CMAKE_SOURCE_DIR}/tests/main.cpp + TestSqDistApproximant.cpp ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) -SET( - libs - math Qt5::Core ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} - ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} +set( + libs + math Qt5::Core ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} ) -ADD_EXECUTABLE(spfit_tests ${sources}) -TARGET_LINK_LIBRARIES(spfit_tests ${libs}) +add_executable(spfit_tests ${sources}) +target_link_libraries(spfit_tests ${libs}) # We want the executable located where we copy all the DLLs. -SET_TARGET_PROPERTIES( - spfit_tests PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +set_target_properties( + spfit_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -ADD_TEST(NAME spfit_tests COMMAND spfit_tests --log_level=message) +add_test(NAME spfit_tests COMMAND spfit_tests --log_level=message) diff --git a/math/spfit/tests/TestSqDistApproximant.cpp b/math/spfit/tests/TestSqDistApproximant.cpp index 0457619b0..ba007fbef 100644 --- a/math/spfit/tests/TestSqDistApproximant.cpp +++ b/math/spfit/tests/TestSqDistApproximant.cpp @@ -16,14 +16,14 @@ along with this program. If not, see . */ -#include "SqDistApproximant.h" -#include "ToLineProjector.h" -#include #include +#include #include #include -#include #include +#include +#include "SqDistApproximant.h" +#include "ToLineProjector.h" namespace spfit { namespace tests { @@ -32,61 +32,61 @@ BOOST_AUTO_TEST_SUITE(SqDistApproximantTestSuite); static const double PI = 3.14159265; static double frand(double from, double to) { - const double rand_0_1 = rand() / double(RAND_MAX); + const double rand_0_1 = rand() / double(RAND_MAX); - return from + (to - from) * rand_0_1; + return from + (to - from) * rand_0_1; } BOOST_AUTO_TEST_CASE(test_point_distance) { - for (int i = 0; i < 100; ++i) { - const Vec2d origin(frand(-50, 50), frand(-50, 50)); - const SqDistApproximant approx(SqDistApproximant::pointDistance(origin)); - for (int j = 0; j < 10; ++j) { - const Vec2d pt(frand(-50, 50), frand(-50, 50)); - const double control = (pt - origin).squaredNorm(); - BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); - } + for (int i = 0; i < 100; ++i) { + const Vec2d origin(frand(-50, 50), frand(-50, 50)); + const SqDistApproximant approx(SqDistApproximant::pointDistance(origin)); + for (int j = 0; j < 10; ++j) { + const Vec2d pt(frand(-50, 50), frand(-50, 50)); + const double control = (pt - origin).squaredNorm(); + BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); } + } } BOOST_AUTO_TEST_CASE(test_line_distance) { - for (int i = 0; i < 100; ++i) { - const Vec2d pt1(frand(-50, 50), frand(-50, 50)); - const double angle = frand(0, 2.0 * PI); - const Vec2d delta(std::cos(angle), std::sin(angle)); - const QLineF line(pt1, pt1 + delta); - const SqDistApproximant approx(SqDistApproximant::lineDistance(line)); - const ToLineProjector proj(line); - for (int j = 0; j < 10; ++j) { - const Vec2d pt(frand(-50, 50), frand(-50, 50)); - const double control = proj.projectionSqDist(pt); - BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); - } + for (int i = 0; i < 100; ++i) { + const Vec2d pt1(frand(-50, 50), frand(-50, 50)); + const double angle = frand(0, 2.0 * PI); + const Vec2d delta(std::cos(angle), std::sin(angle)); + const QLineF line(pt1, pt1 + delta); + const SqDistApproximant approx(SqDistApproximant::lineDistance(line)); + const ToLineProjector proj(line); + for (int j = 0; j < 10; ++j) { + const Vec2d pt(frand(-50, 50), frand(-50, 50)); + const double control = proj.projectionSqDist(pt); + BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); } + } } BOOST_AUTO_TEST_CASE(test_general_case) { - for (int i = 0; i < 100; ++i) { - const Vec2d origin(frand(-50, 50), frand(-50, 50)); - const double angle = frand(0, 2.0 * PI); - const Vec2d u(std::cos(angle), std::sin(angle)); - Vec2d v(-u[1], u[0]); - if (rand() & 1) { - v = -v; - } - const double m = frand(0, 3); - const double n = frand(0, 3); + for (int i = 0; i < 100; ++i) { + const Vec2d origin(frand(-50, 50), frand(-50, 50)); + const double angle = frand(0, 2.0 * PI); + const Vec2d u(std::cos(angle), std::sin(angle)); + Vec2d v(-u[1], u[0]); + if (rand() & 1) { + v = -v; + } + const double m = frand(0, 3); + const double n = frand(0, 3); - const SqDistApproximant approx(origin, u, v, m, n); + const SqDistApproximant approx(origin, u, v, m, n); - for (int j = 0; j < 10; ++j) { - const Vec2d pt(frand(-50, 50), frand(-50, 50)); - const double u_proj = u.dot(pt - origin); - const double v_proj = v.dot(pt - origin); - const double control = m * u_proj * u_proj + n * v_proj * v_proj; - BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); - } + for (int j = 0; j < 10; ++j) { + const Vec2d pt(frand(-50, 50), frand(-50, 50)); + const double u_proj = u.dot(pt - origin); + const double v_proj = v.dot(pt - origin); + const double control = m * u_proj * u_proj + n * v_proj * v_proj; + BOOST_REQUIRE_CLOSE(approx.evaluate(pt), control, 1e-06); } + } } BOOST_AUTO_TEST_SUITE_END(); diff --git a/resources/DarkScheme.qrc b/resources/DarkScheme.qrc index 576cd4c46..a78a82914 100644 --- a/resources/DarkScheme.qrc +++ b/resources/DarkScheme.qrc @@ -1,40 +1,41 @@ - - dark_scheme/stylesheet.qss - dark_scheme/icon_close.png - dark_scheme/icon_undock.png - dark_scheme/icon_branch_closed.png - dark_scheme/icon_branch_end.png - dark_scheme/icon_branch_more.png - dark_scheme/icon_branch_open.png - dark_scheme/icon_vline.png - dark_scheme/icon_checkbox_checked.png - dark_scheme/icon_checkbox_indeterminate.png - dark_scheme/icon_checkbox_checked_pressed.png - dark_scheme/icon_checkbox_indeterminate_pressed.png - dark_scheme/icon_checkbox_checked_disabled.png - dark_scheme/icon_checkbox_indeterminate_disabled.png - dark_scheme/icon_radiobutton_checked.png - dark_scheme/icon_radiobutton_checked_pressed.png - dark_scheme/icon_radiobutton_checked_disabled.png - dark_scheme/icon_window_close.png - dark_scheme/icon_window_maximize.png - dark_scheme/icon_window_minimize.png - dark_scheme/icon_window_restore.png - dark_scheme/icon_scroll_bar_left_arrow.png - dark_scheme/icon_scroll_bar_right_arrow.png - dark_scheme/icon_scroll_bar_down_arrow.png - dark_scheme/icon_scroll_bar_up_arrow.png - dark_scheme/icon_down_arrow.png - dark_scheme/icon_up_arrow.png - dark_scheme/icon_down_arrow_pressed.png - dark_scheme/icon_up_arrow_pressed.png - dark_scheme/icon_slider_handle.png - dark_scheme/icon_close_disabled.png - dark_scheme/icon_undock_disabled.png - dark_scheme/icon_window_close_disabled.png - dark_scheme/icon_window_maximize_disabled.png - dark_scheme/icon_window_minimize_disabled.png - dark_scheme/icon_window_restore_disabled.png - + + dark_scheme/qss/stylesheet.qss + dark_scheme/qss/stylesheet_win.qss + dark_scheme/icons/icon_close.png + dark_scheme/icons/icon_undock.png + dark_scheme/icons/icon_branch_closed.png + dark_scheme/icons/icon_branch_end.png + dark_scheme/icons/icon_branch_more.png + dark_scheme/icons/icon_branch_open.png + dark_scheme/icons/icon_vline.png + dark_scheme/icons/icon_checkbox_checked.png + dark_scheme/icons/icon_checkbox_indeterminate.png + dark_scheme/icons/icon_checkbox_checked_pressed.png + dark_scheme/icons/icon_checkbox_indeterminate_pressed.png + dark_scheme/icons/icon_checkbox_checked_disabled.png + dark_scheme/icons/icon_checkbox_indeterminate_disabled.png + dark_scheme/icons/icon_radiobutton_checked.png + dark_scheme/icons/icon_radiobutton_checked_pressed.png + dark_scheme/icons/icon_radiobutton_checked_disabled.png + dark_scheme/icons/icon_window_close.png + dark_scheme/icons/icon_window_maximize.png + dark_scheme/icons/icon_window_minimize.png + dark_scheme/icons/icon_window_restore.png + dark_scheme/icons/icon_scroll_bar_left_arrow.png + dark_scheme/icons/icon_scroll_bar_right_arrow.png + dark_scheme/icons/icon_scroll_bar_down_arrow.png + dark_scheme/icons/icon_scroll_bar_up_arrow.png + dark_scheme/icons/icon_down_arrow.png + dark_scheme/icons/icon_up_arrow.png + dark_scheme/icons/icon_down_arrow_pressed.png + dark_scheme/icons/icon_up_arrow_pressed.png + dark_scheme/icons/icon_slider_handle.png + dark_scheme/icons/icon_close_disabled.png + dark_scheme/icons/icon_undock_disabled.png + dark_scheme/icons/icon_window_close_disabled.png + dark_scheme/icons/icon_window_maximize_disabled.png + dark_scheme/icons/icon_window_minimize_disabled.png + dark_scheme/icons/icon_window_restore_disabled.png + diff --git a/resources/LightScheme.qrc b/resources/LightScheme.qrc index 2abda26d8..eca6f8444 100644 --- a/resources/LightScheme.qrc +++ b/resources/LightScheme.qrc @@ -1,40 +1,41 @@ - - light_scheme/stylesheet.qss - light_scheme/icon_close.png - light_scheme/icon_undock.png - light_scheme/icon_branch_closed.png - light_scheme/icon_branch_end.png - light_scheme/icon_branch_more.png - light_scheme/icon_branch_open.png - light_scheme/icon_vline.png - light_scheme/icon_checkbox_checked.png - light_scheme/icon_checkbox_indeterminate.png - light_scheme/icon_checkbox_checked_pressed.png - light_scheme/icon_checkbox_indeterminate_pressed.png - light_scheme/icon_checkbox_checked_disabled.png - light_scheme/icon_checkbox_indeterminate_disabled.png - light_scheme/icon_radiobutton_checked.png - light_scheme/icon_radiobutton_checked_pressed.png - light_scheme/icon_radiobutton_checked_disabled.png - light_scheme/icon_window_close.png - light_scheme/icon_window_maximize.png - light_scheme/icon_window_minimize.png - light_scheme/icon_window_restore.png - light_scheme/icon_scroll_bar_left_arrow.png - light_scheme/icon_scroll_bar_right_arrow.png - light_scheme/icon_scroll_bar_down_arrow.png - light_scheme/icon_scroll_bar_up_arrow.png - light_scheme/icon_down_arrow.png - light_scheme/icon_up_arrow.png - light_scheme/icon_down_arrow_pressed.png - light_scheme/icon_up_arrow_pressed.png - light_scheme/icon_slider_handle.png - light_scheme/icon_close_disabled.png - light_scheme/icon_undock_disabled.png - light_scheme/icon_window_close_disabled.png - light_scheme/icon_window_maximize_disabled.png - light_scheme/icon_window_minimize_disabled.png - light_scheme/icon_window_restore_disabled.png - + + light_scheme/qss/stylesheet.qss + light_scheme/qss/stylesheet_win.qss + light_scheme/icons/icon_close.png + light_scheme/icons/icon_undock.png + light_scheme/icons/icon_branch_closed.png + light_scheme/icons/icon_branch_end.png + light_scheme/icons/icon_branch_more.png + light_scheme/icons/icon_branch_open.png + light_scheme/icons/icon_vline.png + light_scheme/icons/icon_checkbox_checked.png + light_scheme/icons/icon_checkbox_indeterminate.png + light_scheme/icons/icon_checkbox_checked_pressed.png + light_scheme/icons/icon_checkbox_indeterminate_pressed.png + light_scheme/icons/icon_checkbox_checked_disabled.png + light_scheme/icons/icon_checkbox_indeterminate_disabled.png + light_scheme/icons/icon_radiobutton_checked.png + light_scheme/icons/icon_radiobutton_checked_pressed.png + light_scheme/icons/icon_radiobutton_checked_disabled.png + light_scheme/icons/icon_window_close.png + light_scheme/icons/icon_window_maximize.png + light_scheme/icons/icon_window_minimize.png + light_scheme/icons/icon_window_restore.png + light_scheme/icons/icon_scroll_bar_left_arrow.png + light_scheme/icons/icon_scroll_bar_right_arrow.png + light_scheme/icons/icon_scroll_bar_down_arrow.png + light_scheme/icons/icon_scroll_bar_up_arrow.png + light_scheme/icons/icon_down_arrow.png + light_scheme/icons/icon_up_arrow.png + light_scheme/icons/icon_down_arrow_pressed.png + light_scheme/icons/icon_up_arrow_pressed.png + light_scheme/icons/icon_slider_handle.png + light_scheme/icons/icon_close_disabled.png + light_scheme/icons/icon_undock_disabled.png + light_scheme/icons/icon_window_close_disabled.png + light_scheme/icons/icon_window_maximize_disabled.png + light_scheme/icons/icon_window_minimize_disabled.png + light_scheme/icons/icon_window_restore_disabled.png + diff --git a/resources/dark_scheme/icon_branch_end.png b/resources/dark_scheme/icon_branch_end.png deleted file mode 100644 index cae16f02a..000000000 Binary files a/resources/dark_scheme/icon_branch_end.png and /dev/null differ diff --git a/resources/dark_scheme/icon_branch_more.png b/resources/dark_scheme/icon_branch_more.png deleted file mode 100644 index 1cf875bf9..000000000 Binary files a/resources/dark_scheme/icon_branch_more.png and /dev/null differ diff --git a/resources/dark_scheme/icon_vline.png b/resources/dark_scheme/icon_vline.png deleted file mode 100644 index d34394153..000000000 Binary files a/resources/dark_scheme/icon_vline.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_close.png b/resources/dark_scheme/icon_window_close.png deleted file mode 100644 index f00fc380d..000000000 Binary files a/resources/dark_scheme/icon_window_close.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_close_disabled.png b/resources/dark_scheme/icon_window_close_disabled.png deleted file mode 100644 index 55e100d14..000000000 Binary files a/resources/dark_scheme/icon_window_close_disabled.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_maximize.png b/resources/dark_scheme/icon_window_maximize.png deleted file mode 100644 index b0ba1dfd2..000000000 Binary files a/resources/dark_scheme/icon_window_maximize.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_maximize_disabled.png b/resources/dark_scheme/icon_window_maximize_disabled.png deleted file mode 100644 index c4eb06644..000000000 Binary files a/resources/dark_scheme/icon_window_maximize_disabled.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_minimize.png b/resources/dark_scheme/icon_window_minimize.png deleted file mode 100644 index c129018d9..000000000 Binary files a/resources/dark_scheme/icon_window_minimize.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_minimize_disabled.png b/resources/dark_scheme/icon_window_minimize_disabled.png deleted file mode 100644 index 5fd6e38fa..000000000 Binary files a/resources/dark_scheme/icon_window_minimize_disabled.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_restore.png b/resources/dark_scheme/icon_window_restore.png deleted file mode 100644 index 9f6e805ea..000000000 Binary files a/resources/dark_scheme/icon_window_restore.png and /dev/null differ diff --git a/resources/dark_scheme/icon_window_restore_disabled.png b/resources/dark_scheme/icon_window_restore_disabled.png deleted file mode 100644 index dba497816..000000000 Binary files a/resources/dark_scheme/icon_window_restore_disabled.png and /dev/null differ diff --git a/resources/dark_scheme/icon_branch_closed.png b/resources/dark_scheme/icons/icon_branch_closed.png similarity index 100% rename from resources/dark_scheme/icon_branch_closed.png rename to resources/dark_scheme/icons/icon_branch_closed.png diff --git a/resources/dark_scheme/icons/icon_branch_end.png b/resources/dark_scheme/icons/icon_branch_end.png new file mode 100644 index 000000000..511088a9f Binary files /dev/null and b/resources/dark_scheme/icons/icon_branch_end.png differ diff --git a/resources/dark_scheme/icons/icon_branch_more.png b/resources/dark_scheme/icons/icon_branch_more.png new file mode 100644 index 000000000..a84e1eacc Binary files /dev/null and b/resources/dark_scheme/icons/icon_branch_more.png differ diff --git a/resources/dark_scheme/icon_branch_open.png b/resources/dark_scheme/icons/icon_branch_open.png similarity index 100% rename from resources/dark_scheme/icon_branch_open.png rename to resources/dark_scheme/icons/icon_branch_open.png diff --git a/resources/dark_scheme/icon_checkbox_checked.png b/resources/dark_scheme/icons/icon_checkbox_checked.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_checked.png rename to resources/dark_scheme/icons/icon_checkbox_checked.png diff --git a/resources/dark_scheme/icon_checkbox_checked_disabled.png b/resources/dark_scheme/icons/icon_checkbox_checked_disabled.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_checked_disabled.png rename to resources/dark_scheme/icons/icon_checkbox_checked_disabled.png diff --git a/resources/dark_scheme/icon_checkbox_checked_pressed.png b/resources/dark_scheme/icons/icon_checkbox_checked_pressed.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_checked_pressed.png rename to resources/dark_scheme/icons/icon_checkbox_checked_pressed.png diff --git a/resources/dark_scheme/icon_checkbox_indeterminate.png b/resources/dark_scheme/icons/icon_checkbox_indeterminate.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_indeterminate.png rename to resources/dark_scheme/icons/icon_checkbox_indeterminate.png diff --git a/resources/dark_scheme/icon_checkbox_indeterminate_disabled.png b/resources/dark_scheme/icons/icon_checkbox_indeterminate_disabled.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_indeterminate_disabled.png rename to resources/dark_scheme/icons/icon_checkbox_indeterminate_disabled.png diff --git a/resources/dark_scheme/icon_checkbox_indeterminate_pressed.png b/resources/dark_scheme/icons/icon_checkbox_indeterminate_pressed.png similarity index 100% rename from resources/dark_scheme/icon_checkbox_indeterminate_pressed.png rename to resources/dark_scheme/icons/icon_checkbox_indeterminate_pressed.png diff --git a/resources/dark_scheme/icon_close.png b/resources/dark_scheme/icons/icon_close.png similarity index 100% rename from resources/dark_scheme/icon_close.png rename to resources/dark_scheme/icons/icon_close.png diff --git a/resources/dark_scheme/icon_close_disabled.png b/resources/dark_scheme/icons/icon_close_disabled.png similarity index 100% rename from resources/dark_scheme/icon_close_disabled.png rename to resources/dark_scheme/icons/icon_close_disabled.png diff --git a/resources/dark_scheme/icon_down_arrow.png b/resources/dark_scheme/icons/icon_down_arrow.png similarity index 100% rename from resources/dark_scheme/icon_down_arrow.png rename to resources/dark_scheme/icons/icon_down_arrow.png diff --git a/resources/dark_scheme/icon_down_arrow_pressed.png b/resources/dark_scheme/icons/icon_down_arrow_pressed.png similarity index 100% rename from resources/dark_scheme/icon_down_arrow_pressed.png rename to resources/dark_scheme/icons/icon_down_arrow_pressed.png diff --git a/resources/dark_scheme/icon_radiobutton_checked.png b/resources/dark_scheme/icons/icon_radiobutton_checked.png similarity index 100% rename from resources/dark_scheme/icon_radiobutton_checked.png rename to resources/dark_scheme/icons/icon_radiobutton_checked.png diff --git a/resources/dark_scheme/icon_radiobutton_checked_disabled.png b/resources/dark_scheme/icons/icon_radiobutton_checked_disabled.png similarity index 100% rename from resources/dark_scheme/icon_radiobutton_checked_disabled.png rename to resources/dark_scheme/icons/icon_radiobutton_checked_disabled.png diff --git a/resources/dark_scheme/icon_radiobutton_checked_pressed.png b/resources/dark_scheme/icons/icon_radiobutton_checked_pressed.png similarity index 100% rename from resources/dark_scheme/icon_radiobutton_checked_pressed.png rename to resources/dark_scheme/icons/icon_radiobutton_checked_pressed.png diff --git a/resources/dark_scheme/icon_scroll_bar_down_arrow.png b/resources/dark_scheme/icons/icon_scroll_bar_down_arrow.png similarity index 100% rename from resources/dark_scheme/icon_scroll_bar_down_arrow.png rename to resources/dark_scheme/icons/icon_scroll_bar_down_arrow.png diff --git a/resources/dark_scheme/icon_scroll_bar_left_arrow.png b/resources/dark_scheme/icons/icon_scroll_bar_left_arrow.png similarity index 100% rename from resources/dark_scheme/icon_scroll_bar_left_arrow.png rename to resources/dark_scheme/icons/icon_scroll_bar_left_arrow.png diff --git a/resources/dark_scheme/icon_scroll_bar_right_arrow.png b/resources/dark_scheme/icons/icon_scroll_bar_right_arrow.png similarity index 100% rename from resources/dark_scheme/icon_scroll_bar_right_arrow.png rename to resources/dark_scheme/icons/icon_scroll_bar_right_arrow.png diff --git a/resources/dark_scheme/icon_scroll_bar_up_arrow.png b/resources/dark_scheme/icons/icon_scroll_bar_up_arrow.png similarity index 100% rename from resources/dark_scheme/icon_scroll_bar_up_arrow.png rename to resources/dark_scheme/icons/icon_scroll_bar_up_arrow.png diff --git a/resources/dark_scheme/icon_slider_handle.png b/resources/dark_scheme/icons/icon_slider_handle.png similarity index 100% rename from resources/dark_scheme/icon_slider_handle.png rename to resources/dark_scheme/icons/icon_slider_handle.png diff --git a/resources/dark_scheme/icon_undock.png b/resources/dark_scheme/icons/icon_undock.png similarity index 100% rename from resources/dark_scheme/icon_undock.png rename to resources/dark_scheme/icons/icon_undock.png diff --git a/resources/dark_scheme/icon_undock_disabled.png b/resources/dark_scheme/icons/icon_undock_disabled.png similarity index 100% rename from resources/dark_scheme/icon_undock_disabled.png rename to resources/dark_scheme/icons/icon_undock_disabled.png diff --git a/resources/dark_scheme/icon_up_arrow.png b/resources/dark_scheme/icons/icon_up_arrow.png similarity index 100% rename from resources/dark_scheme/icon_up_arrow.png rename to resources/dark_scheme/icons/icon_up_arrow.png diff --git a/resources/dark_scheme/icon_up_arrow_pressed.png b/resources/dark_scheme/icons/icon_up_arrow_pressed.png similarity index 100% rename from resources/dark_scheme/icon_up_arrow_pressed.png rename to resources/dark_scheme/icons/icon_up_arrow_pressed.png diff --git a/resources/dark_scheme/icons/icon_vline.png b/resources/dark_scheme/icons/icon_vline.png new file mode 100644 index 000000000..8af8b00ae Binary files /dev/null and b/resources/dark_scheme/icons/icon_vline.png differ diff --git a/resources/dark_scheme/icons/icon_window_close.png b/resources/dark_scheme/icons/icon_window_close.png new file mode 100644 index 000000000..cfae2683c Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_close.png differ diff --git a/resources/dark_scheme/icons/icon_window_close_disabled.png b/resources/dark_scheme/icons/icon_window_close_disabled.png new file mode 100644 index 000000000..e9a70a788 Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_close_disabled.png differ diff --git a/resources/dark_scheme/icons/icon_window_maximize.png b/resources/dark_scheme/icons/icon_window_maximize.png new file mode 100644 index 000000000..6c1900af6 Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_maximize.png differ diff --git a/resources/dark_scheme/icons/icon_window_maximize_disabled.png b/resources/dark_scheme/icons/icon_window_maximize_disabled.png new file mode 100644 index 000000000..c72ca43ee Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_maximize_disabled.png differ diff --git a/resources/dark_scheme/icons/icon_window_minimize.png b/resources/dark_scheme/icons/icon_window_minimize.png new file mode 100644 index 000000000..5c69cfdc9 Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_minimize.png differ diff --git a/resources/dark_scheme/icons/icon_window_minimize_disabled.png b/resources/dark_scheme/icons/icon_window_minimize_disabled.png new file mode 100644 index 000000000..846039e5e Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_minimize_disabled.png differ diff --git a/resources/dark_scheme/icons/icon_window_restore.png b/resources/dark_scheme/icons/icon_window_restore.png new file mode 100644 index 000000000..905e6fd52 Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_restore.png differ diff --git a/resources/dark_scheme/icons/icon_window_restore_disabled.png b/resources/dark_scheme/icons/icon_window_restore_disabled.png new file mode 100644 index 000000000..ae0fe96ec Binary files /dev/null and b/resources/dark_scheme/icons/icon_window_restore_disabled.png differ diff --git a/resources/dark_scheme/qss/stylesheet.qss b/resources/dark_scheme/qss/stylesheet.qss new file mode 100644 index 000000000..be48f3d9d --- /dev/null +++ b/resources/dark_scheme/qss/stylesheet.qss @@ -0,0 +1,665 @@ +QPushButton { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.5625em 0.0625em 0.5625em; + min-height: 1.0625em; + min-width: 3.125em; + text-align: center; +} + +QLineEdit, QListView { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.1875em 0.0625em 0.1875em; + min-height: 1.0625em; + min-width: 1.0625em; + text-align: center; + selection-background-color: #3399ff; + selection-color: white; +} + +QToolButton { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0em; + text-align: center; +} + +QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { + border: 0.0625em solid #5e5e5e; + background-color: #4d4d4d; +} + +QPushButton:default, QToolButton:default { + border-color: #939393; +} + +QPushButton:hover, QToolButton:hover, QPushButton:active:focus, QToolButton:active:focus, QLineEdit:active:focus { + border-color: #0f64d2; +} + +QPushButton:pressed, QToolButton:pressed, QPushButton:checked:active:focus, QToolButton:checked:active:focus { + border-color: #0f64d2; + background-color: #383838; +} + +QPushButton:checked, QToolButton:checked { + border-color: #595fb1; + background-color: #383838; +} + +QPushButton:checked:disabled, QToolButton:checked:disabled { + border-color: #595fb1; + background-color: #4d4d4d; +} + +QPushButton:flat, QToolButton:flat { + border: none; +} + +QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QRadioButton::indicator { + width: 0.875em; + height: 0.875em; + margin: 0.0625em; +} + +QMenu::indicator:non-exclusive, QMenu::indicator:exclusive { + width: 0.875em; + height: 0.875em; + margin: 0.0625em 0.0625em 0.0625em 0.1875em; +} + +QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked { + border: 0.0625em solid #666666; + margin: 0em; + background-color: palette(base); + border-radius: 0.1719em; +} + +QMenu::indicator:non-exclusive:unchecked { + border: 0.0625em solid #666666; + margin: 0em 0em 0em 0.125em; + background-color: palette(base); + border-radius: 0.1719em; +} + +QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:active:focus, QTreeView::indicator:unchecked:active:focus, QTableView::indicator:unchecked:active:focus, QGroupBox::indicator:unchecked:active:focus { + border-color: #0f64d2; +} + +QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { + border-color: #0f64d2; + background-color: #363636; +} + +QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { + border-color: #5e5e5e; + background-color: #4d4d4d; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, +QMenu::indicator:non-exclusive:checked { + background-color: #d2d2d2; + border-radius: 0.1094em; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QMenu::indicator:non-exclusive:checked { + image: url(:/dark_scheme/icons/icon_checkbox_checked.png); +} + +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate.png); +} + +QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, +QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, +QGroupBox::indicator:indeterminate:hover, +QCheckBox::indicator:checked:active:focus, QTreeView::indicator:checked:active:focus, QTableView::indicator:checked:active:focus, QGroupBox::indicator:checked:active:focus, +QCheckBox::indicator:indeterminate:active:focus, QTreeView::indicator:indeterminate:active:focus, QTableView::indicator:indeterminate:active:focus, +QGroupBox::indicator:indeterminate:active:focus { + border: 0.0625em solid #0f64d2; + margin: 0em; + border-radius: 0.1719em; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + border: 0.0625em solid #0f64d2; + margin: 0em; + border-radius: 0.1719em; + background-color: #868686; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { + image: url(:/dark_scheme/icons/icon_checkbox_checked_pressed.png); +} + +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate_pressed.png); +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + background-color: #707070; +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { + image: url(:/dark_scheme/icons/icon_checkbox_checked_disabled.png); +} + +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate_disabled.png); +} + +QRadioButton::indicator:unchecked { + border: 0.0625em solid #666666; + margin: 0em; + background-color: palette(base); + border-radius: 0.4844em; +} + +QMenu::indicator:exclusive:unchecked { + border: 0.0625em solid #666666; + margin: 0em 0em 0em 0.125em; + background-color: palette(base); + border-radius: 0.4844em; +} + +QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:active:focus { + border-color: #0f64d2; +} + +QRadioButton::indicator:unchecked:pressed { + border-color: #0f64d2; + background-color: #363636; +} + +QRadioButton::indicator:unchecked:disabled { + border-color: #5e5e5e; + background-color: #4d4d4d; +} + +QRadioButton::indicator:checked, +QMenu::indicator:exclusive:checked { + background-color: #d2d2d2; + border-radius: 0.4219em; + image: url(:/dark_scheme/icons/icon_radiobutton_checked.png); +} + +QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:active:focus { + border: 0.0625em solid #0f64d2; + margin: 0em; + border-radius: 0.4844em; +} + +QRadioButton::indicator:checked:pressed { + border: 0.0625em solid #0f64d2; + margin: 0em; + border-radius: 0.4844em; + background-color: #868686; + image: url(:/dark_scheme/icons/icon_radiobutton_checked_pressed.png); +} + +QRadioButton::indicator:checked:disabled { + background-color: #707070; + image: url(:/dark_scheme/icons/icon_radiobutton_checked_disabled.png); +} + +QProgressBar { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0.4219em; + height: 0.875em; + text-align: center; +} + +QProgressBar::chunk { + background-color: #3399ff; + border-radius: 0.4219em; +} + +QGroupBox { + background-color: palette(window); + border: 0.0625em solid #424242; + border-radius: 0em; + margin-top: 0.5em; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.25em 0em 0.25em +} + +QGraphicsView, ImageViewBase, QFrame#imageViewFrame { + background-color: #282828; + background-attachment: scroll; + border: none; +} + +QGraphicsView { + margin: 0.0625em 0.0625em 0.0625em 0.0625em; +} + +QGroupBox#recentProjectsGroup { + background-color: transparent; + border: 0.0625em solid #0f64d2; + border-radius: 0em; + margin-top: 0.875em; +} + +QGroupBox#recentProjectsGroup::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.25em 0em 0.25em; +} + +QMenuBar { + background-color: palette(window); + border-bottom: 0.0625em solid #383838; + margin-bottom: 0.0625em; +} + +QMenuBar::item { + spacing: 0.125em; + padding: 0.0625em 0.1875em 0.0625em; + background: transparent; + border-radius: 0.1094em; + margin-bottom: 0.1875em; +} + +QMenuBar::item:selected { + border: 0.0625em solid #636363; + background-color: #474747; +} + +QMenuBar::item:pressed { + border: 0.0625em solid #636363; + background-color: #383838; +} + +QMenu { + background-color: palette(base); +} + +QMenu::separator { + height: 0.0625em; + background: #383838; + margin-left: 0.3125em; + margin-right: 0.3125em; +} + +QMenu::item { + padding: 0.0625em 0.875em 0.0625em 1.25em; + height: 1.5em; + margin: 0.0625em 0em 0.0625em 0em; +} + +QMenu::item:disabled { + background-color: #4d4d4d; + color: #989898; +} + +QMenu::item:selected { + background-color: palette(highlight); + color: palette(highlighted-text); +} + +QDockWidget::title { + background-color: #424242; + border: 0.0625em solid #383838; + text-align: center; +} + +QDockWidget { + titlebar-close-icon: url(:/dark_scheme/icons/icon_window_close.png); + titlebar-normal-icon: url(:/dark_scheme/icons/icon_window_restore.png); +} + +QDockWidget:disabled { + titlebar-close-icon: url(:/dark_scheme/icons/icon_window_close_disabled.png); + titlebar-normal-icon: url(:/dark_scheme/icons/icon_window_restore_disabled.png); +} + +QDockWidget > QWidget { + border: 0.0625em solid #383838; + border-top: none; + border-bottom: none; +} + +QDockWidget::close-button, QDockWidget::float-button { + padding: 0em; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover, QDockWidget::close-button:active:focus, QDockWidget::float-button:active:focus { + background-color: #4f4f4f; +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #383838; +} + +QScrollBar:vertical { + background: #494949; + border: none; + width: 1.0625em; + margin: 0em; +} + +QScrollBar::handle:vertical { + background-color: #696969; + border: none; + min-height: 1.25em; + margin: 1.0625em 0.125em 1.0625em 0.125em; +} + +QScrollBar::add-line:vertical { + background: transparent; + height: 1.0625em; + subcontrol-position: bottom; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar::sub-line:vertical { + background: transparent; + height: 1.0625em; + subcontrol-position: top; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar:horizontal { + background: #494949; + border: none; + height: 1.0625em; + margin: 0em; +} + +QScrollBar::handle:horizontal { + background-color: #696969; + border: none; + min-width: 1.25em; + margin: 0.125em 1.0625em 0.125em 1.0625em; +} + +QScrollBar::add-line:horizontal { + background: transparent; + width: 1.0625em; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + background: transparent; + width: 1.0625em; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { + background: #1a1a1a; +} + +QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { + width: 1.0625em; + height: 1.0625em; + background: transparent; + border: none; +} + +QScrollBar:left-arrow:horizontal { + image: url(:/dark_scheme/icons/icon_scroll_bar_left_arrow.png); +} + +QScrollBar::right-arrow:horizontal { + image: url(:/dark_scheme/icons/icon_scroll_bar_right_arrow.png); +} + +QScrollBar:down-arrow:vertical { + image: url(:/dark_scheme/icons/icon_scroll_bar_down_arrow.png); +} + +QScrollBar::up-arrow:vertical { + image: url(:/dark_scheme/icons/icon_scroll_bar_up_arrow.png); +} + +QTabWidget > QWidget { + qproperty-drawBase:0; +} + +QTabWidget::pane { + background-color: palette(window); + background-color: #424242; + qproperty-drawBase:0; +} + +QTabWidget::tab-bar { + top: 0.0625em; +} + +QTabBar { + background-color: transparent; + border: none; +} + +QTabBar::tab { + padding: 0.1875em 0.5625em 0.1875em 0.5625em; + min-height: 1.0625em; + min-width: 1.0625em; + background-color: #424242; + border: 0.0625em solid #383838; +} + +QTabBar::tab:!first { + border-left-width: 0em; +} + +QTabBar::tab:hover { + background-color: #4f4f4f; +} + +QTabBar::tab:selected { + background-color: palette(window); + border-bottom-width: 0em; + padding-bottom: 0.25em; +} + +output--TabbedImageView::tab-bar { + left: 0em; + right: 0em; + top: 0em; +} + +output--TabbedImageView > QTabBar::tab { + padding: 0.5625em 0.1875em 0.5625em 0.1875em; + min-height: 1.0625em; + min-width: 1.0625em; +} + +output--TabbedImageView > QTabBar::tab:!first { + border-left-width: 0.0625em; + border-top-width: 0em; +} + +output--TabbedImageView > QTabBar::tab:selected { + border-bottom-width: 0.0625em; + padding-bottom: 0.5625em; + border-left-width: 0em; + padding-left: 0.25em; +} + +QComboBox { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0.1719em; + padding: 0.0625em 0em 0.0625em 0.5em; + min-height: 1.0625em; + min-width: 1.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox { + border: 0.0625em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.1875em 0.0625em 0.1875em; + min-height: 1.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QComboBox:disabled, QAbstractSpinBox:disabled { + border: 0.0625em solid #5e5e5e; + background-color: #4d4d4d; +} + +QComboBox:hover, QComboBox:active:focus, QAbstractSpinBox:active:focus { + border-color: #0f64d2; +} + +QComboBox:on { + border-color: #0f64d2; + background-color: #383838; +} + +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: bottom right; + width: 1.125em; + background: transparent; + border: none; +} + +QComboBox::down-arrow { + image: url(:/dark_scheme/icons/icon_down_arrow.png); +} + +QComboBox QAbstractItemView { + border: none; + padding: 0.0625em 0.0625em 0.0625em 0.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + padding-top: 0.25em; + padding-right: 0.0625em; + width: 0.875em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + padding-bottom: 0.25em; + padding-right: 0.0625em; + width: 0.875em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-arrow { + image: url(:/dark_scheme/icons/icon_down_arrow.png); +} + +QAbstractSpinBox::up-arrow { + image: url(:/dark_scheme/icons/icon_up_arrow.png); +} + +QAbstractSpinBox::down-arrow:pressed { + image: url(:/dark_scheme/icons/icon_down_arrow_pressed.png); +} + +QAbstractSpinBox::up-arrow:pressed { + image: url(:/dark_scheme/icons/icon_up_arrow_pressed.png); +} + +QSlider:horizontal { + margin: 0.5625em 0.0625em 0.5625em 0.0625em; +} + +QSlider::groove:horizontal { + border: none; + height: 0.375em; + background-color: #757575; +} + +QSlider::handle:horizontal { + height: 1.1875em; + width: 0.75em; + margin: -1.3125em 0em; + image: url(:/dark_scheme/icons/icon_slider_handle.png); +} + +QSlider::handle:disabled { + image: none; +} + +QToolTip { + color: palette(window-text); + background-color: #424242; + border: 0.0625em solid #383838; + border-radius: 0em; +} + +QStatusBar { + background-color: #424242; + color: palette(window-text); +} + +QTreeView, QTableView { + alternate-background-color: palette(window); + background: palette(base); + border: 0.0625em solid #383838; +} + +QTreeView QHeaderView::section, QTableView QHeaderView::section { + background: #424242; + border-style: none; + border-right: 0.0625em solid #383838; + border-bottom: 0.0625em solid #383838; + padding-left: 0.1875em; + padding-right: 0.1875em; +} + +QTreeView::item:selected:disabled, QTableView::item:selected:disabled { + background: #5d5d5d; +} + +QTreeView::branch { + background-color: palette(base); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/dark_scheme/icons/icon_vline.png) 0; +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/dark_scheme/icons/icon_branch_more.png) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/dark_scheme/icons/icon_branch_end.png) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(:/dark_scheme/icons/icon_branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(:/dark_scheme/icons/icon_branch_open.png); +} \ No newline at end of file diff --git a/resources/dark_scheme/qss/stylesheet_win.qss b/resources/dark_scheme/qss/stylesheet_win.qss new file mode 100644 index 000000000..72d0d5b00 --- /dev/null +++ b/resources/dark_scheme/qss/stylesheet_win.qss @@ -0,0 +1,665 @@ +QPushButton { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.6923em 0.0769em 0.6923em; + min-height: 1.3077em; + min-width: 3.8462em; + text-align: center; +} + +QLineEdit, QListView { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.2308em 0.0769em 0.2308em; + min-height: 1.3077em; + min-width: 1.3077em; + text-align: center; + selection-background-color: #3399ff; + selection-color: white; +} + +QToolButton { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0em; + text-align: center; +} + +QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { + border: 0.0769em solid #5e5e5e; + background-color: #4d4d4d; +} + +QPushButton:default, QToolButton:default { + border-color: #939393; +} + +QPushButton:hover, QToolButton:hover, QPushButton:active:focus, QToolButton:active:focus, QLineEdit:active:focus { + border-color: #0f64d2; +} + +QPushButton:pressed, QToolButton:pressed, QPushButton:checked:active:focus, QToolButton:checked:active:focus { + border-color: #0f64d2; + background-color: #383838; +} + +QPushButton:checked, QToolButton:checked { + border-color: #595fb1; + background-color: #383838; +} + +QPushButton:checked:disabled, QToolButton:checked:disabled { + border-color: #595fb1; + background-color: #4d4d4d; +} + +QPushButton:flat, QToolButton:flat { + border: none; +} + +QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QRadioButton::indicator { + width: 1.0769em; + height: 1.0769em; + margin: 0.0769em; +} + +QMenu::indicator:non-exclusive, QMenu::indicator:exclusive { + width: 1.0769em; + height: 1.0769em; + margin: 0.0769em 0.0769em 0.0769em 0.2308em; +} + +QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked { + border: 0.0769em solid #666666; + margin: 0em; + background-color: palette(base); + border-radius: 0.2115em; +} + +QMenu::indicator:non-exclusive:unchecked { + border: 0.0769em solid #666666; + margin: 0em 0em 0em 0.1538em; + background-color: palette(base); + border-radius: 0.2115em; +} + +QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:active:focus, QTreeView::indicator:unchecked:active:focus, QTableView::indicator:unchecked:active:focus, QGroupBox::indicator:unchecked:active:focus { + border-color: #0f64d2; +} + +QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { + border-color: #0f64d2; + background-color: #363636; +} + +QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { + border-color: #5e5e5e; + background-color: #4d4d4d; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, +QMenu::indicator:non-exclusive:checked { + background-color: #d2d2d2; + border-radius: 0.1346em; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QMenu::indicator:non-exclusive:checked { + image: url(:/dark_scheme/icons/icon_checkbox_checked.png); +} + +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate.png); +} + +QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, +QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, +QGroupBox::indicator:indeterminate:hover, +QCheckBox::indicator:checked:active:focus, QTreeView::indicator:checked:active:focus, QTableView::indicator:checked:active:focus, QGroupBox::indicator:checked:active:focus, +QCheckBox::indicator:indeterminate:active:focus, QTreeView::indicator:indeterminate:active:focus, QTableView::indicator:indeterminate:active:focus, +QGroupBox::indicator:indeterminate:active:focus { + border: 0.0769em solid #0f64d2; + margin: 0em; + border-radius: 0.2115em; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + border: 0.0769em solid #0f64d2; + margin: 0em; + border-radius: 0.2115em; + background-color: #868686; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { + image: url(:/dark_scheme/icons/icon_checkbox_checked_pressed.png); +} + +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate_pressed.png); +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + background-color: #707070; +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { + image: url(:/dark_scheme/icons/icon_checkbox_checked_disabled.png); +} + +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + image: url(:/dark_scheme/icons/icon_checkbox_indeterminate_disabled.png); +} + +QRadioButton::indicator:unchecked { + border: 0.0769em solid #666666; + margin: 0em; + background-color: palette(base); + border-radius: 0.5962em; +} + +QMenu::indicator:exclusive:unchecked { + border: 0.0769em solid #666666; + margin: 0em 0em 0em 0.1538em; + background-color: palette(base); + border-radius: 0.5962em; +} + +QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:active:focus { + border-color: #0f64d2; +} + +QRadioButton::indicator:unchecked:pressed { + border-color: #0f64d2; + background-color: #363636; +} + +QRadioButton::indicator:unchecked:disabled { + border-color: #5e5e5e; + background-color: #4d4d4d; +} + +QRadioButton::indicator:checked, +QMenu::indicator:exclusive:checked { + background-color: #d2d2d2; + border-radius: 0.5192em; + image: url(:/dark_scheme/icons/icon_radiobutton_checked.png); +} + +QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:active:focus { + border: 0.0769em solid #0f64d2; + margin: 0em; + border-radius: 0.5962em; +} + +QRadioButton::indicator:checked:pressed { + border: 0.0769em solid #0f64d2; + margin: 0em; + border-radius: 0.5962em; + background-color: #868686; + image: url(:/dark_scheme/icons/icon_radiobutton_checked_pressed.png); +} + +QRadioButton::indicator:checked:disabled { + background-color: #707070; + image: url(:/dark_scheme/icons/icon_radiobutton_checked_disabled.png); +} + +QProgressBar { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0.5192em; + height: 1.0769em; + text-align: center; +} + +QProgressBar::chunk { + background-color: #3399ff; + border-radius: 0.5192em; +} + +QGroupBox { + background-color: palette(window); + border: 0.0769em solid #424242; + border-radius: 0em; + margin-top: 0.6154em; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.3077em 0em 0.3077em +} + +QGraphicsView, ImageViewBase, QFrame#imageViewFrame { + background-color: #282828; + background-attachment: scroll; + border: none; +} + +QGraphicsView { + margin: 0.0769em 0.0769em 0.0769em 0.0769em; +} + +QGroupBox#recentProjectsGroup { + background-color: transparent; + border: 0.0769em solid #0f64d2; + border-radius: 0em; + margin-top: 1.0769em; +} + +QGroupBox#recentProjectsGroup::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.3077em 0em 0.3077em; +} + +QMenuBar { + background-color: palette(window); + border-bottom: 0.0769em solid #383838; + margin-bottom: 0.0769em; +} + +QMenuBar::item { + spacing: 0.1538em; + padding: 0.0769em 0.2308em 0.0769em; + background: transparent; + border-radius: 0.1346em; + margin-bottom: 0.2308em; +} + +QMenuBar::item:selected { + border: 0.0769em solid #636363; + background-color: #474747; +} + +QMenuBar::item:pressed { + border: 0.0769em solid #636363; + background-color: #383838; +} + +QMenu { + background-color: palette(base); +} + +QMenu::separator { + height: 0.0769em; + background: #383838; + margin-left: 0.3846em; + margin-right: 0.3846em; +} + +QMenu::item { + padding: 0.0769em 1.0769em 0.0769em 1.5385em; + height: 1.8462em; + margin: 0.0769em 0em 0.0769em 0em; +} + +QMenu::item:disabled { + background-color: #4d4d4d; + color: #989898; +} + +QMenu::item:selected { + background-color: palette(highlight); + color: palette(highlighted-text); +} + +QDockWidget::title { + background-color: #424242; + border: 0.0769em solid #383838; + text-align: center; +} + +QDockWidget { + titlebar-close-icon: url(:/dark_scheme/icons/icon_window_close.png); + titlebar-normal-icon: url(:/dark_scheme/icons/icon_window_restore.png); +} + +QDockWidget:disabled { + titlebar-close-icon: url(:/dark_scheme/icons/icon_window_close_disabled.png); + titlebar-normal-icon: url(:/dark_scheme/icons/icon_window_restore_disabled.png); +} + +QDockWidget > QWidget { + border: 0.0769em solid #383838; + border-top: none; + border-bottom: none; +} + +QDockWidget::close-button, QDockWidget::float-button { + padding: 0em; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover, QDockWidget::close-button:active:focus, QDockWidget::float-button:active:focus { + background-color: #4f4f4f; +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #383838; +} + +QScrollBar:vertical { + background: #494949; + border: none; + width: 1.3077em; + margin: 0em; +} + +QScrollBar::handle:vertical { + background-color: #696969; + border: none; + min-height: 1.5385em; + margin: 1.3077em 0.1538em 1.3077em 0.1538em; +} + +QScrollBar::add-line:vertical { + background: transparent; + height: 1.3077em; + subcontrol-position: bottom; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar::sub-line:vertical { + background: transparent; + height: 1.3077em; + subcontrol-position: top; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar:horizontal { + background: #494949; + border: none; + height: 1.3077em; + margin: 0em; +} + +QScrollBar::handle:horizontal { + background-color: #696969; + border: none; + min-width: 1.5385em; + margin: 0.1538em 1.3077em 0.1538em 1.3077em; +} + +QScrollBar::add-line:horizontal { + background: transparent; + width: 1.3077em; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + background: transparent; + width: 1.3077em; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { + background: #1a1a1a; +} + +QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { + width: 1.3077em; + height: 1.3077em; + background: transparent; + border: none; +} + +QScrollBar:left-arrow:horizontal { + image: url(:/dark_scheme/icons/icon_scroll_bar_left_arrow.png); +} + +QScrollBar::right-arrow:horizontal { + image: url(:/dark_scheme/icons/icon_scroll_bar_right_arrow.png); +} + +QScrollBar:down-arrow:vertical { + image: url(:/dark_scheme/icons/icon_scroll_bar_down_arrow.png); +} + +QScrollBar::up-arrow:vertical { + image: url(:/dark_scheme/icons/icon_scroll_bar_up_arrow.png); +} + +QTabWidget > QWidget { + qproperty-drawBase:0; +} + +QTabWidget::pane { + background-color: palette(window); + background-color: #424242; + qproperty-drawBase:0; +} + +QTabWidget::tab-bar { + top: 0.0769em; +} + +QTabBar { + background-color: transparent; + border: none; +} + +QTabBar::tab { + padding: 0.2308em 0.6923em 0.2308em 0.6923em; + min-height: 1.3077em; + min-width: 1.3077em; + background-color: #424242; + border: 0.0769em solid #383838; +} + +QTabBar::tab:!first { + border-left-width: 0em; +} + +QTabBar::tab:hover { + background-color: #4f4f4f; +} + +QTabBar::tab:selected { + background-color: palette(window); + border-bottom-width: 0em; + padding-bottom: 0.3077em; +} + +output--TabbedImageView::tab-bar { + left: 0em; + right: 0em; + top: 0em; +} + +output--TabbedImageView > QTabBar::tab { + padding: 0.6923em 0.2308em 0.6923em 0.2308em; + min-height: 1.3077em; + min-width: 1.3077em; +} + +output--TabbedImageView > QTabBar::tab:!first { + border-left-width: 0.0769em; + border-top-width: 0em; +} + +output--TabbedImageView > QTabBar::tab:selected { + border-bottom-width: 0.0769em; + padding-bottom: 0.6923em; + border-left-width: 0em; + padding-left: 0.3077em; +} + +QComboBox { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0.2115em; + padding: 0.0769em 0em 0.0769em 0.6154em; + min-height: 1.3077em; + min-width: 1.3077em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox { + border: 0.0769em solid #666666; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.2308em 0.0769em 0.2308em; + min-height: 1.3077em; + selection-background-color: #3399ff; + selection-color: white; +} + +QComboBox:disabled, QAbstractSpinBox:disabled { + border: 0.0769em solid #5e5e5e; + background-color: #4d4d4d; +} + +QComboBox:hover, QComboBox:active:focus, QAbstractSpinBox:active:focus { + border-color: #0f64d2; +} + +QComboBox:on { + border-color: #0f64d2; + background-color: #383838; +} + +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: bottom right; + width: 1.3846em; + background: transparent; + border: none; +} + +QComboBox::down-arrow { + image: url(:/dark_scheme/icons/icon_down_arrow.png); +} + +QComboBox QAbstractItemView { + border: none; + padding: 0.0769em 0.0769em 0.0769em 0.0769em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + padding-top: 0.3077em; + padding-right: 0.0769em; + width: 1.0769em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + padding-bottom: 0.3077em; + padding-right: 0.0769em; + width: 1.0769em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-arrow { + image: url(:/dark_scheme/icons/icon_down_arrow.png); +} + +QAbstractSpinBox::up-arrow { + image: url(:/dark_scheme/icons/icon_up_arrow.png); +} + +QAbstractSpinBox::down-arrow:pressed { + image: url(:/dark_scheme/icons/icon_down_arrow_pressed.png); +} + +QAbstractSpinBox::up-arrow:pressed { + image: url(:/dark_scheme/icons/icon_up_arrow_pressed.png); +} + +QSlider:horizontal { + margin: 0.6923em 0.0769em 0.6923em 0.0769em; +} + +QSlider::groove:horizontal { + border: none; + height: 0.4615em; + background-color: #757575; +} + +QSlider::handle:horizontal { + height: 1.4615em; + width: 0.9231em; + margin: -1.6154em 0em; + image: url(:/dark_scheme/icons/icon_slider_handle.png); +} + +QSlider::handle:disabled { + image: none; +} + +QToolTip { + color: palette(window-text); + background-color: #424242; + border: 0.0769em solid #383838; + border-radius: 0em; +} + +QStatusBar { + background-color: #424242; + color: palette(window-text); +} + +QTreeView, QTableView { + alternate-background-color: palette(window); + background: palette(base); + border: 0.0769em solid #383838; +} + +QTreeView QHeaderView::section, QTableView QHeaderView::section { + background: #424242; + border-style: none; + border-right: 0.0769em solid #383838; + border-bottom: 0.0769em solid #383838; + padding-left: 0.2308em; + padding-right: 0.2308em; +} + +QTreeView::item:selected:disabled, QTableView::item:selected:disabled { + background: #5d5d5d; +} + +QTreeView::branch { + background-color: palette(base); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/dark_scheme/icons/icon_vline.png) 0; +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/dark_scheme/icons/icon_branch_more.png) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/dark_scheme/icons/icon_branch_end.png) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(:/dark_scheme/icons/icon_branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(:/dark_scheme/icons/icon_branch_open.png); +} \ No newline at end of file diff --git a/resources/dark_scheme/stylesheet.qss b/resources/dark_scheme/stylesheet.qss deleted file mode 100644 index 987023928..000000000 --- a/resources/dark_scheme/stylesheet.qss +++ /dev/null @@ -1,633 +0,0 @@ -QPushButton, QLineEdit, QListView { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 0px; - padding: 1px 3px 1px 3px; - height: 17px; - width: 78px; - text-align: center; - selection-background-color: #3399ff; - selection-color: white; -} - -QToolButton { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 0px; - text-align: center; -} - -QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { - border: 1px solid #5e5e5e; - background-color: #4d4d4d; -} - -QPushButton:default, QToolButton:default { - border-color: #939393; -} - -QPushButton:hover, QToolButton:hover, QLineEdit:focus { - border-color: #0f64d2; -} - -QPushButton:pressed, QToolButton:pressed { - border-color: #0f64d2; - background-color: #383838; -} - -QPushButton:checked, QToolButton:checked { - border-color: #595fb1; - background-color: #383838; -} - -QPushButton:checked:disabled, QToolButton:checked:disabled { - border-color: #595fb1; - background-color: #4d4d4d; -} - -QPushButton:flat, QToolButton:flat { - border: none; -} - -QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QMenu::indicator:non-exclusive, -QRadioButton::indicator, QMenu::indicator:exclusive { - width: 14px; - height: 14px; - margin: 1px 0px 1px 0px; -} - -QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked, -QMenu::indicator:non-exclusive:unchecked { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 3px; -} - -QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border-color: #0f64d2; -} - -QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { - border-color: #0f64d2; - background-color: #363636; -} - -QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - border: 1px solid #5e5e5e; - background-color: #4d4d4d; -} - -QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, -QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, -QMenu::indicator:non-exclusive:checked { - border: none; - margin-left: 1px; - margin-right: 1px; - background-color: #d2d2d2; - border-radius: 2px; -} - -QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, -QMenu::indicator:non-exclusive:checked { - image: url(:/dark_scheme/icon_checkbox_checked.png); -} - -QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { - image: url(:/dark_scheme/icon_checkbox_indeterminate.png); -} - -QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, -QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, QGroupBox::indicator:indeterminate:hover { - border: 1px solid #0f64d2; - margin-left: 0px; - margin-right: 0px; - border-radius: 3px; -} - -QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, -QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { - border: 1px solid #0f64d2; - margin-left: 0px; - margin-right: 0px; - border-radius: 3px; - background-color: #868686; -} - -QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { - image: url(:/dark_scheme/icon_checkbox_checked_pressed.png); -} - -QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { - image: url(:/dark_scheme/icon_checkbox_indeterminate_pressed.png); -} - -QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, -QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { - background-color: #707070; -} - -QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(:/dark_scheme/icon_checkbox_checked_disabled.png); -} - -QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { - image: url(:/dark_scheme/icon_checkbox_indeterminate_disabled.png); -} - -QRadioButton::indicator:unchecked, -QMenu::indicator:exclusive:unchecked { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 8px; -} - -QRadioButton::indicator:unchecked:hover { - border-color: #0f64d2; -} - -QRadioButton::indicator:unchecked:pressed { - border-color: #0f64d2; - background-color: #363636; -} - -QRadioButton::indicator:unchecked:disabled { - border: 1px solid #5e5e5e; - background-color: #4d4d4d; -} - -QRadioButton::indicator:checked, -QMenu::indicator:exclusive:checked { - border: none; - margin-left: 1px; - margin-right: 1px; - background-color: #d2d2d2; - border-radius: 7px; - image: url(:/dark_scheme/icon_radiobutton_checked.png); -} - -QRadioButton::indicator:checked:hover { - border: 1px solid #0f64d2; - margin-left: 0px; - margin-right: 0px; - border-radius: 8px; -} - -QRadioButton::indicator:checked:pressed { - border: 1px solid #0f64d2; - margin-left: 0px; - margin-right: 0px; - border-radius: 8px; - background-color: #868686; - image: url(:/dark_scheme/icon_radiobutton_checked_pressed.png); -} - -QRadioButton::indicator:checked:disabled { - background-color: #707070; - image: url(:/dark_scheme/icon_radiobutton_checked_disabled.png); -} - -QProgressBar { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 7px; - height: 14px; - text-align: center; -} - -QProgressBar::chunk { - background-color: #3399ff; - border-radius: 7px; -} - -QGroupBox { - background-color: palette(window); - border: 1px solid #424242; - border-radius: 0px; - margin-top: 8px; -} - -QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top center; - margin: 0px 4px 0px 4px -} - -QGraphicsView, ImageViewBase, QFrame#imageViewFrame { - background-color: #282828; - background-attachment: scroll; - border: none; -} - -QGraphicsView { - margin: 1px 1px 1px 1px; -} - -QGroupBox#recentProjectsGroup { - background-color: transparent; - border: 1px solid #0f64d2; - border-radius: 0px; - margin-top: 14px; -} - -QGroupBox#recentProjectsGroup::title { - subcontrol-origin: margin; - subcontrol-position: top center; - margin: 0px 4px 0px 4px; -} - -QMenuBar { - background-color: palette(window); - border-bottom: 1px solid #383838; - margin-bottom: 1px; -} - -QMenuBar::item { - spacing: 2px; - padding: 1px 3px 1px; - background: transparent; - border-radius: 2px; - margin-bottom: 3px; -} - -QMenuBar::item:selected { - border: 1px solid #636363; - background-color: #474747; -} - -QMenuBar::item:pressed { - border: 1px solid #636363; - background-color: #383838; -} - -QMenu { - background-color: palette(base); -} - -QMenu::separator { - height: 1px; - background: #383838; - margin-left: 5px; - margin-right: 5px; -} - -QMenu::item { - padding: 1px 14px 1px 20px; - height: 24px; - margin: 1px 0px 1px 0px; -} - -QMenu::item:disabled { - background-color: #4d4d4d; - color: #989898; -} - -QMenu::item:selected { - background-color: palette(highlight); - color: palette(highlighted-text); -} - -QDockWidget::title { - background-color: #424242; - border: 1px solid #383838; - text-align: center; -} - -QDockWidget { - titlebar-close-icon: url(:/dark_scheme/icon_window_close.png); - titlebar-normal-icon: url(:/dark_scheme/icon_window_restore.png); - font-weight: bold; -} - -QDockWidget:disabled { - titlebar-close-icon: url(:/dark_scheme/icon_window_close_disabled.png); - titlebar-normal-icon: url(:/dark_scheme/icon_window_restore_disabled.png); - font-weight: bold; -} - -QDockWidget > QWidget { - border: 1px solid #383838; - border-top: none; - border-bottom: none; -} - -QDockWidget::close-button, QDockWidget::float-button { - subcontrol-origin: margin; - subcontrol-position: top right; - position: absolute; - top: 0px; - bottom: 0px; - width: 22px; - height: 22px; -} - -QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #4f4f4f; -} - -QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #383838; -} - -QScrollBar:vertical { - background: #494949; - border: none; - width: 17px; - margin: 0px; -} - -QScrollBar::handle:vertical { - background-color: #696969; - border: none; - min-height: 20px; - margin: 17px 2px 17px 2px; -} - -QScrollBar::add-line:vertical { - background: transparent; - height: 17px; - subcontrol-position: bottom; - subcontrol-origin: margin; - color: palette(window-text) -} - -QScrollBar::sub-line:vertical { - background: transparent; - height: 17px; - subcontrol-position: top; - subcontrol-origin: margin; - color: palette(window-text) -} - -QScrollBar:horizontal { - background: #494949; - border: none; - height: 17px; - margin: 0px; -} - -QScrollBar::handle:horizontal { - background-color: #696969; - border: none; - min-width: 20px; - margin: 2px 17px 2px 17px; -} - -QScrollBar::add-line:horizontal { - background: transparent; - width: 17px; - subcontrol-position: right; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:horizontal { - background: transparent; - width: 17px; - subcontrol-position: left; - subcontrol-origin: margin; -} - -QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { - background: #1a1a1a; -} - -QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { - width: 17px; - height: 17px; - background: transparent; - border: none; -} - -QScrollBar:left-arrow:horizontal { - image: url(:/dark_scheme/icon_scroll_bar_left_arrow.png); -} - -QScrollBar::right-arrow:horizontal { - image: url(:/dark_scheme/icon_scroll_bar_right_arrow.png); -} - -QScrollBar:down-arrow:vertical { - image: url(:/dark_scheme/icon_scroll_bar_down_arrow.png); -} - -QScrollBar::up-arrow:vertical { - image: url(:/dark_scheme/icon_scroll_bar_up_arrow.png); -} - -QTabWidget > QWidget { - qproperty-drawBase:0; -} - -QTabWidget::pane { - background-color: palette(window); - background-color: #424242; - qproperty-drawBase:0; -} - -QTabWidget::tab-bar { - top: 1px; -} - -QTabBar { - background-color: transparent; - border: none; -} - -QTabBar::tab { - padding: 5px 1px 5px 1px; - height: 14px; - width: 96px; - background-color: #424242; - border: 1px solid #383838; - font-weight: bold; -} - -QTabBar::tab:!first { - border-left: none; -} - -QTabBar::tab:hover { - background-color: #4f4f4f; -} - -QTabBar::tab:selected { - background-color: palette(window); - border-bottom: palette(window); -} - -output--TabbedImageView > QTabBar::tab { - padding: 1px 4px 1px 5px; - height: 96px; - width: 14px; -} - -output--TabbedImageView::tab-bar { - left: 0px; - right: 0px; - top: 0px; -} - -QComboBox { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 3px; - padding: 1px 0px 1px 8px; - height: 17px; - width: 86px; - selection-background-color: #3399ff; - selection-color: white; -} - -QAbstractSpinBox { - border: 1px solid #666666; - background-color: palette(base); - border-radius: 0px; - padding: 1px 3px 1px 3px; - height: 17px; - selection-background-color: #3399ff; - selection-color: white; -} - -QComboBox:disabled, QAbstractSpinBox:disabled { - border: 1px solid #5e5e5e; - background-color: #4d4d4d; -} - -QComboBox:hover, QAbstractSpinBox:focus { - border-color: #0f64d2; -} - -QComboBox:on { - border-color: #0f64d2; - background-color: #383838; -} - -QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: bottom right; - width: 18px; - background: transparent; - border: none; -} - -QComboBox::down-arrow { - image: url(:/dark_scheme/icon_down_arrow.png); -} - -QComboBox QAbstractItemView { - border: none; - padding: 1px 1px 1px 1px; - selection-background-color: #3399ff; - selection-color: white; -} - -QAbstractSpinBox::up-button { - subcontrol-origin: border; - subcontrol-position: top right; - padding-top: 4px; - padding-right: 1px; - width: 14px; - background: transparent; - border: none; -} - -QAbstractSpinBox::down-button { - subcontrol-origin: border; - subcontrol-position: bottom right; - padding-bottom: 4px; - padding-right: 1px; - width: 14px; - background: transparent; - border: none; -} - -QAbstractSpinBox::down-arrow { - image: url(:/dark_scheme/icon_down_arrow.png); -} - -QAbstractSpinBox::up-arrow { - image: url(:/dark_scheme/icon_up_arrow.png); -} - -QAbstractSpinBox::down-arrow:pressed { - image: url(:/dark_scheme/icon_down_arrow_pressed.png); -} - -QAbstractSpinBox::up-arrow:pressed { - image: url(:/dark_scheme/icon_up_arrow_pressed.png); -} - -QSlider:horizontal { - margin: 9px 1px 9px 1px; -} - -QSlider::groove:horizontal { - border: none; - height: 6px; - background-color: #757575; -} - -QSlider::handle:horizontal { - height: 19px; - width: 12px; - margin: -21px 0px; - image: url(:/dark_scheme/icon_slider_handle.png); -} - -QToolTip { - color: palette(window-text); - background-color: #424242; - border: 1px solid #383838; - border-radius: 0px; -} - -QStatusBar { - background-color: #424242; - color: palette(window-text); -} - -QTreeView, QTableView { - alternate-background-color: palette(window); - background: palette(base); - border: 1px solid #383838; -} - -QTreeView QHeaderView::section, QTableView QHeaderView::section { - background: #424242; - border-style: none; - border-right: 1px solid #383838; - border-bottom: 1px solid #383838; - padding-left: 3px; - padding-right: 3px; -} - -QTreeView::item:selected:disabled, QTableView::item:selected:disabled { - background: #5d5d5d; -} - -QTreeView::branch { - background-color: palette(base); -} - -QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/dark_scheme/icon_vline.png) 0; -} - -QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/dark_scheme/icon_branch_more.png) 0; -} - -QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/dark_scheme/icon_branch_end.png) 0; -} - -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url(:/dark_scheme/icon_branch_closed.png); -} - -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url(:/dark_scheme/icon_branch_open.png); -} \ No newline at end of file diff --git a/resources/light_scheme/icon_branch_end.png b/resources/light_scheme/icon_branch_end.png deleted file mode 100644 index aec7ef79a..000000000 Binary files a/resources/light_scheme/icon_branch_end.png and /dev/null differ diff --git a/resources/light_scheme/icon_branch_more.png b/resources/light_scheme/icon_branch_more.png deleted file mode 100644 index 2032670ae..000000000 Binary files a/resources/light_scheme/icon_branch_more.png and /dev/null differ diff --git a/resources/light_scheme/icon_slider_handle.png b/resources/light_scheme/icon_slider_handle.png deleted file mode 100644 index 164bf845d..000000000 Binary files a/resources/light_scheme/icon_slider_handle.png and /dev/null differ diff --git a/resources/light_scheme/icon_vline.png b/resources/light_scheme/icon_vline.png deleted file mode 100644 index c0b46b534..000000000 Binary files a/resources/light_scheme/icon_vline.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_close.png b/resources/light_scheme/icon_window_close.png deleted file mode 100644 index 403784d3b..000000000 Binary files a/resources/light_scheme/icon_window_close.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_close_disabled.png b/resources/light_scheme/icon_window_close_disabled.png deleted file mode 100644 index ae198320a..000000000 Binary files a/resources/light_scheme/icon_window_close_disabled.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_maximize.png b/resources/light_scheme/icon_window_maximize.png deleted file mode 100644 index 3b76b2954..000000000 Binary files a/resources/light_scheme/icon_window_maximize.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_maximize_disabled.png b/resources/light_scheme/icon_window_maximize_disabled.png deleted file mode 100644 index 0b25d3e25..000000000 Binary files a/resources/light_scheme/icon_window_maximize_disabled.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_minimize.png b/resources/light_scheme/icon_window_minimize.png deleted file mode 100644 index e1566132e..000000000 Binary files a/resources/light_scheme/icon_window_minimize.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_minimize_disabled.png b/resources/light_scheme/icon_window_minimize_disabled.png deleted file mode 100644 index ea2729381..000000000 Binary files a/resources/light_scheme/icon_window_minimize_disabled.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_restore.png b/resources/light_scheme/icon_window_restore.png deleted file mode 100644 index 7defc4bb2..000000000 Binary files a/resources/light_scheme/icon_window_restore.png and /dev/null differ diff --git a/resources/light_scheme/icon_window_restore_disabled.png b/resources/light_scheme/icon_window_restore_disabled.png deleted file mode 100644 index 8ef65bec0..000000000 Binary files a/resources/light_scheme/icon_window_restore_disabled.png and /dev/null differ diff --git a/resources/light_scheme/icon_branch_closed.png b/resources/light_scheme/icons/icon_branch_closed.png similarity index 100% rename from resources/light_scheme/icon_branch_closed.png rename to resources/light_scheme/icons/icon_branch_closed.png diff --git a/resources/light_scheme/icons/icon_branch_end.png b/resources/light_scheme/icons/icon_branch_end.png new file mode 100644 index 000000000..1305bd63d Binary files /dev/null and b/resources/light_scheme/icons/icon_branch_end.png differ diff --git a/resources/light_scheme/icons/icon_branch_more.png b/resources/light_scheme/icons/icon_branch_more.png new file mode 100644 index 000000000..304bb1230 Binary files /dev/null and b/resources/light_scheme/icons/icon_branch_more.png differ diff --git a/resources/light_scheme/icon_branch_open.png b/resources/light_scheme/icons/icon_branch_open.png similarity index 100% rename from resources/light_scheme/icon_branch_open.png rename to resources/light_scheme/icons/icon_branch_open.png diff --git a/resources/light_scheme/icon_checkbox_checked.png b/resources/light_scheme/icons/icon_checkbox_checked.png similarity index 100% rename from resources/light_scheme/icon_checkbox_checked.png rename to resources/light_scheme/icons/icon_checkbox_checked.png diff --git a/resources/light_scheme/icon_checkbox_checked_disabled.png b/resources/light_scheme/icons/icon_checkbox_checked_disabled.png similarity index 100% rename from resources/light_scheme/icon_checkbox_checked_disabled.png rename to resources/light_scheme/icons/icon_checkbox_checked_disabled.png diff --git a/resources/light_scheme/icon_checkbox_checked_pressed.png b/resources/light_scheme/icons/icon_checkbox_checked_pressed.png similarity index 100% rename from resources/light_scheme/icon_checkbox_checked_pressed.png rename to resources/light_scheme/icons/icon_checkbox_checked_pressed.png diff --git a/resources/light_scheme/icon_checkbox_indeterminate.png b/resources/light_scheme/icons/icon_checkbox_indeterminate.png similarity index 100% rename from resources/light_scheme/icon_checkbox_indeterminate.png rename to resources/light_scheme/icons/icon_checkbox_indeterminate.png diff --git a/resources/light_scheme/icon_checkbox_indeterminate_disabled.png b/resources/light_scheme/icons/icon_checkbox_indeterminate_disabled.png similarity index 100% rename from resources/light_scheme/icon_checkbox_indeterminate_disabled.png rename to resources/light_scheme/icons/icon_checkbox_indeterminate_disabled.png diff --git a/resources/light_scheme/icon_checkbox_indeterminate_pressed.png b/resources/light_scheme/icons/icon_checkbox_indeterminate_pressed.png similarity index 100% rename from resources/light_scheme/icon_checkbox_indeterminate_pressed.png rename to resources/light_scheme/icons/icon_checkbox_indeterminate_pressed.png diff --git a/resources/light_scheme/icon_close.png b/resources/light_scheme/icons/icon_close.png similarity index 100% rename from resources/light_scheme/icon_close.png rename to resources/light_scheme/icons/icon_close.png diff --git a/resources/light_scheme/icon_close_disabled.png b/resources/light_scheme/icons/icon_close_disabled.png similarity index 100% rename from resources/light_scheme/icon_close_disabled.png rename to resources/light_scheme/icons/icon_close_disabled.png diff --git a/resources/light_scheme/icon_down_arrow.png b/resources/light_scheme/icons/icon_down_arrow.png similarity index 100% rename from resources/light_scheme/icon_down_arrow.png rename to resources/light_scheme/icons/icon_down_arrow.png diff --git a/resources/light_scheme/icon_down_arrow_pressed.png b/resources/light_scheme/icons/icon_down_arrow_pressed.png similarity index 100% rename from resources/light_scheme/icon_down_arrow_pressed.png rename to resources/light_scheme/icons/icon_down_arrow_pressed.png diff --git a/resources/light_scheme/icon_radiobutton_checked.png b/resources/light_scheme/icons/icon_radiobutton_checked.png similarity index 100% rename from resources/light_scheme/icon_radiobutton_checked.png rename to resources/light_scheme/icons/icon_radiobutton_checked.png diff --git a/resources/light_scheme/icon_radiobutton_checked_disabled.png b/resources/light_scheme/icons/icon_radiobutton_checked_disabled.png similarity index 100% rename from resources/light_scheme/icon_radiobutton_checked_disabled.png rename to resources/light_scheme/icons/icon_radiobutton_checked_disabled.png diff --git a/resources/light_scheme/icon_radiobutton_checked_pressed.png b/resources/light_scheme/icons/icon_radiobutton_checked_pressed.png similarity index 100% rename from resources/light_scheme/icon_radiobutton_checked_pressed.png rename to resources/light_scheme/icons/icon_radiobutton_checked_pressed.png diff --git a/resources/light_scheme/icon_scroll_bar_down_arrow.png b/resources/light_scheme/icons/icon_scroll_bar_down_arrow.png similarity index 100% rename from resources/light_scheme/icon_scroll_bar_down_arrow.png rename to resources/light_scheme/icons/icon_scroll_bar_down_arrow.png diff --git a/resources/light_scheme/icon_scroll_bar_left_arrow.png b/resources/light_scheme/icons/icon_scroll_bar_left_arrow.png similarity index 100% rename from resources/light_scheme/icon_scroll_bar_left_arrow.png rename to resources/light_scheme/icons/icon_scroll_bar_left_arrow.png diff --git a/resources/light_scheme/icon_scroll_bar_right_arrow.png b/resources/light_scheme/icons/icon_scroll_bar_right_arrow.png similarity index 100% rename from resources/light_scheme/icon_scroll_bar_right_arrow.png rename to resources/light_scheme/icons/icon_scroll_bar_right_arrow.png diff --git a/resources/light_scheme/icon_scroll_bar_up_arrow.png b/resources/light_scheme/icons/icon_scroll_bar_up_arrow.png similarity index 100% rename from resources/light_scheme/icon_scroll_bar_up_arrow.png rename to resources/light_scheme/icons/icon_scroll_bar_up_arrow.png diff --git a/resources/light_scheme/icons/icon_slider_handle.png b/resources/light_scheme/icons/icon_slider_handle.png new file mode 100644 index 000000000..680f940d6 Binary files /dev/null and b/resources/light_scheme/icons/icon_slider_handle.png differ diff --git a/resources/light_scheme/icon_undock.png b/resources/light_scheme/icons/icon_undock.png similarity index 100% rename from resources/light_scheme/icon_undock.png rename to resources/light_scheme/icons/icon_undock.png diff --git a/resources/light_scheme/icon_undock_disabled.png b/resources/light_scheme/icons/icon_undock_disabled.png similarity index 100% rename from resources/light_scheme/icon_undock_disabled.png rename to resources/light_scheme/icons/icon_undock_disabled.png diff --git a/resources/light_scheme/icon_up_arrow.png b/resources/light_scheme/icons/icon_up_arrow.png similarity index 100% rename from resources/light_scheme/icon_up_arrow.png rename to resources/light_scheme/icons/icon_up_arrow.png diff --git a/resources/light_scheme/icon_up_arrow_pressed.png b/resources/light_scheme/icons/icon_up_arrow_pressed.png similarity index 100% rename from resources/light_scheme/icon_up_arrow_pressed.png rename to resources/light_scheme/icons/icon_up_arrow_pressed.png diff --git a/resources/light_scheme/icons/icon_vline.png b/resources/light_scheme/icons/icon_vline.png new file mode 100644 index 000000000..29cd2d11f Binary files /dev/null and b/resources/light_scheme/icons/icon_vline.png differ diff --git a/resources/light_scheme/icons/icon_window_close.png b/resources/light_scheme/icons/icon_window_close.png new file mode 100644 index 000000000..7ed831554 Binary files /dev/null and b/resources/light_scheme/icons/icon_window_close.png differ diff --git a/resources/light_scheme/icons/icon_window_close_disabled.png b/resources/light_scheme/icons/icon_window_close_disabled.png new file mode 100644 index 000000000..54bd5f935 Binary files /dev/null and b/resources/light_scheme/icons/icon_window_close_disabled.png differ diff --git a/resources/light_scheme/icons/icon_window_maximize.png b/resources/light_scheme/icons/icon_window_maximize.png new file mode 100644 index 000000000..854b329ed Binary files /dev/null and b/resources/light_scheme/icons/icon_window_maximize.png differ diff --git a/resources/light_scheme/icons/icon_window_maximize_disabled.png b/resources/light_scheme/icons/icon_window_maximize_disabled.png new file mode 100644 index 000000000..d56d31bda Binary files /dev/null and b/resources/light_scheme/icons/icon_window_maximize_disabled.png differ diff --git a/resources/light_scheme/icons/icon_window_minimize.png b/resources/light_scheme/icons/icon_window_minimize.png new file mode 100644 index 000000000..8621b25ba Binary files /dev/null and b/resources/light_scheme/icons/icon_window_minimize.png differ diff --git a/resources/light_scheme/icons/icon_window_minimize_disabled.png b/resources/light_scheme/icons/icon_window_minimize_disabled.png new file mode 100644 index 000000000..6d0fe2970 Binary files /dev/null and b/resources/light_scheme/icons/icon_window_minimize_disabled.png differ diff --git a/resources/light_scheme/icons/icon_window_restore.png b/resources/light_scheme/icons/icon_window_restore.png new file mode 100644 index 000000000..aa67ca2c7 Binary files /dev/null and b/resources/light_scheme/icons/icon_window_restore.png differ diff --git a/resources/light_scheme/icons/icon_window_restore_disabled.png b/resources/light_scheme/icons/icon_window_restore_disabled.png new file mode 100644 index 000000000..335c945bf Binary files /dev/null and b/resources/light_scheme/icons/icon_window_restore_disabled.png differ diff --git a/resources/light_scheme/qss/stylesheet.qss b/resources/light_scheme/qss/stylesheet.qss new file mode 100644 index 000000000..f763ff468 --- /dev/null +++ b/resources/light_scheme/qss/stylesheet.qss @@ -0,0 +1,666 @@ +QPushButton { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.5625em 0.0625em 0.5625em; + min-height: 1.0625em; + min-width: 3.125em; + text-align: center; +} + +QLineEdit, QListView { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.1875em 0.0625em 0.1875em; + min-height: 1.0625em; + min-width: 1.0625em; + text-align: center; + selection-background-color: #3399ff; + selection-color: white; +} + +QToolButton { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + text-align: center; +} + +QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { + border: 0.0625em solid #dedede; + background-color: #fafafa; +} + +QPushButton:default, QToolButton:default { + border-color: #4b4b4b; +} + +QPushButton:hover, QToolButton:hover, QPushButton:active:focus, QToolButton:active:focus, QLineEdit:active:focus { + border-color: #1e76e3; +} + +QPushButton:pressed, QToolButton:pressed, QPushButton:checked:active:focus, QToolButton:checked:active:focus { + border-color: #1e76e3; + background-color: #bfbfbf; +} + +QPushButton:checked, QToolButton:checked { + border-color: #89689c; + background-color: #bfbfbf; +} + +QPushButton:checked:disabled, QToolButton:checked:disabled { + border-color: #89689c; + background-color: #fafafa; +} + +QPushButton:flat, QToolButton:flat { + border: none; +} + +QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QRadioButton::indicator { + width: 0.875em; + height: 0.875em; + margin: 0.0625em; +} + +QMenu::indicator:non-exclusive, QMenu::indicator:exclusive { + width: 0.875em; + height: 0.875em; + margin: 0.0625em 0.0625em 0.0625em 0.1875em; +} + +QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked { + border: 0.0625em solid #cccccc; + margin: 0em; + background-color: palette(base); + border-radius: 0.1719em; +} + +QMenu::indicator:non-exclusive:unchecked { + border: 0.0625em solid #cccccc; + margin: 0em 0em 0em 0.125em; + background-color: palette(base); + border-radius: 0.1719em; +} + +QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:active:focus, QTreeView::indicator:unchecked:active:focus, QTableView::indicator:unchecked:active:focus, QGroupBox::indicator:unchecked:active:focus { + border-color: #1e76e3; +} + +QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { + border-color: #1e76e3; + background-color: #b5b5b5; +} + +QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { + border-color: #dedede; + background-color: #fafafa; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, +QMenu::indicator:non-exclusive:checked { + background-color: #6c6c6c; + border-radius: 0.1094em; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QMenu::indicator:non-exclusive:checked { + image: url(:/light_scheme/icons/icon_checkbox_checked.png); +} + +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate.png); +} + +QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, +QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, +QGroupBox::indicator:indeterminate:hover, +QCheckBox::indicator:checked:active:focus, QTreeView::indicator:checked:active:focus, QTableView::indicator:checked:active:focus, QGroupBox::indicator:checked:active:focus, +QCheckBox::indicator:indeterminate:active:focus, QTreeView::indicator:indeterminate:active:focus, QTableView::indicator:indeterminate:active:focus, +QGroupBox::indicator:indeterminate:active:focus { + border: 0.0625em solid #1e76e3; + margin: 0em; + border-radius: 0.1719em; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + border: 0.0625em solid #1e76e3; + margin: 0em; + border-radius: 0.1719em; + background-color: #555555; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { + image: url(:/light_scheme/icons/icon_checkbox_checked_pressed.png); +} + +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate_pressed.png); +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + background-color: #b5b5b5; +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { + image: url(:/light_scheme/icons/icon_checkbox_checked_disabled.png); +} + +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate_disabled.png); +} + +QRadioButton::indicator:unchecked { + border: 0.0625em solid #cccccc; + margin: 0em; + background-color: palette(base); + border-radius: 0.4844em; +} + +QMenu::indicator:exclusive:unchecked { + border: 0.0625em solid #cccccc; + margin: 0em 0em 0em 0.125em; + background-color: palette(base); + border-radius: 0.4844em; +} + +QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:active:focus { + border-color: #1e76e3; +} + +QRadioButton::indicator:unchecked:pressed { + border-color: #1e76e3; + background-color: #b5b5b5; +} + +QRadioButton::indicator:unchecked:disabled { + border-color: #dedede; + background-color: #fafafa; +} + +QRadioButton::indicator:checked, +QMenu::indicator:exclusive:checked { + background-color: #6c6c6c; + border-radius: 0.4219em; + image: url(:/light_scheme/icons/icon_radiobutton_checked.png); +} + +QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:active:focus { + border: 0.0625em solid #1e76e3; + margin: 0em; + border-radius: 0.4844em; +} + +QRadioButton::indicator:checked:pressed { + border: 0.0625em solid #1e76e3; + margin: 0em; + border-radius: 0.4844em; + background-color: #555555; + image: url(:/light_scheme/icons/icon_radiobutton_checked_pressed.png); +} + +QRadioButton::indicator:checked:disabled { + background-color: #b5b5b5; + image: url(:/light_scheme/icons/icon_radiobutton_checked_disabled.png); +} + +QProgressBar { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0.4219em; + height: 0.875em; + text-align: center; +} + +QProgressBar::chunk { + background-color: #5d9bf3; + border-radius: 0.4219em; +} + +QGroupBox { + background-color: palette(window); + border: 0.0625em solid #d1d1d1; + border-radius: 0em; + margin-top: 0.5em; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.25em 0em 0.25em +} + +QGraphicsView, ImageViewBase, QFrame#imageViewFrame { + background-color: #939393; + background-attachment: scroll; + border: none; +} + +QGraphicsView { + margin: 0.0625em 0.0625em 0.0625em 0.0625em; +} + +QGroupBox#recentProjectsGroup { + background-color: transparent; + border: 0.0625em solid #1e76e3; + border-radius: 0em; + margin-top: 0.875em; + color: #000000; +} + +QGroupBox#recentProjectsGroup::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.25em 0em 0.25em; +} + +QMenuBar { + background-color: palette(window); + border-bottom: 0.0625em solid #bfbfbf; + margin-bottom: 0.0625em; +} + +QMenuBar::item { + spacing: 0.125em; + padding: 0.0625em 0.1875em 0.0625em; + background: transparent; + border-radius: 0.1094em; + margin-bottom: 0.1875em; +} + +QMenuBar::item:selected { + border: 0.0625em solid #dbdbdb; + background-color: #fafafa; +} + +QMenuBar::item:pressed { + border: 0.0625em solid #dbdbdb; + background-color: #bfbfbf; +} + +QMenu { + background-color: palette(base); +} + +QMenu::separator { + height: 0.0625em; + background: #bfbfbf; + margin-left: 0.3125em; + margin-right: 0.3125em; +} + +QMenu::item { + padding: 0.0625em 0.875em 0.0625em 1.25em; + height: 1.5em; + margin: 0.0625em 0em 0.0625em 0em; +} + +QMenu::item:disabled { + background-color: #fafafa; + color: #909090; +} + +QMenu::item:selected { + background-color: palette(highlight); + color: palette(highlighted-text); +} + +QDockWidget::title { + background-color: #dbdbdb; + border: 0.0625em solid #cccccc; + text-align: center; +} + +QDockWidget { + titlebar-close-icon: url(:/light_scheme/icons/icon_window_close.png); + titlebar-normal-icon: url(:/light_scheme/icons/icon_window_restore.png); +} + +QDockWidget:disabled { + titlebar-close-icon: url(:/light_scheme/icons/icon_window_close_disabled.png); + titlebar-normal-icon: url(:/light_scheme/icons/icon_window_restore_disabled.png); +} + +QDockWidget > QWidget { + border: 0.0625em solid #cccccc; + border-top: none; + border-bottom: none; +} + +QDockWidget::close-button, QDockWidget::float-button { + padding: 0em; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover, QDockWidget::close-button:active:focus, QDockWidget::float-button:active:focus { + background-color: #ebebeb; +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #bfbfbf; +} + +QScrollBar:vertical { + background: #e3e3e3; + border: none; + width: 1.0625em; + margin: 0em; +} + +QScrollBar::handle:vertical { + background-color: #bdbdbd; + border: none; + min-height: 1.25em; + margin: 1.0625em 0.125em 1.0625em 0.125em; +} + +QScrollBar::add-line:vertical { + background: transparent; + height: 1.0625em; + subcontrol-position: bottom; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar::sub-line:vertical { + background: transparent; + height: 1.0625em; + subcontrol-position: top; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar:horizontal { + background: #e3e3e3; + border: none; + height: 1.0625em; + margin: 0em; +} + +QScrollBar::handle:horizontal { + background-color: #bdbdbd; + border: none; + min-width: 1.25em; + margin: 0.125em 1.0625em 0.125em 1.0625em; +} + +QScrollBar::add-line:horizontal { + background: transparent; + width: 1.0625em; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + background: transparent; + width: 1.0625em; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { + background: #d9d9d9; +} + +QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { + width: 1.0625em; + height: 1.0625em; + background: transparent; + border: none; +} + +QScrollBar:left-arrow:horizontal { + image: url(:/light_scheme/icons/icon_scroll_bar_left_arrow.png); +} + +QScrollBar::right-arrow:horizontal { + image: url(:/light_scheme/icons/icon_scroll_bar_right_arrow.png); +} + +QScrollBar:down-arrow:vertical { + image: url(:/light_scheme/icons/icon_scroll_bar_down_arrow.png); +} + +QScrollBar::up-arrow:vertical { + image: url(:/light_scheme/icons/icon_scroll_bar_up_arrow.png); +} + +QTabWidget > QWidget { + qproperty-drawBase:0; +} + +QTabWidget::pane { + background-color: palette(window); + border: 0.0625em solid #cccccc; + qproperty-drawBase:0; +} + +QTabWidget::tab-bar { + top: 0.0625em; +} + +QTabBar { + background-color: transparent; + border: none; +} + +QTabBar::tab { + padding: 0.1875em 0.5625em 0.1875em 0.5625em; + min-height: 1.0625em; + min-width: 1.0625em; + background-color: #dbdbdb; + border: 0.0625em solid #cccccc; +} + +QTabBar::tab:!first { + border-left-width: 0em; +} + +QTabBar::tab:hover { + background-color: #ebebeb; +} + +QTabBar::tab:selected { + background-color: palette(window); + border-bottom-width: 0em; + padding-bottom: 0.25em; +} + +output--TabbedImageView::tab-bar { + left: 0em; + right: 0em; + top: 0em; +} + +output--TabbedImageView > QTabBar::tab { + padding: 0.5625em 0.1875em 0.5625em 0.1875em; + min-height: 1.0625em; + min-width: 1.0625em; +} + +output--TabbedImageView > QTabBar::tab:!first { + border-left-width: 0.0625em; + border-top-width: 0em; +} + +output--TabbedImageView > QTabBar::tab:selected { + border-bottom-width: 0.0625em; + padding-bottom: 0.5625em; + border-left-width: 0em; + padding-left: 0.25em; +} + +QComboBox { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0.1719em; + padding: 0.0625em 0em 0.0625em 0.5em; + min-height: 1.0625em; + min-width: 1.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox { + border: 0.0625em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0625em 0.1875em 0.0625em 0.1875em; + min-height: 1.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QComboBox:disabled, QAbstractSpinBox:disabled { + border: 0.0625em solid #dedede; + background-color: #fafafa; +} + +QComboBox:hover, QComboBox:active:focus, QAbstractSpinBox:active:focus { + border-color: #1e76e3; +} + +QComboBox:on { + border-color: #1e76e3; + background-color: #bfbfbf; +} + +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: bottom right; + width: 1.125em; + background: transparent; + border: none; +} + +QComboBox::down-arrow { + image: url(:/light_scheme/icons/icon_down_arrow.png); +} + +QComboBox QAbstractItemView { + border: none; + padding: 0.0625em 0.0625em 0.0625em 0.0625em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + padding-top: 0.25em; + padding-right: 0.0625em; + width: 0.875em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + padding-bottom: 0.25em; + padding-right: 0.0625em; + width: 0.875em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-arrow { + image: url(:/light_scheme/icons/icon_down_arrow.png); +} + +QAbstractSpinBox::up-arrow { + image: url(:/light_scheme/icons/icon_up_arrow.png); +} + +QAbstractSpinBox::down-arrow:pressed { + image: url(:/light_scheme/icons/icon_down_arrow_pressed.png); +} + +QAbstractSpinBox::up-arrow:pressed { + image: url(:/light_scheme/icons/icon_up_arrow_pressed.png); +} + +QSlider:horizontal { + margin: 0.5625em 0.0625em 0.5625em 0.0625em; +} + +QSlider::groove:horizontal { + border: none; + height: 0.375em; + background-color: #cccccc; +} + +QSlider::handle:horizontal { + height: 1.1875em; + width: 0.75em; + margin: -1.3125em 0em; + image: url(:/light_scheme/icons/icon_slider_handle.png); +} + +QSlider::handle:disabled { + image: none; +} + +QToolTip { + color: #000000; + background-color: #ffffcd; + border: 0.0625em solid #000000; + border-radius: 0em; +} + +QStatusBar { + background-color: #dbdbdb; + color: palette(window-text); +} + +QTreeView, QTableView { + alternate-background-color: palette(window); + background: palette(base); + border: 0.0625em solid #cccccc; +} + +QTreeView QHeaderView::section, QTableView QHeaderView::section { + background: #dbdbdb; + border-style: none; + border-right: 0.0625em solid #cccccc; + border-bottom: 0.0625em solid #cccccc; + padding-left: 0.1875em; + padding-right: 0.1875em; +} + +QTreeView::item:selected:disabled, QTableView::item:selected:disabled { + background: #dfdfdf; +} + +QTreeView::branch { + background-color: palette(base); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/light_scheme/icons/icon_vline.png) 0; +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/light_scheme/icons/icon_branch_more.png) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/light_scheme/icons/icon_branch_end.png) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(:/light_scheme/icons/icon_branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(:/light_scheme/icons/icon_branch_open.png); +} \ No newline at end of file diff --git a/resources/light_scheme/qss/stylesheet_win.qss b/resources/light_scheme/qss/stylesheet_win.qss new file mode 100644 index 000000000..8ff592918 --- /dev/null +++ b/resources/light_scheme/qss/stylesheet_win.qss @@ -0,0 +1,666 @@ +QPushButton { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.6923em 0.0769em 0.6923em; + min-height: 1.3077em; + min-width: 3.8462em; + text-align: center; +} + +QLineEdit, QListView { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.2308em 0.0769em 0.2308em; + min-height: 1.3077em; + min-width: 1.3077em; + text-align: center; + selection-background-color: #3399ff; + selection-color: white; +} + +QToolButton { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + text-align: center; +} + +QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { + border: 0.0769em solid #dedede; + background-color: #fafafa; +} + +QPushButton:default, QToolButton:default { + border-color: #4b4b4b; +} + +QPushButton:hover, QToolButton:hover, QPushButton:active:focus, QToolButton:active:focus, QLineEdit:active:focus { + border-color: #1e76e3; +} + +QPushButton:pressed, QToolButton:pressed, QPushButton:checked:active:focus, QToolButton:checked:active:focus { + border-color: #1e76e3; + background-color: #bfbfbf; +} + +QPushButton:checked, QToolButton:checked { + border-color: #89689c; + background-color: #bfbfbf; +} + +QPushButton:checked:disabled, QToolButton:checked:disabled { + border-color: #89689c; + background-color: #fafafa; +} + +QPushButton:flat, QToolButton:flat { + border: none; +} + +QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QRadioButton::indicator { + width: 1.0769em; + height: 1.0769em; + margin: 0.0769em; +} + +QMenu::indicator:non-exclusive, QMenu::indicator:exclusive { + width: 1.0769em; + height: 1.0769em; + margin: 0.0769em 0.0769em 0.0769em 0.2308em; +} + +QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked { + border: 0.0769em solid #cccccc; + margin: 0em; + background-color: palette(base); + border-radius: 0.2115em; +} + +QMenu::indicator:non-exclusive:unchecked { + border: 0.0769em solid #cccccc; + margin: 0em 0em 0em 0.1538em; + background-color: palette(base); + border-radius: 0.2115em; +} + +QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:active:focus, QTreeView::indicator:unchecked:active:focus, QTableView::indicator:unchecked:active:focus, QGroupBox::indicator:unchecked:active:focus { + border-color: #1e76e3; +} + +QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { + border-color: #1e76e3; + background-color: #b5b5b5; +} + +QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { + border-color: #dedede; + background-color: #fafafa; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, +QMenu::indicator:non-exclusive:checked { + background-color: #6c6c6c; + border-radius: 0.1346em; +} + +QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, +QMenu::indicator:non-exclusive:checked { + image: url(:/light_scheme/icons/icon_checkbox_checked.png); +} + +QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate.png); +} + +QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, +QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, +QGroupBox::indicator:indeterminate:hover, +QCheckBox::indicator:checked:active:focus, QTreeView::indicator:checked:active:focus, QTableView::indicator:checked:active:focus, QGroupBox::indicator:checked:active:focus, +QCheckBox::indicator:indeterminate:active:focus, QTreeView::indicator:indeterminate:active:focus, QTableView::indicator:indeterminate:active:focus, +QGroupBox::indicator:indeterminate:active:focus { + border: 0.0769em solid #1e76e3; + margin: 0em; + border-radius: 0.2115em; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + border: 0.0769em solid #1e76e3; + margin: 0em; + border-radius: 0.2115em; + background-color: #555555; +} + +QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { + image: url(:/light_scheme/icons/icon_checkbox_checked_pressed.png); +} + +QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate_pressed.png); +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + background-color: #b5b5b5; +} + +QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { + image: url(:/light_scheme/icons/icon_checkbox_checked_disabled.png); +} + +QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { + image: url(:/light_scheme/icons/icon_checkbox_indeterminate_disabled.png); +} + +QRadioButton::indicator:unchecked { + border: 0.0769em solid #cccccc; + margin: 0em; + background-color: palette(base); + border-radius: 0.5962em; +} + +QMenu::indicator:exclusive:unchecked { + border: 0.0769em solid #cccccc; + margin: 0em 0em 0em 0.1538em; + background-color: palette(base); + border-radius: 0.5962em; +} + +QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:active:focus { + border-color: #1e76e3; +} + +QRadioButton::indicator:unchecked:pressed { + border-color: #1e76e3; + background-color: #b5b5b5; +} + +QRadioButton::indicator:unchecked:disabled { + border-color: #dedede; + background-color: #fafafa; +} + +QRadioButton::indicator:checked, +QMenu::indicator:exclusive:checked { + background-color: #6c6c6c; + border-radius: 0.5192em; + image: url(:/light_scheme/icons/icon_radiobutton_checked.png); +} + +QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:active:focus { + border: 0.0769em solid #1e76e3; + margin: 0em; + border-radius: 0.5962em; +} + +QRadioButton::indicator:checked:pressed { + border: 0.0769em solid #1e76e3; + margin: 0em; + border-radius: 0.5962em; + background-color: #555555; + image: url(:/light_scheme/icons/icon_radiobutton_checked_pressed.png); +} + +QRadioButton::indicator:checked:disabled { + background-color: #b5b5b5; + image: url(:/light_scheme/icons/icon_radiobutton_checked_disabled.png); +} + +QProgressBar { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0.5192em; + height: 1.0769em; + text-align: center; +} + +QProgressBar::chunk { + background-color: #5d9bf3; + border-radius: 0.5192em; +} + +QGroupBox { + background-color: palette(window); + border: 0.0769em solid #d1d1d1; + border-radius: 0em; + margin-top: 0.6154em; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.3077em 0em 0.3077em +} + +QGraphicsView, ImageViewBase, QFrame#imageViewFrame { + background-color: #939393; + background-attachment: scroll; + border: none; +} + +QGraphicsView { + margin: 0.0769em 0.0769em 0.0769em 0.0769em; +} + +QGroupBox#recentProjectsGroup { + background-color: transparent; + border: 0.0769em solid #1e76e3; + border-radius: 0em; + margin-top: 1.0769em; + color: #000000; +} + +QGroupBox#recentProjectsGroup::title { + subcontrol-origin: margin; + subcontrol-position: top center; + margin: 0em 0.3077em 0em 0.3077em; +} + +QMenuBar { + background-color: palette(window); + border-bottom: 0.0769em solid #bfbfbf; + margin-bottom: 0.0769em; +} + +QMenuBar::item { + spacing: 0.1538em; + padding: 0.0769em 0.2308em 0.0769em; + background: transparent; + border-radius: 0.1346em; + margin-bottom: 0.2308em; +} + +QMenuBar::item:selected { + border: 0.0769em solid #dbdbdb; + background-color: #fafafa; +} + +QMenuBar::item:pressed { + border: 0.0769em solid #dbdbdb; + background-color: #bfbfbf; +} + +QMenu { + background-color: palette(base); +} + +QMenu::separator { + height: 0.0769em; + background: #bfbfbf; + margin-left: 0.3846em; + margin-right: 0.3846em; +} + +QMenu::item { + padding: 0.0769em 1.0769em 0.0769em 1.5385em; + height: 1.8462em; + margin: 0.0769em 0em 0.0769em 0em; +} + +QMenu::item:disabled { + background-color: #fafafa; + color: #909090; +} + +QMenu::item:selected { + background-color: palette(highlight); + color: palette(highlighted-text); +} + +QDockWidget::title { + background-color: #dbdbdb; + border: 0.0769em solid #cccccc; + text-align: center; +} + +QDockWidget { + titlebar-close-icon: url(:/light_scheme/icons/icon_window_close.png); + titlebar-normal-icon: url(:/light_scheme/icons/icon_window_restore.png); +} + +QDockWidget:disabled { + titlebar-close-icon: url(:/light_scheme/icons/icon_window_close_disabled.png); + titlebar-normal-icon: url(:/light_scheme/icons/icon_window_restore_disabled.png); +} + +QDockWidget > QWidget { + border: 0.0769em solid #cccccc; + border-top: none; + border-bottom: none; +} + +QDockWidget::close-button, QDockWidget::float-button { + padding: 0em; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover, QDockWidget::close-button:active:focus, QDockWidget::float-button:active:focus { + background-color: #ebebeb; +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #bfbfbf; +} + +QScrollBar:vertical { + background: #e3e3e3; + border: none; + width: 1.3077em; + margin: 0em; +} + +QScrollBar::handle:vertical { + background-color: #bdbdbd; + border: none; + min-height: 1.5385em; + margin: 1.3077em 0.1538em 1.3077em 0.1538em; +} + +QScrollBar::add-line:vertical { + background: transparent; + height: 1.3077em; + subcontrol-position: bottom; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar::sub-line:vertical { + background: transparent; + height: 1.3077em; + subcontrol-position: top; + subcontrol-origin: margin; + color: palette(window-text) +} + +QScrollBar:horizontal { + background: #e3e3e3; + border: none; + height: 1.3077em; + margin: 0em; +} + +QScrollBar::handle:horizontal { + background-color: #bdbdbd; + border: none; + min-width: 1.5385em; + margin: 0.1538em 1.3077em 0.1538em 1.3077em; +} + +QScrollBar::add-line:horizontal { + background: transparent; + width: 1.3077em; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + background: transparent; + width: 1.3077em; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { + background: #d9d9d9; +} + +QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { + width: 1.3077em; + height: 1.3077em; + background: transparent; + border: none; +} + +QScrollBar:left-arrow:horizontal { + image: url(:/light_scheme/icons/icon_scroll_bar_left_arrow.png); +} + +QScrollBar::right-arrow:horizontal { + image: url(:/light_scheme/icons/icon_scroll_bar_right_arrow.png); +} + +QScrollBar:down-arrow:vertical { + image: url(:/light_scheme/icons/icon_scroll_bar_down_arrow.png); +} + +QScrollBar::up-arrow:vertical { + image: url(:/light_scheme/icons/icon_scroll_bar_up_arrow.png); +} + +QTabWidget > QWidget { + qproperty-drawBase:0; +} + +QTabWidget::pane { + background-color: palette(window); + border: 0.0769em solid #cccccc; + qproperty-drawBase:0; +} + +QTabWidget::tab-bar { + top: 0.0769em; +} + +QTabBar { + background-color: transparent; + border: none; +} + +QTabBar::tab { + padding: 0.2308em 0.6923em 0.2308em 0.6923em; + min-height: 1.3077em; + min-width: 1.3077em; + background-color: #dbdbdb; + border: 0.0769em solid #cccccc; +} + +QTabBar::tab:!first { + border-left-width: 0em; +} + +QTabBar::tab:hover { + background-color: #ebebeb; +} + +QTabBar::tab:selected { + background-color: palette(window); + border-bottom-width: 0em; + padding-bottom: 0.3077em; +} + +output--TabbedImageView::tab-bar { + left: 0em; + right: 0em; + top: 0em; +} + +output--TabbedImageView > QTabBar::tab { + padding: 0.6923em 0.2308em 0.6923em 0.2308em; + min-height: 1.3077em; + min-width: 1.3077em; +} + +output--TabbedImageView > QTabBar::tab:!first { + border-left-width: 0.0769em; + border-top-width: 0em; +} + +output--TabbedImageView > QTabBar::tab:selected { + border-bottom-width: 0.0769em; + padding-bottom: 0.6923em; + border-left-width: 0em; + padding-left: 0.3077em; +} + +QComboBox { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0.2115em; + padding: 0.0769em 0em 0.0769em 0.6154em; + min-height: 1.3077em; + min-width: 1.3077em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox { + border: 0.0769em solid #cccccc; + background-color: palette(base); + border-radius: 0em; + padding: 0.0769em 0.2308em 0.0769em 0.2308em; + min-height: 1.3077em; + selection-background-color: #3399ff; + selection-color: white; +} + +QComboBox:disabled, QAbstractSpinBox:disabled { + border: 0.0769em solid #dedede; + background-color: #fafafa; +} + +QComboBox:hover, QComboBox:active:focus, QAbstractSpinBox:active:focus { + border-color: #1e76e3; +} + +QComboBox:on { + border-color: #1e76e3; + background-color: #bfbfbf; +} + +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: bottom right; + width: 1.3846em; + background: transparent; + border: none; +} + +QComboBox::down-arrow { + image: url(:/light_scheme/icons/icon_down_arrow.png); +} + +QComboBox QAbstractItemView { + border: none; + padding: 0.0769em 0.0769em 0.0769em 0.0769em; + selection-background-color: #3399ff; + selection-color: white; +} + +QAbstractSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + padding-top: 0.3077em; + padding-right: 0.0769em; + width: 1.0769em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + padding-bottom: 0.3077em; + padding-right: 0.0769em; + width: 1.0769em; + background: transparent; + border: none; +} + +QAbstractSpinBox::down-arrow { + image: url(:/light_scheme/icons/icon_down_arrow.png); +} + +QAbstractSpinBox::up-arrow { + image: url(:/light_scheme/icons/icon_up_arrow.png); +} + +QAbstractSpinBox::down-arrow:pressed { + image: url(:/light_scheme/icons/icon_down_arrow_pressed.png); +} + +QAbstractSpinBox::up-arrow:pressed { + image: url(:/light_scheme/icons/icon_up_arrow_pressed.png); +} + +QSlider:horizontal { + margin: 0.6923em 0.0769em 0.6923em 0.0769em; +} + +QSlider::groove:horizontal { + border: none; + height: 0.4615em; + background-color: #cccccc; +} + +QSlider::handle:horizontal { + height: 1.4615em; + width: 0.9231em; + margin: -1.6154em 0em; + image: url(:/light_scheme/icons/icon_slider_handle.png); +} + +QSlider::handle:disabled { + image: none; +} + +QToolTip { + color: #000000; + background-color: #ffffcd; + border: 0.0769em solid #000000; + border-radius: 0em; +} + +QStatusBar { + background-color: #dbdbdb; + color: palette(window-text); +} + +QTreeView, QTableView { + alternate-background-color: palette(window); + background: palette(base); + border: 0.0769em solid #cccccc; +} + +QTreeView QHeaderView::section, QTableView QHeaderView::section { + background: #dbdbdb; + border-style: none; + border-right: 0.0769em solid #cccccc; + border-bottom: 0.0769em solid #cccccc; + padding-left: 0.2308em; + padding-right: 0.2308em; +} + +QTreeView::item:selected:disabled, QTableView::item:selected:disabled { + background: #dfdfdf; +} + +QTreeView::branch { + background-color: palette(base); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/light_scheme/icons/icon_vline.png) 0; +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/light_scheme/icons/icon_branch_more.png) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/light_scheme/icons/icon_branch_end.png) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(:/light_scheme/icons/icon_branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(:/light_scheme/icons/icon_branch_open.png); +} \ No newline at end of file diff --git a/resources/light_scheme/stylesheet.qss b/resources/light_scheme/stylesheet.qss deleted file mode 100644 index a98040c8f..000000000 --- a/resources/light_scheme/stylesheet.qss +++ /dev/null @@ -1,634 +0,0 @@ -QPushButton, QLineEdit, QListView { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 0px; - padding: 1px 3px 1px 3px; - height: 17px; - width: 78px; - text-align: center; - selection-background-color: #3399ff; - selection-color: white; -} - -QToolButton { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 0px; - text-align: center; -} - -QPushButton:disabled, QLineEdit:disabled, QListView:disabled, QToolButton:disabled { - border: 1px solid #dedede; - background-color: #fafafa; -} - -QPushButton:default, QToolButton:default { - border-color: #4b4b4b; -} - -QPushButton:hover, QToolButton:hover, QLineEdit:focus { - border-color: #1e76e3; -} - -QPushButton:pressed, QToolButton:pressed { - border-color: #1e76e3; - background-color: #bfbfbf; -} - -QPushButton:checked, QToolButton:checked { - border-color: #89689c; - background-color: #bfbfbf; -} - -QPushButton:checked:disabled, QToolButton:checked:disabled { - border-color: #89689c; - background-color: #fafafa; -} - -QPushButton:flat, QToolButton:flat { - border: none; -} - -QCheckBox::indicator, QTreeView::indicator, QTableView::indicator, QGroupBox::indicator, QMenu::indicator:non-exclusive, -QRadioButton::indicator, QMenu::indicator:exclusive { - width: 14px; - height: 14px; - margin: 1px 0px 1px 0px; -} - -QCheckBox::indicator:unchecked, QTreeView::indicator:unchecked, QTableView::indicator:unchecked, QGroupBox::indicator:unchecked, -QMenu::indicator:non-exclusive:unchecked { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 3px; -} - -QCheckBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QTableView::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border-color: #1e76e3; -} - -QCheckBox::indicator:unchecked:pressed, QTreeView::indicator:unchecked:pressed, QTableView::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:pressed { - border-color: #1e76e3; - background-color: #b5b5b5; -} - -QCheckBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QTableView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - border: 1px solid #dedede; - background-color: #fafafa; -} - -QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, -QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate, -QMenu::indicator:non-exclusive:checked { - border: none; - margin-left: 1px; - margin-right: 1px; - background-color: #6c6c6c; - border-radius: 2px; -} - -QCheckBox::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked, QGroupBox::indicator:checked, -QMenu::indicator:non-exclusive:checked { - image: url(:/light_scheme/icon_checkbox_checked.png); -} - -QCheckBox::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate, QGroupBox::indicator:indeterminate { - image: url(:/light_scheme/icon_checkbox_indeterminate.png); -} - -QCheckBox::indicator:checked:hover, QTreeView::indicator:checked:hover, QTableView::indicator:checked:hover, QGroupBox::indicator:checked:hover, -QCheckBox::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:hover, QTableView::indicator:indeterminate:hover, QGroupBox::indicator:indeterminate:hover { - border: 1px solid #1e76e3; - margin-left: 0px; - margin-right: 0px; - border-radius: 3px; -} - -QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed, -QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { - border: 1px solid #1e76e3; - margin-left: 0px; - margin-right: 0px; - border-radius: 3px; - background-color: #555555; -} - -QCheckBox::indicator:checked:pressed, QTreeView::indicator:checked:pressed, QTableView::indicator:checked:pressed, QGroupBox::indicator:checked:pressed { - image: url(:/light_scheme/icon_checkbox_checked_pressed.png); -} - -QCheckBox::indicator:indeterminate:pressed, QTreeView::indicator:indeterminate:pressed, QTableView::indicator:indeterminate:pressed, QGroupBox::indicator:indeterminate:pressed { - image: url(:/light_scheme/icon_checkbox_indeterminate_pressed.png); -} - -QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled, -QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { - background-color: #b5b5b5; -} - -QCheckBox::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(:/light_scheme/icon_checkbox_checked_disabled.png); -} - -QCheckBox::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { - image: url(:/light_scheme/icon_checkbox_indeterminate_disabled.png); -} - -QRadioButton::indicator:unchecked, -QMenu::indicator:exclusive:unchecked { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 8px; -} - -QRadioButton::indicator:unchecked:hover { - border-color: #1e76e3; -} - -QRadioButton::indicator:unchecked:pressed { - border-color: #1e76e3; - background-color: #b5b5b5; -} - -QRadioButton::indicator:unchecked:disabled { - border: 1px solid #dedede; - background-color: #fafafa; -} - -QRadioButton::indicator:checked, -QMenu::indicator:exclusive:checked { - border: none; - margin-left: 1px; - margin-right: 1px; - background-color: #6c6c6c; - border-radius: 7px; - image: url(:/light_scheme/icon_radiobutton_checked.png); -} - -QRadioButton::indicator:checked:hover { - border: 1px solid #1e76e3; - margin-left: 0px; - margin-right: 0px; - border-radius: 8px; -} - -QRadioButton::indicator:checked:pressed { - border: 1px solid #1e76e3; - margin-left: 0px; - margin-right: 0px; - border-radius: 8px; - background-color: #555555; - image: url(:/light_scheme/icon_radiobutton_checked_pressed.png); -} - -QRadioButton::indicator:checked:disabled { - background-color: #b5b5b5; - image: url(:/light_scheme/icon_radiobutton_checked_disabled.png); -} - -QProgressBar { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 7px; - height: 14px; - text-align: center; -} - -QProgressBar::chunk { - background-color: #5d9bf3; - border-radius: 7px; -} - -QGroupBox { - background-color: palette(window); - border: 1px solid #d1d1d1; - border-radius: 0px; - margin-top: 8px; -} - -QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top center; - margin: 0px 4px 0px 4px -} - -QGraphicsView, ImageViewBase, QFrame#imageViewFrame { - background-color: #939393; - background-attachment: scroll; - border: none; -} - -QGraphicsView { - margin: 1px 1px 1px 1px; -} - -QGroupBox#recentProjectsGroup { - background-color: transparent; - border: 1px solid #1e76e3; - border-radius: 0px; - margin-top: 14px; - color: #000000; -} - -QGroupBox#recentProjectsGroup::title { - subcontrol-origin: margin; - subcontrol-position: top center; - margin: 0px 4px 0px 4px; -} - -QMenuBar { - background-color: palette(window); - border-bottom: 1px solid #bfbfbf; - margin-bottom: 1px; -} - -QMenuBar::item { - spacing: 2px; - padding: 1px 3px 1px; - background: transparent; - border-radius: 2px; - margin-bottom: 3px; -} - -QMenuBar::item:selected { - border: 1px solid #dbdbdb; - background-color: #fafafa; -} - -QMenuBar::item:pressed { - border: 1px solid #dbdbdb; - background-color: #bfbfbf; -} - -QMenu { - background-color: palette(base); -} - -QMenu::separator { - height: 1px; - background: #bfbfbf; - margin-left: 5px; - margin-right: 5px; -} - -QMenu::item { - padding: 1px 14px 1px 20px; - height: 24px; - margin: 1px 0px 1px 0px; -} - -QMenu::item:disabled { - background-color: #fafafa; - color: #909090; -} - -QMenu::item:selected { - background-color: palette(highlight); - color: palette(highlighted-text); -} - -QDockWidget::title { - background-color: #dbdbdb; - border: 1px solid #cccccc; - text-align: center; -} - -QDockWidget { - titlebar-close-icon: url(:/light_scheme/icon_window_close.png); - titlebar-normal-icon: url(:/light_scheme/icon_window_restore.png); - font-weight: bold; -} - -QDockWidget:disabled { - titlebar-close-icon: url(:/light_scheme/icon_window_close_disabled.png); - titlebar-normal-icon: url(:/light_scheme/icon_window_restore_disabled.png); - font-weight: bold; -} - -QDockWidget > QWidget { - border: 1px solid #cccccc; - border-top: none; - border-bottom: none; -} - -QDockWidget::close-button, QDockWidget::float-button { - subcontrol-origin: margin; - subcontrol-position: top right; - position: absolute; - top: 0px; - bottom: 0px; - width: 22px; - height: 22px; -} - -QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #ebebeb; -} - -QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #bfbfbf; -} - -QScrollBar:vertical { - background: #e3e3e3; - border: none; - width: 17px; - margin: 0px; -} - -QScrollBar::handle:vertical { - background-color: #bdbdbd; - border: none; - min-height: 20px; - margin: 17px 2px 17px 2px; -} - -QScrollBar::add-line:vertical { - background: transparent; - height: 17px; - subcontrol-position: bottom; - subcontrol-origin: margin; - color: palette(window-text) -} - -QScrollBar::sub-line:vertical { - background: transparent; - height: 17px; - subcontrol-position: top; - subcontrol-origin: margin; - color: palette(window-text) -} - -QScrollBar:horizontal { - background: #e3e3e3; - border: none; - height: 17px; - margin: 0px; -} - -QScrollBar::handle:horizontal { - background-color: #bdbdbd; - border: none; - min-width: 20px; - margin: 2px 17px 2px 17px; -} - -QScrollBar::add-line:horizontal { - background: transparent; - width: 17px; - subcontrol-position: right; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:horizontal { - background: transparent; - width: 17px; - subcontrol-position: left; - subcontrol-origin: margin; -} - -QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { - background: #d9d9d9; -} - -QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar:down-arrow:vertical, QScrollBar::up-arrow:vertical { - width: 17px; - height: 17px; - background: transparent; - border: none; -} - -QScrollBar:left-arrow:horizontal { - image: url(:/light_scheme/icon_scroll_bar_left_arrow.png); -} - -QScrollBar::right-arrow:horizontal { - image: url(:/light_scheme/icon_scroll_bar_right_arrow.png); -} - -QScrollBar:down-arrow:vertical { - image: url(:/light_scheme/icon_scroll_bar_down_arrow.png); -} - -QScrollBar::up-arrow:vertical { - image: url(:/light_scheme/icon_scroll_bar_up_arrow.png); -} - -QTabWidget > QWidget { - qproperty-drawBase:0; -} - -QTabWidget::pane { - background-color: palette(window); - border: 1px solid #cccccc; - qproperty-drawBase:0; -} - -QTabWidget::tab-bar { - top: 1px; -} - -QTabBar { - background-color: transparent; - border: none; -} - -QTabBar::tab { - padding: 5px 1px 5px 1px; - height: 14px; - width: 96px; - background-color: #dbdbdb; - border: 1px solid #cccccc; - font-weight: bold; -} - -QTabBar::tab:!first { - border-left: none; -} - -QTabBar::tab:hover { - background-color: #ebebeb; -} - -QTabBar::tab:selected { - background-color: palette(window); - border-bottom: palette(window); -} - -output--TabbedImageView > QTabBar::tab { - padding: 1px 4px 1px 5px; - height: 96px; - width: 14px; -} - -output--TabbedImageView::tab-bar { - left: 0px; - right: 0px; - top: 0px; -} - -QComboBox { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 3px; - padding: 1px 0px 1px 8px; - height: 17px; - width: 86px; - selection-background-color: #3399ff; - selection-color: white; -} - -QAbstractSpinBox { - border: 1px solid #cccccc; - background-color: palette(base); - border-radius: 0px; - padding: 1px 3px 1px 3px; - height: 17px; - selection-background-color: #3399ff; - selection-color: white; -} - -QComboBox:disabled, QAbstractSpinBox:disabled { - border: 1px solid #dedede; - background-color: #fafafa; -} - -QComboBox:hover, QAbstractSpinBox:focus { - border-color: #1e76e3; -} - -QComboBox:on { - border-color: #1e76e3; - background-color: #bfbfbf; -} - -QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: bottom right; - width: 18px; - background: transparent; - border: none; -} - -QComboBox::down-arrow { - image: url(:/light_scheme/icon_down_arrow.png); -} - -QComboBox QAbstractItemView { - border: none; - padding: 1px 1px 1px 1px; - selection-background-color: #3399ff; - selection-color: white; -} - -QAbstractSpinBox::up-button { - subcontrol-origin: border; - subcontrol-position: top right; - padding-top: 4px; - padding-right: 1px; - width: 14px; - background: transparent; - border: none; -} - -QAbstractSpinBox::down-button { - subcontrol-origin: border; - subcontrol-position: bottom right; - padding-bottom: 4px; - padding-right: 1px; - width: 14px; - background: transparent; - border: none; -} - -QAbstractSpinBox::down-arrow { - image: url(:/light_scheme/icon_down_arrow.png); -} - -QAbstractSpinBox::up-arrow { - image: url(:/light_scheme/icon_up_arrow.png); -} - -QAbstractSpinBox::down-arrow:pressed { - image: url(:/light_scheme/icon_down_arrow_pressed.png); -} - -QAbstractSpinBox::up-arrow:pressed { - image: url(:/light_scheme/icon_up_arrow_pressed.png); -} - -QSlider:horizontal { - margin: 9px 1px 9px 1px; -} - -QSlider::groove:horizontal { - border: none; - height: 6px; - background-color: #cccccc; -} - -QSlider::handle:horizontal { - height: 19px; - width: 12px; - margin: -21px 0px; - image: url(:/light_scheme/icon_slider_handle.png); -} - -QToolTip { - color: #000000; - background-color: #ffffcd; - border: 1px solid #000000; - border-radius: 0px; -} - -QStatusBar { - background-color: #dbdbdb; - color: palette(window-text); -} - -QTreeView, QTableView { - alternate-background-color: palette(window); - background: palette(base); - border: 1px solid #cccccc; -} - -QTreeView QHeaderView::section, QTableView QHeaderView::section { - background: #dbdbdb; - border-style: none; - border-right: 1px solid #cccccc; - border-bottom: 1px solid #cccccc; - padding-left: 3px; - padding-right: 3px; -} - -QTreeView::item:selected:disabled, QTableView::item:selected:disabled { - background: #dfdfdf; -} - -QTreeView::branch { - background-color: palette(base); -} - -QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/light_scheme/icon_vline.png) 0; -} - -QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/light_scheme/icon_branch_more.png) 0; -} - -QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/light_scheme/icon_branch_end.png) 0; -} - -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url(:/light_scheme/icon_branch_closed.png); -} - -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url(:/light_scheme/icon_branch_open.png); -} \ No newline at end of file diff --git a/resources/unix/mime/scantailor-project.xml b/resources/unix/mime/scantailor-project.xml new file mode 100644 index 000000000..7937cf80d --- /dev/null +++ b/resources/unix/mime/scantailor-project.xml @@ -0,0 +1,8 @@ + + + + ScanTailor project file + + + + diff --git a/resources/unix/scantailor.desktop b/resources/unix/scantailor.desktop new file mode 100644 index 000000000..9e49d5eb5 --- /dev/null +++ b/resources/unix/scantailor.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=ScanTailor Advanced +Comment=Interactive post-processing tool for scanned pages +Exec=scantailor %f +Icon=ScanTailor +Terminal=false +X-MultipleArgs=false +Type=Application +Categories=Graphics;Applications; +MimeType=application/x-scantailor-project; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae227e0f2..d80d8f08e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,31 +1,31 @@ -INCLUDE_DIRECTORIES(BEFORE ..) - -SET( - sources - main.cpp TestContentSpanFinder.cpp - TestSmartFilenameOrdering.cpp - TestMatrixCalc.cpp - ../ContentSpanFinder.cpp ../ContentSpanFinder.h - ../SmartFilenameOrdering.cpp ../SmartFilenameOrdering.h +include_directories(BEFORE ..) + +set( + sources + main.cpp TestContentSpanFinder.cpp + TestSmartFilenameOrdering.cpp + TestMatrixCalc.cpp + ../ContentSpanFinder.cpp ../ContentSpanFinder.h + ../SmartFilenameOrdering.cpp ../SmartFilenameOrdering.h ) -SOURCE_GROUP("Sources" FILES ${sources}) +source_group("Sources" FILES ${sources}) -SET( - libs - imageproc math Qt5::Widgets ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} - ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} +set( + libs + imageproc math Qt5::Widgets ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${EXTRA_LIBS} ) -ADD_EXECUTABLE(generic_tests ${sources}) -TARGET_LINK_LIBRARIES(generic_tests ${libs}) +add_executable(generic_tests ${sources}) +target_link_libraries(generic_tests ${libs}) # We want the executable located where we copy all the DLLs. -SET_TARGET_PROPERTIES( - generic_tests PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +set_target_properties( + generic_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -ADD_TEST(NAME generic_tests COMMAND generic_tests --log_level=message) +add_test(NAME generic_tests COMMAND generic_tests --log_level=message) diff --git a/tests/TestContentSpanFinder.cpp b/tests/TestContentSpanFinder.cpp index ab6fa2121..d47dab5a3 100644 --- a/tests/TestContentSpanFinder.cpp +++ b/tests/TestContentSpanFinder.cpp @@ -16,11 +16,11 @@ along with this program. If not, see . */ +#include +#include #include "ContentSpanFinder.h" -#include "imageproc/SlicedHistogram.h" #include "Span.h" -#include -#include +#include "imageproc/SlicedHistogram.h" namespace Tests { using namespace imageproc; @@ -28,88 +28,88 @@ using namespace imageproc; BOOST_AUTO_TEST_SUITE(ContentSpanFinderTestSuite); BOOST_AUTO_TEST_CASE(test_empty_input) { - ContentSpanFinder span_finder; + ContentSpanFinder span_finder; - std::vector spans; - span_finder.find(SlicedHistogram(), [&](const Span& span) { spans.push_back(span); }); + std::vector spans; + span_finder.find(SlicedHistogram(), [&](const Span& span) { spans.push_back(span); }); - BOOST_CHECK(spans.empty()); + BOOST_CHECK(spans.empty()); } BOOST_AUTO_TEST_CASE(test_min_content_width) { - SlicedHistogram hist; - hist.setSize(9); - hist[0] = 0; - hist[1] = 1; - hist[2] = 0; - hist[3] = 1; - hist[4] = 1; - hist[5] = 1; - hist[6] = 0; - hist[7] = 1; - hist[8] = 1; - - ContentSpanFinder span_finder; - span_finder.setMinContentWidth(2); - - std::vector spans; - span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); - - BOOST_REQUIRE(spans.size() == 2); - BOOST_REQUIRE(spans[0] == Span(3, 3 + 3)); - BOOST_REQUIRE(spans[1] == Span(7, 7 + 2)); + SlicedHistogram hist; + hist.setSize(9); + hist[0] = 0; + hist[1] = 1; + hist[2] = 0; + hist[3] = 1; + hist[4] = 1; + hist[5] = 1; + hist[6] = 0; + hist[7] = 1; + hist[8] = 1; + + ContentSpanFinder span_finder; + span_finder.setMinContentWidth(2); + + std::vector spans; + span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); + + BOOST_REQUIRE(spans.size() == 2); + BOOST_REQUIRE(spans[0] == Span(3, 3 + 3)); + BOOST_REQUIRE(spans[1] == Span(7, 7 + 2)); } BOOST_AUTO_TEST_CASE(test_min_whitespace_width) { - SlicedHistogram hist; - hist.setSize(9); - hist[0] = 0; - hist[1] = 1; - hist[2] = 0; - hist[3] = 1; - hist[4] = 1; - hist[5] = 0; - hist[6] = 0; - hist[7] = 1; - hist[8] = 1; - - ContentSpanFinder span_finder; - span_finder.setMinWhitespaceWidth(2); - - std::vector spans; - span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); - - BOOST_REQUIRE(spans.size() == 2); - BOOST_REQUIRE(spans[0] == Span(1, 1 + 4)); - BOOST_REQUIRE(spans[1] == Span(7, 7 + 2)); + SlicedHistogram hist; + hist.setSize(9); + hist[0] = 0; + hist[1] = 1; + hist[2] = 0; + hist[3] = 1; + hist[4] = 1; + hist[5] = 0; + hist[6] = 0; + hist[7] = 1; + hist[8] = 1; + + ContentSpanFinder span_finder; + span_finder.setMinWhitespaceWidth(2); + + std::vector spans; + span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); + + BOOST_REQUIRE(spans.size() == 2); + BOOST_REQUIRE(spans[0] == Span(1, 1 + 4)); + BOOST_REQUIRE(spans[1] == Span(7, 7 + 2)); } BOOST_AUTO_TEST_CASE(test_min_content_and_whitespace_width) { - SlicedHistogram hist; - hist.setSize(9); - hist[0] = 0; - hist[1] = 1; - hist[2] = 0; - hist[3] = 1; - hist[4] = 1; - hist[5] = 0; - hist[6] = 0; - hist[7] = 1; - hist[8] = 0; - - ContentSpanFinder span_finder; - span_finder.setMinContentWidth(2); - span_finder.setMinWhitespaceWidth(2); - - std::vector spans; - span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); - - // Note that although a content block at index 1 is too short, - // it's still allowed to merge with content at positions 3 and 4 - // because the whitespace between them is too short as well. - - BOOST_REQUIRE(spans.size() == 1); - BOOST_REQUIRE(spans[0] == Span(1, 1 + 4)); + SlicedHistogram hist; + hist.setSize(9); + hist[0] = 0; + hist[1] = 1; + hist[2] = 0; + hist[3] = 1; + hist[4] = 1; + hist[5] = 0; + hist[6] = 0; + hist[7] = 1; + hist[8] = 0; + + ContentSpanFinder span_finder; + span_finder.setMinContentWidth(2); + span_finder.setMinWhitespaceWidth(2); + + std::vector spans; + span_finder.find(hist, [&](const Span& span) { spans.push_back(span); }); + + // Note that although a content block at index 1 is too short, + // it's still allowed to merge with content at positions 3 and 4 + // because the whitespace between them is too short as well. + + BOOST_REQUIRE(spans.size() == 1); + BOOST_REQUIRE(spans[0] == Span(1, 1 + 4)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/tests/TestMatrixCalc.cpp b/tests/TestMatrixCalc.cpp index d702612f8..ecf2d2aae 100644 --- a/tests/TestMatrixCalc.cpp +++ b/tests/TestMatrixCalc.cpp @@ -16,82 +16,82 @@ along with this program. If not, see . */ -#include "MatrixCalc.h" #include #include +#include "MatrixCalc.h" namespace imageproc { namespace tests { BOOST_AUTO_TEST_SUITE(MatrixCalcSuite); BOOST_AUTO_TEST_CASE(test1) { - static const double A[] = {1, 1, 1, 2, 4, -3, 3, 6, -5}; + static const double A[] = {1, 1, 1, 2, 4, -3, 3, 6, -5}; - static const double B[] = {9, 1, 0}; + static const double B[] = {9, 1, 0}; - static const double control[] = {7, -1, 3}; + static const double control[] = {7, -1, 3}; - double x[3]; + double x[3]; - MatrixCalc mc; - mc(A, 3, 3).trans().solve(mc(B, 3, 1)).write(x); + MatrixCalc mc; + mc(A, 3, 3).trans().solve(mc(B, 3, 1)).write(x); - for (int i = 0; i < 3; ++i) { - BOOST_REQUIRE_CLOSE(x[i], control[i], 1e-6); - } + for (int i = 0; i < 3; ++i) { + BOOST_REQUIRE_CLOSE(x[i], control[i], 1e-6); + } } BOOST_AUTO_TEST_CASE(test2) { - static const double A[] = {1, 1, 1, 2, 4, -3, 3, 6, -5, 3, 5, -2, 5, 10, -8}; + static const double A[] = {1, 1, 1, 2, 4, -3, 3, 6, -5, 3, 5, -2, 5, 10, -8}; - double B[] = {9, 1, 0, 10, 1}; + double B[] = {9, 1, 0, 10, 1}; - static const double control[] = {7, -1, 3}; + static const double control[] = {7, -1, 3}; - double x[3]; + double x[3]; - MatrixCalc mc; - mc(A, 3, 5).trans().solve(mc(B, 5, 1)).write(x); + MatrixCalc mc; + mc(A, 3, 5).trans().solve(mc(B, 5, 1)).write(x); - for (int i = 0; i < 3; ++i) { - BOOST_REQUIRE_CLOSE(x[i], control[i], 1e-6); - } + for (int i = 0; i < 3; ++i) { + BOOST_REQUIRE_CLOSE(x[i], control[i], 1e-6); + } - // Now make the system inconsistent. - B[4] += 1.0; - BOOST_CHECK_THROW(mc(A, 3, 5).trans().solve(mc(B, 5, 1)), std::runtime_error); + // Now make the system inconsistent. + B[4] += 1.0; + BOOST_CHECK_THROW(mc(A, 3, 5).trans().solve(mc(B, 5, 1)), std::runtime_error); } BOOST_AUTO_TEST_CASE(test3) { - static const double A[] = {1, 3, 1, 1, 1, 2, 2, 3, 4}; + static const double A[] = {1, 3, 1, 1, 1, 2, 2, 3, 4}; - static const double control[] = {2, 9, -5, 0, -2, 1, -1, -3, 2}; + static const double control[] = {2, 9, -5, 0, -2, 1, -1, -3, 2}; - double inv[9]; + double inv[9]; - MatrixCalc mc; - mc(A, 3, 3).trans().inv().transWrite(inv); + MatrixCalc mc; + mc(A, 3, 3).trans().inv().transWrite(inv); - for (int i = 0; i < 9; ++i) { - BOOST_REQUIRE_CLOSE(inv[i], control[i], 1e-6); - } + for (int i = 0; i < 9; ++i) { + BOOST_REQUIRE_CLOSE(inv[i], control[i], 1e-6); + } } BOOST_AUTO_TEST_CASE(test4) { - static const double A[] = {4, 1, 9, 6, 2, 8, 7, 3, 5, 11, 10, 12}; + static const double A[] = {4, 1, 9, 6, 2, 8, 7, 3, 5, 11, 10, 12}; - static const double B[] = {2, 9, 5, 12, 8, 10}; + static const double B[] = {2, 9, 5, 12, 8, 10}; - static const double control[] = {85, 138, 86, 158, 69, 149, 168, 339}; + static const double control[] = {85, 138, 86, 158, 69, 149, 168, 339}; - double mul[8]; + double mul[8]; - MatrixCalc mc; - (mc(A, 3, 4).trans() * (mc(B, 2, 3).trans())).transWrite(mul); + MatrixCalc mc; + (mc(A, 3, 4).trans() * (mc(B, 2, 3).trans())).transWrite(mul); - for (int i = 0; i < 8; ++i) { - BOOST_REQUIRE_CLOSE(mul[i], control[i], 1e-6); - } + for (int i = 0; i < 8; ++i) { + BOOST_REQUIRE_CLOSE(mul[i], control[i], 1e-6); + } } BOOST_AUTO_TEST_SUITE_END(); diff --git a/tests/TestSmartFilenameOrdering.cpp b/tests/TestSmartFilenameOrdering.cpp index 940dda9f2..333bf0c29 100644 --- a/tests/TestSmartFilenameOrdering.cpp +++ b/tests/TestSmartFilenameOrdering.cpp @@ -16,58 +16,58 @@ along with this program. If not, see . */ -#include "SmartFilenameOrdering.h" #include #include #include +#include "SmartFilenameOrdering.h" namespace Tests { BOOST_AUTO_TEST_SUITE(SmartFilenameOrderingTestSuite); BOOST_AUTO_TEST_CASE(test_same_file) { - const SmartFilenameOrdering less; - const QFileInfo somefile("/etc/somefile"); - BOOST_CHECK(!less(somefile, somefile)); + const SmartFilenameOrdering less; + const QFileInfo somefile("/etc/somefile"); + BOOST_CHECK(!less(somefile, somefile)); } BOOST_AUTO_TEST_CASE(test_dirs_different) { - const SmartFilenameOrdering less; - const QFileInfo lhs("/etc/file"); - const QFileInfo rhs("/ect/file"); - BOOST_CHECK(less(lhs, rhs) == (lhs.absolutePath() < rhs.absolutePath())); - BOOST_CHECK(less(rhs, lhs) == (rhs.absolutePath() < lhs.absolutePath())); + const SmartFilenameOrdering less; + const QFileInfo lhs("/etc/file"); + const QFileInfo rhs("/ect/file"); + BOOST_CHECK(less(lhs, rhs) == (lhs.absolutePath() < rhs.absolutePath())); + BOOST_CHECK(less(rhs, lhs) == (rhs.absolutePath() < lhs.absolutePath())); } BOOST_AUTO_TEST_CASE(test_simple_case) { - const SmartFilenameOrdering less; - const QFileInfo lhs("/etc/1.png"); - const QFileInfo rhs("/etc/2.png"); - BOOST_CHECK(less(lhs, rhs)); - BOOST_CHECK(!less(rhs, lhs)); + const SmartFilenameOrdering less; + const QFileInfo lhs("/etc/1.png"); + const QFileInfo rhs("/etc/2.png"); + BOOST_CHECK(less(lhs, rhs)); + BOOST_CHECK(!less(rhs, lhs)); } BOOST_AUTO_TEST_CASE(test_avg_case) { - const SmartFilenameOrdering less; - const QFileInfo lhs("/etc/a_0002.png"); - const QFileInfo rhs("/etc/a_1.png"); - BOOST_CHECK(!less(lhs, rhs)); - BOOST_CHECK(less(rhs, lhs)); + const SmartFilenameOrdering less; + const QFileInfo lhs("/etc/a_0002.png"); + const QFileInfo rhs("/etc/a_1.png"); + BOOST_CHECK(!less(lhs, rhs)); + BOOST_CHECK(less(rhs, lhs)); } BOOST_AUTO_TEST_CASE(test_compex_case) { - const SmartFilenameOrdering less; - const QFileInfo lhs("/etc/a10_10.png"); - const QFileInfo rhs("/etc/a010_2.png"); - BOOST_CHECK(!less(lhs, rhs)); - BOOST_CHECK(less(rhs, lhs)); + const SmartFilenameOrdering less; + const QFileInfo lhs("/etc/a10_10.png"); + const QFileInfo rhs("/etc/a010_2.png"); + BOOST_CHECK(!less(lhs, rhs)); + BOOST_CHECK(less(rhs, lhs)); } BOOST_AUTO_TEST_CASE(test_almost_equal) { - const SmartFilenameOrdering less; - const QFileInfo lhs("/etc/10.png"); - const QFileInfo rhs("/etc/010.png"); - BOOST_CHECK(!less(lhs, rhs)); - BOOST_CHECK(less(rhs, lhs)); + const SmartFilenameOrdering less; + const QFileInfo lhs("/etc/10.png"); + const QFileInfo rhs("/etc/010.png"); + BOOST_CHECK(!less(lhs, rhs)); + BOOST_CHECK(less(rhs, lhs)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/translations/scantailor_es.ts b/translations/scantailor_es.ts new file mode 100644 index 000000000..d80e919f9 --- /dev/null +++ b/translations/scantailor_es.ts @@ -0,0 +1,2815 @@ + + + + + AboutDialog + + ScanTailor Advanced + No traducido por ser el nombre del programa. + ScanTailor Advanced + + + About + Acerca de + + + Scan Tailor is an interactive post-processing tool for scanned pages. It performs operations such as page splitting, skew correction, adding/removing margins, and others. You give it raw scans, and you get pages ready to be printed or assembled into a PDF or DJVU file. Scanning and optical character recognition is out of scope of this project. + Scan Tailor es una herramienta interactiva de posprocesamiento para páginas escaneadas. Realiza operaciones tales como division de páginas, corrección de inclinación, añadir/eliminar márgenes y otras. Usted le da escaneos en bruto y obtiene páginas listas para imprimirlas o ensamblarlas en un fichero PDF o DJVU. El escaneo y reconocimiento óptico de caracteres están fuera del alcance de este proyecto. + + + Authors + Autores + + + Joseph Artsimovich + Joseph Artsimovich + + + Contributors + Colaboradores + + + U235 - Picture auto-detection algorithm. + U235 - Algoritmo de detección automático de imágenes. + + + Robert B. - First generation dewarping algorithm. + Robert B. - Primera generación del algoritmo de antideformación. + + + Andrey Bergman - System load adjustment. + Andrey Bergman - Ajuste de la carga el sistema. + + + Petr Kovář - Command line interface, ver. Enhanced + Petr Kovář - Interfaz de línea de comandos, versión «Enhanced» + + + Vadim Kuznetsov - ver. Plus + Vadim Kuznetsov - versión «Plus» + + + monday2000 - ver. Featured + monday2000 - versión «Featured» + + + Alexander Trufanov - ver. Universal + Alexander Trufanov - versión «Universal» + + + License + Licencia + + + Lead Developer of original version + + + + Lead Developer of Advanced version + + + + 4lex4 + + + + + BatchProcessingLowerPanel + + Form + + + + Beep when finished + Pitar al terminar + + + + ColorPickupInteraction + + Click on an area to pick up its color, or ESC to cancel. + Haga clic en un área para copiar su color, o ESC para cancelar. + + + + DefaultParamsDialog + + Default parameters + Parámetros por omisión + + + Save + Guardar + + + Delete + Borrar + + + Units: + Unidades: + + + Fix Orientation + Corregir orientación + + + Rotate + Rotar + + + ... + ... + + + Reset + Reiniciar + + + Split Pages + Dividir páginas + + + Mode + Modo + + + Page Layout + Diseño de página + + + Deskew + Enderezar + + + Auto + Automático + + + Manual + Manual + + + Select Content + Seleccionar contenido + + + Page Box + Caja de página + + + Disable + Inhabilitar + + + Options + Opciones + + + Shift with corners while they are in black. + Cambiar con las esquinas mientras estén en negro. + + + Fine Tune Page Corners + Ajuste fino de las esquinas de la página + + + Width + Ancho + + + Height + Alto + + + Content Box + Caja de contenido + + + Margins + Márgenes + + + Auto Margins + Márgenes automáticos + + + Top + Arriba + + + Right + Derecha + + + Left + Izquierda + + + Bottom + Abajo + + + Alignment + Alineación + + + Auto + auto + Automático + + + Manual + manual + Manual + + + Original + original + Original + + + Auto aligning + Alineación automática + + + Enable horizontal + Habilitar en horizontal + + + Enable vertical + Habilitar en vertical + + + Match size with other pages + Igualar el tamaño con otras páginas + + + Output + Salida + + + Normalize illumination before binarization. + Noramalizar la iluminación antes de pasar a binario. + + + Equalize illumination (B&&W) + Igualar la iluminación (blanco y negro) + + + Normalize illumination in color mode / in picture zones in mixed mode. + Normalizar la iluminación en modo color / en zonas de imágenes en modo mixto. + + + Equalize illumination (Color) + Igualar la iluminación (Color) + + + Savitzky-Golay smoothing + Suavizado Savitzky-Golay + + + Morphological smoothing + Suavizado morfológico + + + Filling + Relleno + + + Color: + Color: + + + Color operations + Operaciones de color + + + Split the image into color segments and colorize b&w mask. + Divide la imagen en segmentos de color y colorea la máscara en blanco y negro. + + + Color segmentation + Segmentación de los colores + + + R + R + + + Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. + Ajuste del componente rojo. Un valor negativo indica que el segmentador es más sensible al rojo y lo contrario para un valor positivo. + + + G + G + + + Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. + Ajuste del componente verde. Un valor negativo indica que el segmentador es más sensible al verde y lo contrario para un valor positivo. + + + B + B + + + Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. + Ajuste del componente azul. Un valor negativo indica que el segmentador es más sensible al azul y lo contrario para un valor positivo. + + + Reduce noise: + Reducir ruido: + + + Reduce the number of colors of the output image by grouping similar colors. + Reduce el número de colores de la imagen de salida agrupando los colores parecidos. + + + Posterize + Posterizar + + + Level: + Nivel: + + + Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. + Un valor más bajo significa un menor número de colores en la imagen de salida, valores entre 2 y 6 inclusive garantizan una imagen indexada. + + + Normalize + Normalizar + + + Make dark and light gray gradients black and white respectively. + Hacer los gradientes oscuros y gris oscuro negros y blancos respectivamente. + + + Force b&&w + Forzar blanco y negro + + + Threshold + Umbral + + + Method: + Método: + + + 0 + 0 + + + Thinner + Más fino + + + Thicker + Más grueso + + + Coef: + Coef: + + + The dimensions of a pixel neighborhood to consider. + Las dimensiones de un vecindario de píxeles a considerar. + + + Window size: + Tamaño de ventana: + + + Default value is 0.34. + El valor por omisión es 0,34. + + + The minimum possible gray level that can be made white. + El menor nivel posible de gris que se puede hacer blanco. + + + Upper Bound: + Límite superior: + + + Lower bound: + Límite inferior: + + + The maximum possible gray level that can be made black. + El máximo nivel posible de gris que se puede hacer negro. + + + Coeff: + Coef: + + + Default value is 0.3. + El valor por omisión es 0,3. + + + Picture Shape + Forma de la imagen + + + Sensitivity (%): + Sensibilidad (%): + + + Higher search sensivity + Mayor sensibilidad de búsqueda + + + Output Resolution (DPI) + Resolución de salida (PPP) + + + Splitting + División + + + Split output + Dividir la salida + + + B&&W foreground + Fondo blanco y negro + + + Save the original background of the foreground layer. + Guardar el fondo original de la capa de frente. + + + Original background + Fondo original + + + Color foreground + Color de frente + + + Despeckling + Eliminar manchas + + + Dewarping + Antideformación + + + Post deskew + Antideformación posterior + + + Depth perception + Percepción de profundidad + + + Black and White + Blanco y negro + + + Color / Grayscale + Color / Escala de grises + + + Mixed + Mezclado + + + Background + Fondo + + + White + Blanco + + + Otsu + Otsu + + + Sauvola + Sauvola + + + Wolf + Wolf + + + Off + Desactivado + + + Free + Libre + + + Rectangular + Rectangular + + + Custom + Personalizado + + + Marginal + Marginal + + + Default + Por omisión + + + Source + Origen + + + Error + Error + + + Error loading the profile. + Error al cargar el perfil. + + + The name conflicts with a default profile name. Please enter a different name. + El nombre entra en conflicto con un nombre de perfil predeterminado. Por favor, introduzca un nombre diferente. + + + Error saving the profile. + Error al guardar el perfil. + + + Error deleting the profile. + Error al borrar el perfil. + + + Fill offcut + + + + Fill margins + + + + Despeckle + + + + + DeskewApplyDialog + + Apply to + Aplicar a + + + This page only (already applied) + Esta página solo (ya aplicado) + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + This page and the following every other page + All odd or even pages, depending on the current page being odd or even. + Esta página y las siguientes alternadas + + + Every other page + All odd or even pages, depending on the current page being odd or even. + Páginas alternadas + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + Every other selected page + Todas las demás páginas seleccionadas + + + The current page will be included. + Se incluirá la página actual. + + + + DeskewOptionsWidget + + Form + + + + Deskew + Enderezar + + + Auto + Automático + + + Manual + Manual + + + Apply To ... + Aplicar a... + + + + DragHandler + + Unrestricted dragging is possible by holding down the Shift key. + Es posible arrastrar sin restricciones manteniendo pulsada la tecla Mayúsculas. + + + + ErrorWidget + + Form + + + + + FixDpiDialog + + Fix DPI + Corregir PPP + + + Tab 1 + + + + Tab 2 + + + + DPI + PPP + + + Custom + Personalizado + + + x + x + + + Apply + Aplicar + + + Need Fixing + Necesita corrección + + + All Pages + Todas las páginas + + + DPI is too large and most likely wrong. + PPP es demasiado grande y muy probablemente erróneo. + + + DPI is too small. Even if it's correct, you are not going to get acceptable results with it. + PPP es demasiado pequeño. Incluso si es correcto, no va a obtener resultados aceptables con él. + + + DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. + DPI es demasiado pequeño para el tamaño de píxel. Tal combinación probablemente llevaría a errores de memoria. + + + %1 (page %2) + %1 (página %2) + + + + ImageViewBase + + Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. + Use la rueda del ratón o +/- para hacer zum. Al hacer zum, es posible arrastrar. + + + + InteractiveXSpline + + Click to create a new control point. + Haga clic para crear un nuevo punto de control. + + + This point can be dragged. Hold Ctrl or Shift to drag along axes. + Este punto se puede arrastrar. Mantenga pulsado Ctrl o Mayús. para arrastrar los ejes. + + + Drag this point or delete it by pressing Del or D. + Arrastre este punto o bórrelo pulsando Supr. o D. + + + + LoadFileTask + + The following file could not be loaded: +%1 + El siguiente fichero no se ha podido cargar: +%1 + + + The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. + El siguiente fichero no existe:<br>%1<br><br>Use la <a href="#relink">Herramienta de reconexión</a> para localizarlo. + + + + LoadFilesStatusDialog + + Some files failed to load + Ha fallado la carga de algunos ficheros + + + Loaded successfully: %1 + Cargado con éxito %1 + + + Failed to load: %1 + Error al cargar: %1 + + + + MainWindow + + MainWindow + + + + Tools + Herramientas + + + Units + Unidades + + + File + Archivo + + + Help + Ayuda + + + Thumbnails + Miniaturas + + + Keep current page in view. + Mantener la página actual a la vista. + + + Follow page + Seguir la página + + + Filters + Filtros + + + Debug Mode + Modo de depuración + + + Save Project + Guardar proyecto + + + Save Project As ... + Guardar proyecto como... + + + Next Page + Pagina siguiente + + + Previous Page + Página anterior + + + New Project ... + Proyecto nuevo... + + + Open Project ... + Abrir proyecto... + + + Close Project + Cerrar proyecto + + + Quit + Salir + + + Settings ... + Configuración... + + + First Page + Primera página + + + Last Page + Última página + + + About + Acerca de + + + Fix DPI ... + Corregir PPP... + + + Relinking ... + Reconexión... + + + Switch filter to orientation + Cambiar al filtro de orientación + + + Switch filter to split pages + Cambiar al filtro de dividir páginas + + + Switch filter to deskew + Cambiar al filtro de enderezar + + + Switch filter to select content + Cambiar al filtro de seleccionar contenido + + + Switch filter to margins + Cambiar al filtro de márgenes + + + Switch filter to output + Cambiar al filtro de salida + + + Pixels + Píxeles + + + Millimetres + Milímetros + + + Inches + Pulgadas + + + Centimetres + Centímetros + + + Default parameters ... + Parámetros por omisión... + + + Stop batch processing + Detener proceso por lotes + + + Save the project? + ¿Desea guardar el proyecto? + + + Insert before ... + Insertar antes... + + + Insert after ... + Insertar después... + + + Remove from project ... + Eliminar del proyecto... + + + Insert here ... + Insertar aquí... + + + Scan Tailor Projects + Proyectos de Scan Tailor + + + Open Project + Abrir proyecto + + + Error + Error + + + Unable to open the project file. + No es posible abrir el fichero de proyecto. + + + The project file is broken. + El fichero de proyecto está roto. + + + version + versión + + + Output is not yet possible, as the final size of pages is not yet known. +To determine it, run batch processing at "Select Content" or "Margins". + El resultado todavía no está disponible, ya que aún no se conoce el tamaño final de las páginas. +Para determinarlo, ejecute el proceso por lotes en «Seleccionar contenido» o «Márgenes». + + + Unnamed + Sin nombre + + + %2 - ScanTailor Advanced [%1bit] + %2 - Scan Tailor Advanced [%1bit] + + + Error saving the project file! + ¡Error al guardar el fichero de proyecto! + + + Files to insert + Ficheros a insertar + + + Images not in project (%1) + Imágenes fuera del proyecto (%1) + + + Skip failed files + Saltar los ficheros fallidos + + + Remove + Eliminar + + + Use Home, End, PgUp/Q, PgDown/W to navigate between pages or Shift+PgUp/Q and Shift+PgDown/W to navigate between selected ones. Alt+Wheel - scale thumbnails. + + + + Previous Selected Page + + + + Next Selected Page + + + + + NewOpenProjectPanel + + Form + + + + New Project ... + Proyecto nuevo... + + + Open Project ... + Abrir proyecto... + + + Recent Projects + Proyectos recientes + + + + OrientationApplyDialog + + Fix Orientation + Corregir orientación + + + Apply to + Aplicar a + + + This page only (already applied) + Esta página solo (ya aplicado) + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + This page and the following every other page + All odd or even pages, depending on the current page being odd or even. + Esta página y las siguientes alternadas + + + Every other page + All odd or even pages, depending on the current page being odd or even. + Páginas alternadas + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + Every other selected page + Todas las demás páginas seleccionadas + + + The current page will be included. + Se incluirá la página actual. + + + + OrientationOptionsWidget + + Form + + + + Rotate + Rotar + + + ... + ... + + + Reset + Reiniciar + + + Apply to ... + Aplicar a... + + + + OtsuBinarizationOptionsWidget + + Form + + + + 0 + 0 + + + Thinner + Más fino + + + Thicker + Más grueso + + + + OutOfMemoryDialog + + Out of memory + Memoria agotada + + + Out of Memory Situation in Scan Tailor + Situación de memoria agotada en Scan Tailor + + + Possible reasons + Posibles razones + + + Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? + ¿Tuvo que corregir los PPP de las imágenes originales? ¿Esta seguro de que los valores que introdujo eran correctos? + + + Sometimes your source images may have wrong DPI embedded into them. Scan Tailor tries to detect those, but it's not always easy to tell. You may need to check "Fix DPI even if they look normal" when creating a project and look into "All pages" tab in the "Fix DPI" dialog, which is also accessible from the Tools menu. + A veces, las imágenes originales pueden tener un valor PPP incorrecto embebido. Scan Tailor intenta detectarlas, pero no siempre es fácil. Puede que deba marcar «Corregir los PPP incluso si parecen normales» al crear un proyecto y mirar en la pestaña «Todas las páginas» del diálogo "Corregir PPP», que también es accesible desde el menú Herramientas. + + + Is your output DPI set too high? Usually you don't need it higher than 600. + ¿Son los PPP de salida demasiado altos? Normalmente no se necesitan más de 600. + + + What can help + Lo que puede ser de ayuda + + + Fix your DPIs. Learn how to <a href="http://vimeo.com/12524529">estimate unknown DPIs</a>. + Feel free to change the URL to a localized video / text tutorial. + Corrija los PPP. Aprenda cómo <a href="http://vimeo.com/12524529">estimar PPP desconocidos</a>. + + + If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. + Si su hardware y sistema operativo son de 64-bits, considere cambiar a la versión de 64-bits de Scan Tailor. + + + When working with grayscale images, make sure they are really grayscale. If they are actually color images that just happen to look grayscale, convert them to grayscale using some kind of batch image converter. This will both save memory and increase performance. + Al trabajar con imágenes en escalas de grises, asegúrese de que realmente lo son. Si en realidad son imágenes en color que parecen ser en escala de grises, conviértalas a escala de grises usando algún tipo de convertidor en lote. Esto ahorra memoria y aumenta el rendimiento. + + + As a last resort, you can save some memory by making sure thumbnails are pre-created rather than created on demand. This can be done by slowly scrolling the thumbnail list all the way from top to bottom before starting any real work. + Como último recurso, puede ahorrar algo de memoria asegurándose de que las miniatuas se crean de antemano en vez de bajo demanda. Esto se puede hacer desplazando lentamente la lista de miniaturas desde arriba hasta abajo antes de empezar el trabajo real. + + + What won't help + Lo que no es de ayuda + + + Surprisingly, upgrading your RAM won't help here. The lack of RAM is compensated by the swap mechanism, which makes things slow, but keeps programs running. An out of memory situation means we ran out of memory address space, which has nothing to do with the amount of RAM you have. The only way to increase the memory address space is to go 64-bit hardware, 64-bit operating system and 64-bit Scan Tailor. + Sorprendentemente, aumentar la RAM no es de ayuda en este caso. La falta de RAM se compensa con el mecanismo de intercambio a disco, que enlencete las cosas, pero mantiene los programas en funcionamiento. Un error de memoria agotada significa que se ha quedado sin espacio de direcciones de memoria, que no tiene nada que ver con la cantidad de RAM que tenga. + + + Save Project + Guardar proyecto + + + Save Project As ... + Guardar proyecto como... + + + Don't Save + No guardar + + + Project Saved Successfully + Proyecto guardado con éxito + + + Please note that while Scan Tailor tries to catch out-of-memory situations and give you the opportunity to save your project, it's not always possible. This time it succeeded, but the next time it might just crash. + Tenga en cuenta que aunque Scan Tailor intenta capturar las situaciones de memoria agotada y darle la oportunidad de guardar su proyecto, no siempre es posible. Esta vez tuvo éxito, pero la siguiente puede fallar. + + + Scan Tailor Projects + Proyectos de Scan Tailor + + + Error + Error + + + Error saving the project file! + ¡Error al guardar el fichero de proyecto! + + + + OutputApplyColorsDialog + + Apply Mode + Modo de aplicación + + + Apply to + Aplicar a + + + This page only (already applied) + Esta página solo (ya aplicado) + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + + OutputChangeDewarpingDialog + + Apply Dewarping Mode + Aplicar el modo de enderezado + + + Mode + Modo + + + Off + Desactivado + + + Auto (experimental) + Automático (experimental) + + + Marginal (experimental) + Marginal (experimental) + + + Manual + Manual + + + Options + Opciones + + + Post deskew + Antideformación posterior + + + Apply to + Aplicar a + + + This page only + Esta página solo + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + + OutputChangeDpiDialog + + Apply Output Resolution + Aplicar resolución de salida + + + DPI + PPP + + + Apply to + Aplicar a + + + This page only + Esta página solo + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + + OutputOptionsWidget + + Form + + + + Output Resolution (DPI) + Resolución de salida (PPP) + + + 0 + 0 + + + Change ... + Cambiar... + + + Mode + Modo + + + Options + Opciones + + + Normalize illumination before binarization. + Noramalizar la iluminación antes de pasar a binario. + + + Equalize illumination (B&&W) + Igualar la iluminación (blanco y negro) + + + Normalize illumination in color mode / in picture zones in mixed mode. + Normalizar la iluminación en modo color / en zonas de imágenes en modo mixto. + + + Equalize illumination (Color) + Igualar la iluminación (Color) + + + Savitzky-Golay smoothing + Suavizado Savitzky-Golay + + + Morphological smoothing + Suavizado morfológico + + + Filling + Relleno + + + Color: + Color: + + + Threshold + Umbral + + + Method: + Método: + + + Color operations + Operaciones de color + + + Split the image into color segments and colorize b&w mask. + Divide la imagen en segmentos de color y colorea la máscara en blanco y negro. + + + Color segmentation + Segmentación de los colores + + + R + R + + + Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. + Ajuste del componente rojo. Un valor negativo indica que el segmentador es más sensible al rojo y lo contrario para un valor positivo. + + + G + G + + + Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. + Ajuste del componente verde. Un valor negativo indica que el segmentador es más sensible al verde y lo contrario para un valor positivo. + + + B + B + + + Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. + Ajuste del componente azul. Un valor negativo indica que el segmentador es más sensible al azul y lo contrario para un valor positivo. + + + Reduce noise: + Reducir ruido: + + + Reduce the number of colors of the output image by grouping similar colors. + Reduce el número de colores de la imagen de salida agrupando los colores parecidos. + + + Posterize + Posterizar + + + Level: + Nivel: + + + Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. + Un valor más bajo significa un menor número de colores en la imagen de salida, valores entre 2 y 6 inclusive garantizan una imagen indexada. + + + Normalize + Normalizar + + + Make dark and light gray gradients black and white respectively. + Hacer los gradientes oscuros y gris oscuro negros y blancos respectivamente. + + + Force b&&w + Forzar blanco y negro + + + Picture Shape + Forma de la imagen + + + Sensitivity (%): + Sensibilidad (%): + + + Higher search sensitivity + Mayor sensibilidad de búsqueda + + + Apply To ... + Aplicar a... + + + Splitting + División + + + Split output + Dividir la salida + + + B&&W foreground + Fondo blanco y negro + + + Save the original background of the foreground layer. + Guardar el fondo original de la capa de frente. + + + Original background + Fondo original + + + Color foreground + Color de frente + + + Despeckling + Eliminar manchas + + + Depth perception + Percepción de profundidad + + + Dewarping + Antideformación + + + Fill offcut + + + + Fill margins + + + + Despeckle + + + + Processing + Procesando + + + This option should be enabled when the page has dark content on light background and disabled if vice versa in order to correct processing algorithms. + + + + Black on white mode + + + + + PageLayoutApplyDialog + + Apply to + Aplicar a + + + This page only (already applied) + Esta página solo (ya aplicado) + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + This page and the following every other page + All odd or even pages, depending on the current page being odd or even. + Esta página y las siguientes alternadas + + + Every other page + All odd or even pages, depending on the current page being odd or even. + Páginas alternadas + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + Every other selected page + Todas las demás páginas seleccionadas + + + The current page will be included. + Se incluirá la página actual. + + + + PageLayoutOptionsWidget + + Form + + + + Margins + Márgenes + + + Top + Arriba + + + ... + ... + + + Bottom + Abajo + + + Left + Izquierda + + + Right + Derecha + + + Apply To ... + Aplicar a... + + + Alignment + Alineación + + + Auto + auto + Automático + + + Manual + manual + Manual + + + Original + original + Original + + + Auto Margins + Márgenes automáticos + + + Auto aligning + Alineación automática + + + Enable horizontal + Habilitar en horizontal + + + Enable vertical + Habilitar en vertical + + + Match size with other pages + Igualar el tamaño con otras páginas + + + Guides Help + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> to create/remove guides from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> on a guide to delete that guide from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift+LMB</span><span style=" font-size:7pt;"> - drag the guide under the cursor.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift/Ctrl+LMB</span><span style=" font-size:7pt;"> on the content rectangle - drag the page content. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to restrict moving along the horizontal axis only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for the vertical one. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> for usual dragging.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Double-click</span><span style=" font-size:7pt;"> on content - automatically attach that content to the nearest guide. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to select vertical guides only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for horizontal ones. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> to attach that to both the nearest vertical and horizontal guides.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣</span><span style=" font-size:7pt;"> Use the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> to enable/disable showing the hard margins rectangle.</span></p></body></html> + + + + + PageSplitModeDialog + + Split Pages + Dividir páginas + + + Mode + Modo + + + Auto + Automático + + + Manual + Manual + + + Options + Opciones + + + Apply cut + Aplicar recorte + + + Apply to + Aplicar a + + + This page only + Esta página solo + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + This page and the following every other page + Esta página y las siguientes alternadas + + + Every other page + Páginas alternadas + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + Every other selected page + Todas las demás páginas seleccionadas + + + The current page will be included. + Se incluirá la página actual. + + + + PageSplitOptionsWidget + + Form + + + + Page Layout + Diseño de página + + + ? + ? + + + Change ... + Cambiar... + + + Split Line + Dividir línea + + + Auto + Automático + + + Manual + Manual + + + + PictureZonePropDialog + + Zone Properties + Propiedades de la zona + + + Subtract from all layers + Restar de todas las capas + + + Add to auto layer + Añadir a capa automática + + + Subtract from auto layer + Restar de la capa automática + + + + ProjectFilesDialog + + Project Files + Ficheros de proyecto + + + Input Directory + Directorio de entrada + + + Browse + Examinar + + + Output Directory + Directorio de salida + + + Files Not In Project + Ficheros fuera del proyecto + + + Select All + Seleccionar todo + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Add selected files to project.</p></body></html> + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Añadir los ficheros seleccionados al proyecto.</p></body></html> + + + >> + >> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Remove selected files from project.</p></body></html> + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Borrar los ficheros seleccionados del proyecto.</p></body></html> + + + << + << + + + Files In Project + Ficheros en el proyecto + + + Right to left layout (for Hebrew and Arabic) + Diseño de derecha a izquierda (para hebreo y árabe) + + + Fix DPIs, even if they look OK + Corregir los PPP, incluso si parecen correctos + + + Error + Error + + + No files in project! + ¡No hay ficheros en el proyecto! + + + Input directory is not set or doesn't exist. + El directorio de entrada no se ha especificado o no existe. + + + Input and output directories can't be the same. + Los directorios de entrada y salida no pueden ser el mismo. + + + Create Directory? + ¿Desea crear el directorio? + + + Output directory doesn't exist. Create it? + El directorio de salida no existe. ¿Desea crearlo? + + + Unable to create output directory. + No es posible crear el directorio de salida. + + + Output directory is not set or doesn't exist. + El directorio de salida no se ha especificado o no existe. + + + Some of the files failed to load. +Either we don't support their format, or they are broken. +You should remove them from the project. + Ha fallado la carga de algunos ficheros. +O no se admite su formato o están rotos. +Debería elimnarlos del proyecto. + + + + ProjectOpeningContext + + Error + Error + + + The project file is not compatible with the current application version. + El fichero de proyecto no es compatible con la versión actual de la aplicación. + + + Unable to interpret the project file. + No se puede interpretar el fichero de proyecto. + + + + QObject + + px + px + + + mm + mm + + + cm + cm + + + in + in + + + + RelinkingDialog + + Relinking + Reconexión + + + Undo + Deshacer + + + ... + ... + + + Substitution File for %1 + Fichero de sustitución para %1 + + + Substitution Directory for %1 + Directorio de sustitución para %1 + + + This change would merge several files into one. + Este cambio puede fusionar varios ficheros en uno solo. + + + + RemovePagesDialog + + Remove Pages + Eliminar páginas + + + Remove %1 page(s) from project? + ¿Desea eliminar %1 página(s) del proyecto? + + + Corresponding output files will be deleted, while input files will remain. + Los ficheros correspondientes de salida serán borrados, mientras que se mantendrán los de entrada. + + + + SauvolaBinarizationOptionsWidget + + Form + + + + Coef: + Coef: + + + The dimensions of a pixel neighborhood to consider. + Las dimensiones de un vecindario de píxeles a considerar. + + + Window size: + Tamaño de ventana: + + + Default value is 0.34. + El valor por omisión es 0,34. + + + + SelectContentApplyDialog + + Select Content + Seleccionar contenido + + + Options + Opciones + + + Apply content box + Aplicar caja de contenido + + + Apply page box + Aplicar caja de página + + + Apply to + Aplicar a + + + This page only (already applied) + Esta página solo (ya aplicado) + + + All pages + Todas las páginas + + + This page and the following ones + Esta página y las siguientes + + + This page and the following every other page + All odd or even pages, depending on the current page being odd or even. + Esta página y las siguientes alternadas + + + Every other page + All odd or even pages, depending on the current page being odd or even. + Páginas alternadas + + + Selected pages + Páginas seleccionadas + + + Use Ctrl+Click / Shift+Click to select multiple pages. + Use Ctrl+clic / Mayús+clic para seleccionar varias páginas. + + + Every other selected page + Todas las demás páginas seleccionadas + + + The current page will be included. + Se incluirá la página actual. + + + + SelectContentOptionsWidget + + Form + + + + Page Box + Caja de página + + + Options + Opciones + + + Shift with corners while they are in black. + Cambiar con las esquinas mientras estén en negro. + + + Fine Tune Page Corners + Ajuste fino de las esquinas de la página + + + Disable + Inhabilitar + + + Auto + Automático + + + Content Box + Caja de contenido + + + Manual + Manual + + + Width + Ancho + + + Height + Alto + + + Apply to ... + Aplicar a... + + + + SettingsDialog + + Settings + Configuración + + + General + General + + + Accelerate user interface with OpenGL + Acelerar la interfaz de usuario con OpenGL + + + Device: %1 + Dispositivo: %1 + + + Auto-save the existing project + Guardar automáticamente el proyecto existente + + + Processing + Procesando + + + Deviation + Desviación + + + Highlight the thumbnails of pages with high deviation + Resaltar las miniaturas de páginas con alta desviación + + + Params + Parámetros + + + Deksew: + Enderezar: + + + Select content: + Selección de contenido: + + + Deviation multiplier: a higher value means lower sensivity. + Multiplicador de desvío: un valor más alto significa menor sensibilidad. + + + Margins: + Márgenes: + + + The minimum deviation to be highlighted. + La desviación mínima a resaltar. + + + Color Scheme: + Esquema de color: + + + Language: + Idioma: + + + Saving + Guardando + + + B&W Compression: + Compresión en blanco y negro: + + + Color Compression: + Compresión en color: + + + Your hardware / driver don't provide the necessary features + Su hardware / controlador no ofrece las características necesarias + + + Dark + Oscuro + + + Light + Claro + + + None + Ninguna + + + LZW + LZW + + + Deflate + Deflate + + + CCITT G4 + CCITT G4 + + + JPEG + JPEG + + + Information + Información + + + ScanTailor need to be restarted to apply the color scheme changes. + Es necesario reiniciar ScanTailor para aplicar los cambios del esquema de color. + + + User Interface + + + + Thumbnails + Miniaturas + + + Quality: + + + + The pixel size of the thumbnail image. The default value is 200. + + + + Size: + + + + The thumbnail size in the view. The default value is 250. + + + + White on black detection + + + + Auto detect pages with light content on dark background. The corrections to all the auto algorithms are made for such pages. + + + + Auto detect light content on dark background + + + + Whether to use auto detection at the output stage. The wrong result can be changed manually in the output filter options. + + + + Use auto detection at the output stage + + + + Native + + + + + StageListView + + Launch batch processing + Lanzar el proceso por lotes + + + + StatusBarPanel + + Form + + + + Mouse position. + Posición del ratón. + + + Mouse position relative to page. + Posición del ratón relativa a la página. + + + Physical size. + Tamaño físico. + + + Physical size of image. + Tamaño físico de la imagen. + + + Page number. + Número de página. + + + Position of the selected page in current order. + Posición de la página seleccionada en el orden actual. + + + Page information. + Información de la página. + + + Page name and type. + Nombre y tipo de la página. + + + p. %1 / %2 + p. %1 / %2 + + + [L] + [I] + + + [R] + [D] + + + + SystemLoadWidget + + Form + + + + System load + Carga del sistema + + + ... + ... + + + + ThumbnailSequence + + %1 (page %2) + %1 (página %2) + + + + WolfBinarizationOptionsWidget + + Form + + + + The dimensions of a pixel neighborhood to consider. + Las dimensiones de un vecindario de píxeles a considerar. + + + Window size: + Tamaño de ventana: + + + The minimum possible gray level that can be made white. + El menor nivel posible de gris que se puede hacer blanco. + + + Upper Bound: + Límite superior: + + + Lower bound: + Límite inferior: + + + The maximum possible gray level that can be made black. + El máximo nivel posible de gris que se puede hacer negro. + + + Coeff: + Coef: + + + Default value is 0.3. + El valor por omisión es 0,3. + + + + ZoneContextMenuInteraction + + Delete + Borrar + + + Properties + Propiedades + + + + ZoneCreationInteraction + + Click to finish this rectangular zone. ESC to cancel. + Haga clic para terminar esta zona rectangular. ESC para cancelar. + + + Click to finish this zone. ESC to cancel. + Haga clic para terminar esta zona. ESC para cancelar. + + + Connect first and last points to finish this zone. ESC to cancel. + Conectar el primer y el último punto para terminar esta zona. ESC para cancelar. + + + Hold Ctrl to create a rectangular zone or Shift+Alt+LMB to use lasso mode. ESC to cancel. + + + + + ZoneDefaultInteraction + + Drag the vertex. Hold Ctrl to make the vertex angle right. + Arrastre el vértice. Mantenga pulsado Ctrl para hacer que el ángulo del vértice sea correcto. + + + Click to create a new vertex here. + Haga clic para crear un nuevo vértice aquí. + + + Right click to edit zone properties. Hold Shift to drag the zone or Shift+Ctrl to copy. Press Del to delete this zone. + Clic derecho para editar las propiedades de la zona. Mantenga pulsado Mayúsculas para arrastrar la zona o Mayús+Ctrl para copiarla. Pulse Supr para borrar esta zona. + + + Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone. + Haga clic para empezar a crear una zona nueva. Use Ctrl+Alt+Click para copiar la última zona creada. + + + + ZoneDragInteraction + + Release left mouse button to finish dragging. + Suelte el botón izquierdo del ratón para finalizar el arrastre. + + + + ZoneVertexDragInteraction + + Merge these two vertices. + Fusionar esos dos vértices. + + + Move the vertex to one of its neighbors to merge them. + Mover el vértice hacia uno de sus vecions para fusionarlos. + + + + deskew::Filter + + Natural order + Orden natural + + + Order by decreasing deviation + Orden decreciente de desviación + + + Deskew + Enderezar + + + + deskew::ImageView + + Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. + Use Ctrl+Rueda-del-ratón para rotar o Ctrl+Mayús+Rueda-del-ratón para rotación precisa. + + + Drag this handle to rotate the image. + Arrastre este asa para rotar la imagen. + + + + deskew::OptionsWidget + + Apply Deskew + Aplicar enderezado + + + + fix_orientation::Filter + + Fix Orientation + Corregir orientación + + + + output::ChangeDpiDialog + + Custom + Personalizado + + + Error + Error + + + DPI is not set. + No se han establecido los PPP. + + + DPI is too low! + ¡Valor de PPP demasiado bajo! + + + DPI is too high! + ¡Valor de PPP demasiado alto! + + + + output::FillZoneEditor + + Pick color + Seleccionar color + + + + output::Filter + + Output + Salida + + + Natural order + Orden natural + + + Order by completeness + + + + + output::OptionsWidget + + Black and White + Blanco y negro + + + Color / Grayscale + Color / Escala de grises + + + Mixed + Mezclado + + + Otsu + Otsu + + + Sauvola + Sauvola + + + Wolf + Wolf + + + Background + Fondo + + + White + Blanco + + + Free + Libre + + + Rectangular + Rectangular + + + Apply Splitting Settings + Aplicar los ajustes de división + + + Apply Despeckling Level + Aplicar el nivel de eliminación de manchas + + + Apply Depth Perception + Aplicar la percepción de profundidad + + + Off + Desactivado + + + Auto + Automático + + + Manual + Manual + + + Marginal + Marginal + + + deskew disabled + Enderezar inhabilitado + + + Apply Processing Settings + + + + + output::TabbedImageView + + Use Ctrl+1..5 to switch the tabs. + Use Ctrl+1..5 para cambiar de pestaña. + + + + output::Task::UiUpdater + + Picture zones are only available in Mixed mode. + Las zonas de imagen solo están disponibles en el modo Mixto. + + + Despeckling can't be done in Color / Grayscale mode. + La eliminación de manchas no se puede hacer en el modo Color / Escala de grises. + + + Output + Salida + + + Picture Zones + Zonas de imagen + + + Fill Zones + Rellenar zonas + + + Dewarping + Antideformación + + + Despeckling + Eliminar manchas + + + + page_layout::Filter + + Natural order + Orden natural + + + Order by increasing width + Orden creciente por ancho + + + Order by increasing height + Orden creciente por alto + + + Order by decreasing deviation + Orden decreciente de desviación + + + Margins + Márgenes + + + + page_layout::ImageView + + Resize margins by dragging any of the solid lines. + Cambiar el tamaño de los márgenes arrastrando cuaquiera de las líneas sólidas. + + + Hold left mouse button to drag the page content. + + + + Release left mouse button to finish dragging. + + + + Add a horizontal guide + + + + Add a vertical guide + + + + Remove all the guides + + + + Remove this guide + + + + Show hard margins rectangle + + + + Drag the guide. + + + + + page_layout::OptionsWidget + + Apply Margins + Aplicar márgenes + + + Apply Alignment + Aplicar alineación + + + + page_split::Filter + + Natural order + Orden natural + + + Order by split type + Orden por tipo de división + + + Split Pages + Dividir páginas + + + + page_split::ImageView + + Drag the line or the handles. + Arrastrar la línea o las asas. + + + + page_split::OptionsWidget + + Set manually + Ajustar manualmente + + + Auto detected + Detección automática + + + + page_split::UnremoveButton + + Restore removed page. + Restaurar la página eliminada. + + + + select_content::Filter + + Natural order + Orden natural + + + Order by increasing width + Orden creciente por ancho + + + Order by increasing height + Orden creciente por alto + + + Order by decreasing deviation + Orden decreciente de desviación + + + Select Content + Seleccionar contenido + + + + select_content::ImageView + + Use the context menu to enable / disable the content box. Hold Shift to drag a box. Use double-click on content to automatically adjust the content area. + Use el menú contextual para habilitar / inhabilitar la caja de contenido. Mantenga pulsado Mayús. para arrastrar una caja. Use doble clic en contenidos para ajustar automáticamente el área de contenido. + + + Drag lines or corners to resize the content box. + Arrastrar líneas o esquinas para cambiar el tamaño de la caja de contenido. + + + Drag lines or corners to resize the page box. + Arrastrar líneas o esquinas para cambiar el tamaño de la caja de página. + + + Hold left mouse button to drag the content box. + Mantener pulsado el botón izquierdo del ratón para arrastrar la caja de contenido. + + + Release left mouse button to finish dragging. + Soltar el botón izquierdo del ratón para finalizar el arrastre. + + + Hold left mouse button to drag the page box. + Mantener pulsado el botón izquierdo del ratón para arrastrar la caja de página. + + + Create Content Box + Crear caja de contenido + + + Remove Content Box + Eliminar caja de contenido + + + diff --git a/translations/scantailor_ru.ts b/translations/scantailor_ru.ts index 9d210f049..8353b732a 100644 --- a/translations/scantailor_ru.ts +++ b/translations/scantailor_ru.ts @@ -4,95 +4,81 @@ AboutDialog - ScanTailor Advanced - About О программе - Scan Tailor is an interactive post-processing tool for scanned pages. It performs operations such as page splitting, skew correction, adding/removing margins, and others. You give it raw scans, and you get pages ready to be printed or assembled into a PDF or DJVU file. Scanning and optical character recognition is out of scope of this project. Scan Tailor - это интерактивный инструмент для пост-обработки сканированных страниц. Он делает такие операции как разрезание страниц, компенсация наклона, добавление/удаление полей, и другие. Вы даете ему необработанные сканы, а в результате получаете страницы, готовые для печати или сборки в PDF или DJVU файл. Сканирование и оптическое распознавание символов не входят в задачи проекта. - Authors Авторы - - Lead Developer - Ведущий разработчик - - - Joseph Artsimovich Иосиф Арцимович - Contributors Внесли вклад в проект - U235 - Picture auto-detection algorithm. U235 - Алгоритм авто-распознавания картинок. - Robert B. - First generation dewarping algorithm. Robert B. - Алгоритм выпрямления строк первого поколения. - Andrey Bergman - System load adjustment. Andrey Bergman - Регулировка загрузки системы. - Petr Kovář - Command line interface, ver. Enhanced Petr Kovář - Версия для командной строки, версия Enhanced - Vadim Kuznetsov - ver. Plus Vadim Kuznetsov - версия Plus - monday2000 - ver. Featured monday2000 - версия Featured - Alexander Trufanov - ver. Universal Alexander Trufanov - версия Universal - - 4lex4 - ver. Advanced - 4lex4 - версия Advanced - - - License Лицензия + + Lead Developer of original version + Ведущий разработчик оригинальной версии + + + Lead Developer of Advanced version + Ведущий разработчик версии "Advanced" + + + 4lex4 + + BatchProcessingLowerPanel - Form - Beep when finished Звуковой сигнал по окончании @@ -100,7 +86,6 @@ ColorPickupInteraction - Click on an area to pick up its color, or ESC to cancel. Кликните на область, чтобы захватить ее цвет, или ESC для отмены. @@ -108,682 +93,512 @@ DefaultParamsDialog - Default parameters Параметры по умолчанию - Save Сохранить - Delete Удалить - Units: Единицы: - Fix Orientation Испр. ориент. - Rotate Поворот - - - - - - - - - - - - - - - - ... - Reset Сбросить - Split Pages Разрезка стр. - - Mode Режим - Page Layout Тип разреза - - Deskew Компенс. накл. - - - - - Auto Автоматически - - - - Manual Вручную - Select Content Полезная обл. - Page Box Зона распознавания - - Disable Выключить - - Options Настройки - Shift with corners while they are in black. Скорректировать края по черной области. - Fine Tune Page Corners Тонкая настройка краев страницы - Width Ширина - Height Высота - Content Box Полезная область - - Margins Поля - Auto Margins Автоматические поля - Top Сверху - Right Справа - Left Слева - Bottom Снизу - Alignment Выравнивание - Auto auto Автоматически - Manual manual Вручную - Original original Оригинал - Auto aligning Авто выравнивание - Enable horizontal Вкл. горизонтальное - Enable vertical Вкл. вертикальное - Match size with other pages Выровнять размер с остальн. стр. - Output Вывод - - Cut margins - Подрезать поля - - - Normalize illumination before binarization. Нормализовать освещенность перед бинаризацией. - Equalize illumination (B&&W) Выровнять освещение (ЧБ) - Normalize illumination in color mode / in picture zones in mixed mode. Нормализовать освещенность в цветном режиме / в зонах картинок в смешанном режиме. - Equalize illumination (Color) Выровнять освещение (Цвет.) - Savitzky-Golay smoothing Сглаживание Савицкого-Голея - Morphological smoothing Морфологическое сглаживание - Filling Заливка - Color: Цвет: - Color operations Цветовые операции - Split the image into color segments and colorize b&w mask. Поделить изображение на цветовые сегменты и раскрасить ЧБ маску. - Color segmentation Цветовая сегментация - R - Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. Настройка красного компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к красному и наоборот для положительных. - G - Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. Настройка зеленого компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к зеленому и наоборот для положительных. - B - Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. Настройка синего компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к синему и наоборот для положительных. - Reduce noise: Уменьшить шум: - Reduce the number of colors of the output image by grouping similar colors. Уменьшить количество цветов выходного изображения, группируя похожие цвета. - Posterize Постеризовать - Level: Уровень: - Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. Меньшее значение обозначает меньшее кол-во цветов в выходном изображении, значения 2 и 6 включительно гарантируют индексированое изображение. - Normalize Нормализовать - Make dark and light gray gradients black and white respectively. Сделать темные и светлые оттенки серого черными и белыми соответственно. - Force b&&w Принудительный ЧБ - Threshold Порог - Method: Метод: - 0 - Thinner Тоньше - Thicker Жирнее - Coef: Коэф.: - - The dimensions of a pixel neighborhood to consider. Размер области с соседними пикселями для расчета. - - Window size: Размер окна: - Default value is 0.34. Значение по умолчанию - 0.34. - The minimum possible gray level that can be made white. Минимальный уровень серого, который можно сделать белым. - Upper Bound: Верхняя граница: - Lower bound: Нижняя граница: - The maximum possible gray level that can be made black. Максимальный уровень серого, который можно сделать черным. - Coeff: Коэф.: - Default value is 0.3. Значение по умолчанию — 0.3. - Picture Shape Форма картинок - Sensitivity (%): Чувствительность (%): - Higher search sensivity Большая чувств. поиска - Output Resolution (DPI) Разрешение на выходе (DPI) - Splitting Разделение - Split output Разделить выход - B&&W foreground ЧБ передний слой - Save the original background of the foreground layer. Сохранить оригинальный фон переднего слоя. - Original background Оригинальный фон - Color foreground Цветной передний слой - Despeckling Удаление пятен - - No despeckling - Не удалять пятна - - - - Cautious despeckling - Осторожное удаление пятен - - - - Normal despeckling - Обычное удаление пятен - - - - Aggressive despeckling - Агрессивное удаление пятен - - - Dewarping Выпрямление - Post deskew Компенсировать поворот - Depth perception Восприятие глубины - Black and White Черно-белый - Color / Grayscale Цветной / Серый - Mixed Смешанный - Background Фон - White Белый - Otsu - Sauvola - Wolf - - Off Отключено - Free Свободная - Rectangular Прямоугольная - - - Custom Выборочный - Marginal По краям - Default Стандартный - Source Источник - - - - Error Ошибка - Error loading the profile. Ошибка при загрузке профиля. - The name conflicts with a default profile name. Please enter a different name. Это имя конфликтует со стандартным именем профиля. Введите другое имя. - Error saving the profile. Ошибка при сохранении профиля. - Error deleting the profile. Ошибка при удалении профиля. + + Fill offcut + Залить отрезан. обл. + + + Fill margins + Залить поля + + + Despeckle + Удалить пятна + DeskewApplyDialog - Apply to Область применения - This page only (already applied) Только к этой странице (уже применено) - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - This page and the following every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К этой странице и каждой второй последующей - Every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К каждой второй странице - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. - Every other selected page К каждой второй выбранной странице - The current page will be included. Текущая страница будет включена в список. @@ -791,27 +606,22 @@ DeskewOptionsWidget - Form - Deskew Компенсация наклона - Auto Автоматически - Manual Вручную - Apply To ... Применить... @@ -819,7 +629,6 @@ DragHandler - Unrestricted dragging is possible by holding down the Shift key. Удерживая кнопку Shift, можно перетаскивать без ограничений. @@ -827,7 +636,6 @@ ErrorWidget - Form @@ -835,68 +643,54 @@ FixDpiDialog - Fix DPI Исправить DPI - Tab 1 - Tab 2 - DPI - Custom Выборочный - x - Apply Применить - Need Fixing Необх. испр. - - All Pages Все страницы - DPI is too large and most likely wrong. Слишком большой DPI и, скорее всего, неправильный. - DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI слишком мал. Даже если это правильно, вы не получите приемлемых результатов. - DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI слишком мал для этого пиксельного размера. Такая комбинация, вероятно, приведет к ошибкам переполнения памяти. - %1 (page %2) %1 (стр. %2) @@ -904,7 +698,6 @@ ImageViewBase - Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Используйте колесо мыши для увеличения. В увеличенном виде доступно перетаскивание. @@ -912,17 +705,14 @@ InteractiveXSpline - Click to create a new control point. Кликните для создания новой контрольной точки. - This point can be dragged. Hold Ctrl or Shift to drag along axes. Контрольная точка доступна для перетаскивания. Удерживайте Ctrl или Shift для перетаскивания вдоль осей. - Drag this point or delete it by pressing Del or D. Контрольная точка доступна для перетаскивания и для удаления через Del или D. @@ -930,13 +720,11 @@ LoadFileTask - The following file could not be loaded: %1 Файл не загрузился:%1 - The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. Следующий файл не существует:<br>%1<br><br>Используйте <a href="#relink">Relinking Tool</a> чтобы найти его. @@ -944,17 +732,14 @@ LoadFilesStatusDialog - Some files failed to load Некоторые файлы не удалось загрузить - Loaded successfully: %1 Загружено: %1 - Failed to load: %1 Не удалось загрузить: %1 @@ -962,420 +747,255 @@ MainWindow - MainWindow - Tools Инструменты - Units Единицы - File Файл - Help Справка - Thumbnails Миниатюры - Keep current page in view. Держать текущую страницу в поле зрения. - Follow page Следовать за страницей - - Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. - Используйте Home, End, PgUp (или Q), PgDown (или W) для навигации по страницам. - - - Filters Фильтры - Debug Mode Режим отладки - - Save Project Сохранить проект - - Ctrl+S - - - - Save Project As ... Сохранить проект как ... - - Next Page Следующая страница - - PgDown - - - - - Previous Page Предыдущая страница - - PgUp - - - - New Project ... Новый проект ... - - Ctrl+N - - - - Open Project ... Открыть проект ... - - Ctrl+O - - - - - Q - - - - - W - - - - Close Project Закрыть проект - - Ctrl+W - - - - Quit Выход - - Ctrl+Q - - - - Settings ... Настройки ... - First Page Первая страница - - Home - - - - Last Page Последняя страница - - End - - - - About О программе - Fix DPI ... Исправить DPI ... - Relinking ... Пути в проекте ... - - - - - - Switch filter to orientation Перейти к ориентации - - O - - - - Switch filter to split pages Перейти к разрезке страниц - - E - - - - Switch filter to deskew Перейти к компенсации наклона - - N - - - - Switch filter to select content Перейти к полезной области - - V - - - - Switch filter to margins Перейти к полям - - K - - - - Switch filter to output Перейти к выводу - - P - - - - Pixels Пиксели - Millimetres Миллиметры - Inches Дюймы - Centimetres Сантиметры - Default parameters ... Параметры по умолчанию... - Stop batch processing Остановить пакетную обработку - Save the project? Сохранить этот проект? - - Save - Сохранить - - - - Discard - Отклонить - - - - Cancel - Отмена - - - Insert before ... Вставить перед ... - Insert after ... Вставить после ... - Remove from project ... Удалить из проекта ... - Insert here ... Вставить сюда ... - - Scan Tailor Projects Проекты Scan Tailor - Open Project Открыть проект - - - - Error Ошибка - Unable to open the project file. Не удалось открыть файл проекта. - The project file is broken. Файл проекта поврежден. - version версия - Output is not yet possible, as the final size of pages is not yet known. To determine it, run batch processing at "Select Content" or "Margins". Вывод невозможен, поскольку еще не известны итоговые размеры страниц. Для их определения, прогоните пакетную обработку на этапах "Полезная Область' или "Поля". - Unnamed Без имени - %2 - ScanTailor Advanced [%1bit] - - Error saving the project file! Ошибка при сохранении файла! - Files to insert Файлы для вставки - Images not in project (%1) Изображения не в проекте (%1) - Skip failed files Пропустить файлы с ошибкой - Remove Удалить + + Use Home, End, PgUp/Q, PgDown/W to navigate between pages or Shift+PgUp/Q and Shift+PgDown/W to navigate between selected ones. Alt+Wheel - scale thumbnails. + Используйте Home, End, PgUp/Q, PgDown/W для навигации по страницам или Shift+PgUp/Q и Shift+PgDown/W - по выделенным. Alt+Колесо - масштабировать миниматюры. + + + Previous Selected Page + Предыдущая выделенная страница + + + Next Selected Page + Следующая выделенная страница + NewOpenProjectPanel - Form - New Project ... Новый проект ... - Open Project ... Открыть проект ... - Recent Projects Недавние проекты @@ -1383,61 +1003,50 @@ To determine it, run batch processing at "Select Content" or "Mar OrientationApplyDialog - Fix Orientation Исправление ориентации - Apply to Область применения - This page only (already applied) Только к этой странице (уже применено) - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - This page and the following every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К этой странице и каждой второй последующей - Every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К каждой второй странице - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. - Every other selected page К каждой второй выбранной странице - The current page will be included. Текущая страница будет включена в список. @@ -1445,28 +1054,22 @@ To determine it, run batch processing at "Select Content" or "Mar OrientationOptionsWidget - Form - Rotate Поворот - - ... - Reset Сбросить - Apply to ... Применить... @@ -1474,22 +1077,18 @@ To determine it, run batch processing at "Select Content" or "Mar OtsuBinarizationOptionsWidget - Form - 0 - Thinner Тоньше - Thicker Жирнее @@ -1497,109 +1096,88 @@ To determine it, run batch processing at "Select Content" or "Mar OutOfMemoryDialog - Out of memory Нехватка памяти - Out of Memory Situation in Scan Tailor Недостаточно памяти для продолжения работы - Possible reasons Возможные причины - Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? Если вам пришлось исправлять DPI ваших исходных изображений, убедитесь в правильности этих исправлений. - Sometimes your source images may have wrong DPI embedded into them. Scan Tailor tries to detect those, but it's not always easy to tell. You may need to check "Fix DPI even if they look normal" when creating a project and look into "All pages" tab in the "Fix DPI" dialog, which is also accessible from the Tools menu. Иногда неправильные DPI прописаны прямо в исходных изображениях. Scan Tailor пытается обнаруживать такие случаи, но это не всегда ему удается. Возможно вам стоило поставить галочку "Править DPI, даже если они выглядят нормальными" при создании проекта, и проверить файлы во вкладке "Все страницы" диалога исправления DPI. После создания проекта, этот диалог доступен из меню "Инструменты". - Is your output DPI set too high? Usually you don't need it higher than 600. Возможно DPI вывода чрезмерно высок? Обычно нет смысла выставлять его более чем 600. - What can help Что может помочь - Fix your DPIs. Learn how to <a href="http://vimeo.com/12524529">estimate unknown DPIs</a>. Feel free to change the URL to a localized video / text tutorial. Не стесняйтесь изменять URL на фокусируемое видео / текстовая обучающая программа. Исправьте неправильные DPI. Узнайте как <a href="http://vimeo.com/12524529">расчитывать неизвестные DPI</a>. - If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. Если ваше железо и операционная система 64х-битные, вам стоит перейти на 64х-битную версию Scan Tailor. - When working with grayscale images, make sure they are really grayscale. If they are actually color images that just happen to look grayscale, convert them to grayscale using some kind of batch image converter. This will both save memory and increase performance. Если вы работаете с изображениями в оттенках серого, убедитесь что они в таком виде и сохранены. Если же они сохранены как цветные изображения, пересохраните их в режиме оттенков серого используя какой-нибудь пакетрый конвертер изображений. Это сократит расход памяти а также ускорит обработку. - As a last resort, you can save some memory by making sure thumbnails are pre-created rather than created on demand. This can be done by slowly scrolling the thumbnail list all the way from top to bottom before starting any real work. В качестве последней меры, можно сэкономить немного памяти, создав миниатюры страниц заблаговременно. Для этого, перед работой над проектом нужно медленно пройтись по всей ленте миниатюр, давая им возможность подгрузиться с диска. - What won't help Что не поможет - Surprisingly, upgrading your RAM won't help here. The lack of RAM is compensated by the swap mechanism, which makes things slow, but keeps programs running. An out of memory situation means we ran out of memory address space, which has nothing to do with the amount of RAM you have. The only way to increase the memory address space is to go 64-bit hardware, 64-bit operating system and 64-bit Scan Tailor. Удивительно, но покупка дополнительной памяти не решит данную проблему. Нехватка памяти компенсируется механизмом подкачки, который вызывает "тормоза", но все-же позволяет программам продолжать работать. В данном случае произошла нехватка не просто памяти, а адресного пространства памяти, которое не имеет ничего общего с реальным объемом памяти, установленном на компьютере. Единственный способ увеличить адресное пространство - переход на 64х-битное железо, 64х-битную операционную систему и 64х-битный Scan Tailor. - Save Project Сохранить проект - Save Project As ... Сохранить проект как ... - Don't Save Не сохранять - Project Saved Successfully Проект успешно сохранен - Please note that while Scan Tailor tries to catch out-of-memory situations and give you the opportunity to save your project, it's not always possible. This time it succeeded, but the next time it might just crash. Обратите внимание на то, что хотя Scan Tailor и пытается обработать ситуации нехватки памяти, чтобы дать вам возможность сохранить проект, это не всегда бывает возможно. В этот раз все получилось, но в следующий раз программа может просто "упасть". - Scan Tailor Projects Проекты Scan Tailor - Error Ошибка - Error saving the project file! Ошибка при сохранении файла! @@ -1607,37 +1185,30 @@ To determine it, run batch processing at "Select Content" or "Mar OutputApplyColorsDialog - Apply Mode Применить режим вывода - Apply to Область применения - This page only (already applied) Только к этой странице (уже применено) - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. @@ -1645,72 +1216,58 @@ To determine it, run batch processing at "Select Content" or "Mar OutputChangeDewarpingDialog - Apply Dewarping Mode Применить режим Выпрямления строк - Mode Режим - Off Отключено - Auto (experimental) Автоматически (эксперимент.) - Marginal (experimental) По краям (эксперимент.) - Manual Вручную - Options Настройки - Post deskew Компенсировать поворот - Apply to Область применения - This page only Только к этой странице - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. @@ -1718,42 +1275,34 @@ To determine it, run batch processing at "Select Content" or "Mar OutputChangeDpiDialog - Apply Output Resolution Разрешение на выходе - DPI - Apply to Область применения - This page only Только к этой странице - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - Selected pages К выделенным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. @@ -1761,325 +1310,257 @@ To determine it, run batch processing at "Select Content" or "Mar OutputOptionsWidget - Form - Output Resolution (DPI) Разрешение на выходе (DPI) - 0 - - Change ... Применить... - Mode Режим - Options Настройки - - Cut margins - Подрезать поля - - - Normalize illumination before binarization. Нормализовать освещенность перед бинаризацией. - Equalize illumination (B&&W) Выровнять освещение (ЧБ) - Normalize illumination in color mode / in picture zones in mixed mode. Нормализовать освещенность в цветном режиме / в зонах картинок в смешанном режиме. - Equalize illumination (Color) Выровнять освещение (Цвет.) - Savitzky-Golay smoothing Сглаживание Савицкого-Голея - Morphological smoothing Морфологическое сглаживание - Filling Заливка - Color: Цвет: - Threshold Порог - Method: Метод: - Color operations Цветовые операции - Split the image into color segments and colorize b&w mask. Поделить изображение на цветовые сегменты и раскрасить ЧБ маску. - Color segmentation Цветовая сегментация - R - Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. Настройка красного компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к красному и наоборот для положительных. - G - Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. Настройка зеленого компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к зеленому и наоборот для положительных. - B - Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. Настройка синего компонента. Отрицательные значения обозначают, что сегментер будет более чувствителен к синему и наоборот для положительных. - Reduce noise: Уменьшить шум: - Reduce the number of colors of the output image by grouping similar colors. Уменьшить количество цветов выходного изображения, группируя похожие цвета. - Posterize Постеризовать - Level: Уровень: - Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. Меньшее значение обозначает меньшее кол-во цветов в выходном изображении, значения 2 и 6 включительно гарантируют индексированое изображение. - Normalize Нормализовать - Make dark and light gray gradients black and white respectively. Сделать темные и светлые оттенки серого черными и белыми соответственно. - Force b&&w Принудительный ЧБ - Picture Shape Форма картинок - Sensitivity (%): Чувствительность (%): - Higher search sensitivity Большая чувств. поиска - - - - Apply To ... Применить... - Splitting Разделение - Split output Разделить выход - B&&W foreground ЧБ передний слой - Save the original background of the foreground layer. Сохранить оригинальный фон переднего слоя. - Original background Оригинальный фон - Color foreground Цветной передний слой - Despeckling Удаление пятен - - No despeckling - Не удалять пятна + Depth perception + Восприятие глубины - - Cautious despeckling - Осторожное удаление пятен + Dewarping + Выпрямление - - - - ... - + Fill offcut + Залить отрезан. обл. - - Normal despeckling - Обычное удаление пятен + Fill margins + Залить поля - - Aggressive despeckling - Агрессивное удаление пятен + Despeckle + Удалить пятна - - Depth perception - Восприятие глубины + Processing + Обработка - - Dewarping - Выпрямление + This option should be enabled when the page has dark content on light background and disabled if vice versa in order to correct processing algorithms. + Эту опцию следует включить если страница имеет темный контент на светлом фоне и выключить при обратном для коррекции алгоритмов обработки. + + + Black on white mode + Режим черного на белом PageLayoutApplyDialog - Apply to Область применения - This page only (already applied) Только к этой странице (уже применено) - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - This page and the following every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К этой странице и следующей каждой второй странице - Every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К каждой второй странице - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. - Every other selected page К каждой второй выбранной странице - The current page will be included. Текущая страница будет включена в список. @@ -2087,186 +1568,168 @@ To determine it, run batch processing at "Select Content" or "Mar PageLayoutOptionsWidget - Form - Margins Поля - Top Сверху - - - - - - - - - - - ... - Bottom Снизу - Left Слева - Right Справа - - Apply To ... Применить... - Alignment Выравнивание - Auto auto Автоматически - Manual manual Вручную - Original original Оригинал - Auto Margins Автоматические поля - Auto aligning Авто выравнивание - Enable horizontal Вкл. горизонтальное - Enable vertical Вкл. вертикальное - Match size with other pages Выровнять размер с остальн. стр. + + Guides Help + Помощь по направляющим + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> to create/remove guides from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> on a guide to delete that guide from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift+LMB</span><span style=" font-size:7pt;"> - drag the guide under the cursor.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift/Ctrl+LMB</span><span style=" font-size:7pt;"> on the content rectangle - drag the page content. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to restrict moving along the horizontal axis only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for the vertical one. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> for usual dragging.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Double-click</span><span style=" font-size:7pt;"> on content - automatically attach that content to the nearest guide. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to select vertical guides only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for horizontal ones. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> to attach that to both the nearest vertical and horizontal guides.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣</span><span style=" font-size:7pt;"> Use the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> to enable/disable showing the hard margins rectangle.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Правый клик</span><span style=" font-size:7pt;"> - создать/удалить направляющие из </span><span style=" font-size:7pt; font-weight:600;">контекстного меню</span><span style=" font-size:7pt;">.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Правый клик</span><span style=" font-size:7pt;"> по направляющей - удалить направляющую из </span><span style=" font-size:7pt; font-weight:600;">контекстного меню</span><span style=" font-size:7pt;">.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift+ЛКМ</span><span style=" font-size:7pt;"> - перетащить направляющую под курсором.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift/Ctrl+ЛКМ</span><span style=" font-size:7pt;"> по прямоугольнику с контентом - перетащить контент страницы. Удерживайте </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> для перетаскивания по горизонтальной оси или </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> по вертикальной. Удерживайте </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> для обычного перетаскивания.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Двойной клик</span><span style=" font-size:7pt;"> по контенту - автоматически прикрепить контент к ближайшей направляющей. Удерживайте </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> для выбора только вертикальных направляющих или </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> для горизонтальных. Удерживайте </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> для прикрепления к ближайшим вертикальной и горизонтальной направляющим.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣</span><span style=" font-size:7pt;"> Используйте </span><span style=" font-size:7pt; font-weight:600;">контекстное меню</span><span style=" font-size:7pt;"> для включения/выключения отображения прямоугольника жестких полей.</span></p></body></html> + PageSplitModeDialog - Split Pages Разрезание страниц - Mode Тип разреза - Auto Автоматически - Manual Вручную - Options Настройки - Apply cut Применить разрез - Apply to Область применения - This page only Только к этой странице - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - This page and the following every other page Все нечетные или четные страницы, в зависимости от текущей страницы. К этой странице и следующей каждой второй странице - Every other page Все нечетные или четные страницы, в зависимости от текущей страницы. К каждой второй странице - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. - Every other selected page К каждой второй выбранной странице - The current page will be included. Текущая страница будет включена в список. @@ -2274,37 +1737,30 @@ To determine it, run batch processing at "Select Content" or "Mar PageSplitOptionsWidget - Form - Page Layout Тип разреза - ? - Change ... Применить... - Split Line Разделительная линия - Auto Автоматически - Manual Вручную @@ -2312,22 +1768,18 @@ To determine it, run batch processing at "Select Content" or "Mar PictureZonePropDialog - Zone Properties Свойства зоны - Subtract from all layers Вычесть из всех слоев - Add to auto layer Добавить к авто-слою - Subtract from auto layer Вычесть из авто-слоя @@ -2335,41 +1787,30 @@ To determine it, run batch processing at "Select Content" or "Mar ProjectFilesDialog - Project Files Файлы проекта - - Input Directory Директория ввода - - Browse Обзор - - Output Directory Директория вывода - Files Not In Project Файлы вне проекта - - Select All Выбрать все - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> @@ -2380,12 +1821,10 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Добавить выбранные файлы в проект.</p></body></html> - >> - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> @@ -2396,72 +1835,54 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Убрать выбранные файлы из проекта.</p></body></html> - << - Files In Project Файлы в проекте - Right to left layout (for Hebrew and Arabic) Система письменности справа-налево (для Иврита или Арабского) - Fix DPIs, even if they look OK Исправить DPI, даже если он выглядят нормально - - - - - - Error Ошибка - No files in project! В проекте нет файлов! - Input directory is not set or doesn't exist. Директория ввода не задана или не существует. - Input and output directories can't be the same. Директории ввода и вывода не могут совпадать. - Create Directory? Создать директорию? - Output directory doesn't exist. Create it? Директория вывода не существует. Создать ее? - Unable to create output directory. Невозможно создать директорию вывода. - Output directory is not set or doesn't exist. Директория вывода не задана или не существует. - Some of the files failed to load. Either we don't support their format, or they are broken. You should remove them from the project. @@ -2473,41 +1894,33 @@ You should remove them from the project. ProjectOpeningContext - - Error Ошибка - The project file is not compatible with the current application version. Файл проекта несовместим с текущей версией приложения. - Unable to interpret the project file. - Не удалось интерпритировать файл проекта. + Не удалось интерпретировать файл проекта. QObject - px - mm мм - cm см - in @@ -2515,32 +1928,26 @@ You should remove them from the project. RelinkingDialog - Relinking Пути проекта - Undo Откатить действие - ... - Substitution File for %1 Файл для %1 - Substitution Directory for %1 Папка для %1 - This change would merge several files into one. Это изменение объединит несколько файлов в один. @@ -2548,17 +1955,14 @@ You should remove them from the project. RemovePagesDialog - Remove Pages Удалить страницы - Remove %1 page(s) from project? Удалить %1 страниц из проекта? - Corresponding output files will be deleted, while input files will remain. Соответствующие файлы вывода будут удалены, а исходные останутся на диске. @@ -2566,27 +1970,22 @@ You should remove them from the project. SauvolaBinarizationOptionsWidget - Form - Coef: Коэф.: - The dimensions of a pixel neighborhood to consider. Размер области с соседними пикселями для расчета. - Window size: Размер окна: - Default value is 0.34. Значение по умолчанию - 0.34. @@ -2594,76 +1993,62 @@ You should remove them from the project. SelectContentApplyDialog - Select Content Полезная область - Options Настройки - Apply content box Применить полезную область - Apply page box Применить область страницы - Apply to Область применения - This page only (already applied) Только к этой странице (уже применено) - All pages Ко всем страницам - This page and the following ones К этой странице и всем последующим - This page and the following every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К этой странице и следующей каждой второй странице - Every other page All odd or even pages, depending on the current page being odd or even. Все нечетные или четные страницы, в зависимости от текущей страницы. К каждой второй странице - Selected pages К выбранным страницам - Use Ctrl+Click / Shift+Click to select multiple pages. Используйте Ctrl+Клик / Shift+Клик для выбора группы страниц. - Every other selected page К каждой второй выбранной странице - The current page will be included. Текущая страница будет включена в список. @@ -2671,65 +2056,50 @@ You should remove them from the project. SelectContentOptionsWidget - Form - Page Box Зона распознавания - Options Настройки - Shift with corners while they are in black. Скорректировать края по черной области. - Fine Tune Page Corners Тонкая настройка краев страницы - - Disable Сбросить - - Auto Автоматически - Content Box Полезная область - - Manual Вручную - Width Ширина - Height Высота - Apply to ... Применить... @@ -2737,163 +2107,173 @@ You should remove them from the project. SettingsDialog - Settings Настройки - - General Общие - Accelerate user interface with OpenGL Использовать ускорение OpenGL для интерфейса - Device: %1 Устройство: %1 - Auto-save the existing project Автосохранение текущего проекта - Processing Обработка - Deviation Отклонение - Highlight the thumbnails of pages with high deviation Подсвечивать страницы с большим отклонением - Params Параметры - Deksew: Комп. поворота: - Select content: Полезн. обл.: - - - Deviation multiplier: a higher value means lower sensivity. Множитель отклонения: большее значение обозначает меньшую чувствительность. - Margins: Поля: - - - The minimum deviation to be highlighted. Минимальное отклонение, которое будет подсвечено. - Color Scheme: Цветовая схема: - Language: Язык: - Saving Сохранение - B&W Compression: ЧБ сжатие: - Color Compression: Цветное сжатие: - Your hardware / driver don't provide the necessary features Вашим оборудованием / драйвером эта функция не поддерживается - Dark Темная - Light Светлая - - None Без сжатия - - LZW - - Deflate - CCITT G4 - JPEG - Information Информация - ScanTailor need to be restarted to apply the color scheme changes. Для применения цветовой схемы перезапустите приложение. + + User Interface + Интерфейс + + + Thumbnails + Миниатюры + + + Quality: + Качество: + + + The pixel size of the thumbnail image. The default value is 200. + Пиксельный размер изображения миниатюры. Значение по умолчанию - 200. + + + Size: + Размер: + + + The thumbnail size in the view. The default value is 250. + Размер миниатюры при отображении. Значение по умолчанию - 250. + + + White on black detection + Определение белого на черном + + + Auto detect pages with light content on dark background. The corrections to all the auto algorithms are made for such pages. + Автоматически определять страницы со светлым контентом на темном фоне, чтобы откорректировать все автоматические алгоритмы. + + + Auto detect light content on dark background + Автом. определять светлый контент на темном фоне + + + Whether to use auto detection at the output stage. The wrong result can be changed manually in the output filter options. + Нужно ли использовать авто-определение на стадии выхода. Неверный результат может быть изменен вручную в опциях на стадии выхода. + + + Use auto detection at the output stage + Авто-определение на стадии выхода + + + Native + Нативная + StageListView - Launch batch processing Запустить пакетную обработку @@ -2901,62 +2281,50 @@ You should remove them from the project. StatusBarPanel - Form - Mouse position. Позиция курсора. - Mouse position relative to page. Позиция курсора по отношению к странице. - Physical size. Физический размер. - Physical size of image. Физический размер изображения. - Page number. Номер страницы. - Position of the selected page in current order. Позиция выбранной страницы в текущем порядке. - Page information. Информация о странице. - Page name and type. Имя страницы и тип. - p. %1 / %2 с. %1 / %2 - [L] [Л] - [R] [П] @@ -2964,18 +2332,14 @@ You should remove them from the project. SystemLoadWidget - Form - System load Загрузка системы - - ... @@ -2983,7 +2347,6 @@ You should remove them from the project. ThumbnailSequence - %1 (page %2) %1 (стр. %2) @@ -2991,47 +2354,38 @@ You should remove them from the project. WolfBinarizationOptionsWidget - Form - The dimensions of a pixel neighborhood to consider. Размер области с соседними пикселями для расчета. - Window size: Размер окна: - The minimum possible gray level that can be made white. Минимальный уровень серого, который можно сделать белым. - Upper Bound: Верхняя граница: - Lower bound: Нижняя граница: - The maximum possible gray level that can be made black. Максимальный уровень серого, который можно сделать черным. - Coeff: Коэф.: - Default value is 0.3. Значение по умолчанию — 0.3. @@ -3039,12 +2393,10 @@ You should remove them from the project. ZoneContextMenuInteraction - Delete Удалить - Properties Свойства @@ -3052,45 +2404,37 @@ You should remove them from the project. ZoneCreationInteraction - Click to finish this rectangular zone. ESC to cancel. Кликните для завершения этой прямоугольной зоны. ESC для отмены. - Click to finish this zone. ESC to cancel. Кликните для завершения этой зоны. ESC для отмены. - Connect first and last points to finish this zone. ESC to cancel. Соедините первую и последнюю точку для завершения этой зоны. ESC для отмены. - - Hold Ctrl to create a rectangular zone, Alt+LMB to switch to lasso mode. ESC to cancel. - Удерживайте Ctrl для создания прямоугольной зоны, Alt+ЛКМ для переключения в режим лассо. ESC для отмены. + Hold Ctrl to create a rectangular zone or Shift+Alt+LMB to use lasso mode. ESC to cancel. + ZoneDefaultInteraction - Drag the vertex. Hold Ctrl to make the vertex angle right. Перетаскивайте вершину. Удерживайте Ctrl, чтобы сделать угол при вершине прямым. - Click to create a new vertex here. Кликните для создания новой вершины. - Right click to edit zone properties. Hold Shift to drag the zone or Shift+Ctrl to copy. Press Del to delete this zone. ПКМ для редактирования свойств зоны. Удерживайте Shift для перетаскивания зоны или Shift+Ctrl для копирования. Нажмите Del, чтобы удалить зону. - Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone. Кликните, чтобы создать новую зону. Используйте Ctrl+Alt+Клик для копирования последней созданной зоны. @@ -3098,7 +2442,6 @@ You should remove them from the project. ZoneDragInteraction - Release left mouse button to finish dragging. Отпустите левую кнопку мыши для завершения перетаскивания. @@ -3106,12 +2449,10 @@ You should remove them from the project. ZoneVertexDragInteraction - Merge these two vertices. Объединить эти две вершины. - Move the vertex to one of its neighbors to merge them. Придвиньте вершину к одной из соседних вершин, чтобы объединить их. @@ -3119,17 +2460,14 @@ You should remove them from the project. deskew::Filter - Natural order Естественный порядок - Order by decreasing deviation Сортировка по убывающему отклонению - Deskew Компенсация наклона @@ -3137,12 +2475,10 @@ You should remove them from the project. deskew::ImageView - Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Используйте Ctrl+Колесо мыши для вращения, или Ctrl+Shift+Колесо мыши для более точного вращения. - Drag this handle to rotate the image. Тяните эту рукоятку чтобы повернуть изображение. @@ -3150,7 +2486,6 @@ You should remove them from the project. deskew::OptionsWidget - Apply Deskew Область примененияомпенсацию наклона @@ -3158,7 +2493,6 @@ You should remove them from the project. fix_orientation::Filter - Fix Orientation Исправление ориентации @@ -3166,29 +2500,22 @@ You should remove them from the project. output::ChangeDpiDialog - Custom Особый - - - Error Ошибка - DPI is not set. DPI не указан. - DPI is too low! DPI слишком маленький! - DPI is too high! DPI слишком большой! @@ -3196,7 +2523,6 @@ You should remove them from the project. output::FillZoneEditor - Pick color Выбрать цвет @@ -3204,109 +2530,100 @@ You should remove them from the project. output::Filter - Output Вывод + + Natural order + Естественный порядок + + + Order by completeness + Сортировка по завершенности + output::OptionsWidget - Black and White Черно-белый - Color / Grayscale Цветной / Серый - Mixed Смешанный - Otsu - Sauvola - Wolf - Background Фон - White Белый - Free Свободная - Rectangular Прямоугольная - Apply Splitting Settings Применить настройки разделения - Apply Despeckling Level Применить уровень удаления пятен - Apply Depth Perception Применить восприятие глубины - - Off Отключено - Auto Автоматически - Manual Вручную - Marginal По краям - deskew disabled Выравнивание отключено + + Apply Processing Settings + Применить настройки обработки + output::TabbedImageView - Use Ctrl+1..5 to switch the tabs. Используйте Ctrl+1..5 для переключения табов. @@ -3314,37 +2631,30 @@ You should remove them from the project. output::Task::UiUpdater - Picture zones are only available in Mixed mode. Зоны картинок доступны только в режиме "Смешанный". - Despeckling can't be done in Color / Grayscale mode. Удаление пятен не делается в режиме "Цветной / Серый". - Output Вывод - Picture Zones Зоны картинок - Fill Zones Зоны заливки - Dewarping Выпрямл. строк - Despeckling Удаление пятен @@ -3352,27 +2662,22 @@ You should remove them from the project. page_layout::Filter - Natural order Естественный порядок - Order by increasing width Сортировка по возрастающей ширине - Order by increasing height Сортировка по возрастающей высоте - Order by decreasing deviation Сортировка по убывающему отклонению - Margins Поля @@ -3380,20 +2685,49 @@ You should remove them from the project. page_layout::ImageView - Resize margins by dragging any of the solid lines. Меняйте размеры полей, перетаскивая хоть внешние, хоть внутренние сплошные линии. + + Hold left mouse button to drag the page content. + Удерживайте ЛКМ для перетаскивания области страницы. + + + Release left mouse button to finish dragging. + Отпустите левую кнопку мыши для завершения перетаскивания. + + + Add a horizontal guide + Добавить горизонтальн. направл. + + + Add a vertical guide + Добавить вертикальн. направл. + + + Remove all the guides + Удалить все направляющие + + + Remove this guide + Удалить направляющую + + + Show hard margins rectangle + Показывать жесткие поля + + + Drag the guide. + Перетаскивайте направляющую. + page_layout::OptionsWidget - Apply Margins Применить поля - Apply Alignment Применить выравнивание @@ -3401,17 +2735,14 @@ You should remove them from the project. page_split::Filter - Natural order Естественный порядок - Order by split type Сортировка по типу разреза - Split Pages Разрезка страниц @@ -3419,7 +2750,6 @@ You should remove them from the project. page_split::ImageView - Drag the line or the handles. Тяните линию или рукоятки. @@ -3427,15 +2757,10 @@ You should remove them from the project. page_split::OptionsWidget - - - Set manually Установлено вручную - - Auto detected Определено автоматически @@ -3443,7 +2768,6 @@ You should remove them from the project. page_split::UnremoveButton - Restore removed page. Вернуть на место удаленную страницу. @@ -3451,27 +2775,22 @@ You should remove them from the project. select_content::Filter - Natural order Естественный порядок - Order by increasing width Сортировка по возрастающей ширине - Order by increasing height Сортировка по возрастающей высоте - Order by decreasing deviation Сортировка по убывающему отклонению - Select Content Полезная область @@ -3479,43 +2798,34 @@ You should remove them from the project. select_content::ImageView - Use the context menu to enable / disable the content box. Hold Shift to drag a box. Use double-click on content to automatically adjust the content area. - Используйте контекстное меню для включения / выключения полезной области. Удерживайте Shift для перетаскивания области. Используйте двойной клик по контенту, чтобы автоматически настроить полезную область. + Исп. контекстное меню для вкл. / выкл. полезной области. Удерж. Shift для перетаскивания области. Исп. двойной клик по контенту, чтобы автом. настроить полезную область. - Drag lines or corners to resize the content box. Перетаскивайте линии или углы, чтобы изменить размеры полезной области. - Drag lines or corners to resize the page box. Перетаскивайте стороны или углы для изменения размера области страницы. - Hold left mouse button to drag the content box. Зажмите левую кнопку мыши для перетаскивания полезной области. - - Release left mouse button to finish dragging. Отпустите левую кнопку мыши для завершения перетаскивания. - Hold left mouse button to drag the page box. Удерживайте левую кнопку мыши для перетаскивания области страницы. - Create Content Box Создать полезную область - Remove Content Box Убрать полезную область diff --git a/translations/scantailor_untranslated.ts b/translations/scantailor_untranslated.ts index 38fc143ff..854b51acd 100644 --- a/translations/scantailor_untranslated.ts +++ b/translations/scantailor_untranslated.ts @@ -4,95 +4,81 @@ AboutDialog - ScanTailor Advanced - About - Scan Tailor is an interactive post-processing tool for scanned pages. It performs operations such as page splitting, skew correction, adding/removing margins, and others. You give it raw scans, and you get pages ready to be printed or assembled into a PDF or DJVU file. Scanning and optical character recognition is out of scope of this project. - Authors - - Lead Developer - - - - Joseph Artsimovich - Contributors - U235 - Picture auto-detection algorithm. - Robert B. - First generation dewarping algorithm. - Andrey Bergman - System load adjustment. - Petr Kovář - Command line interface, ver. Enhanced - Vadim Kuznetsov - ver. Plus - monday2000 - ver. Featured - Alexander Trufanov - ver. Universal - - 4lex4 - ver. Advanced + License - - License + Lead Developer of original version + + + + Lead Developer of Advanced version + + + + 4lex4 BatchProcessingLowerPanel - Form - Beep when finished @@ -100,7 +86,6 @@ ColorPickupInteraction - Click on an area to pick up its color, or ESC to cancel. @@ -108,680 +93,510 @@ DefaultParamsDialog - Default parameters - Save - Delete - Units: - Fix Orientation - Rotate - - - - - - - - - - - - - - - - ... - Reset - Split Pages - - Mode - Page Layout - - Deskew - - - - - Auto - - - - Manual - Select Content - Page Box - - Disable - - Options - Shift with corners while they are in black. - Fine Tune Page Corners - Width - Height - Content Box - - Margins - Auto Margins - Top - Right - Left - Bottom - Alignment - Auto auto - Manual manual - Original original - Auto aligning - Enable horizontal - Enable vertical - Match size with other pages - Output - - Cut margins - - - - Normalize illumination before binarization. - Equalize illumination (B&&W) - Normalize illumination in color mode / in picture zones in mixed mode. - Equalize illumination (Color) - Savitzky-Golay smoothing - Morphological smoothing - Filling - Color: - Color operations - Split the image into color segments and colorize b&w mask. - Color segmentation - R - Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. - G - Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. - B - Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. - Reduce noise: - Reduce the number of colors of the output image by grouping similar colors. - Posterize - Level: - Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. - Normalize - Make dark and light gray gradients black and white respectively. - Force b&&w - Threshold - Method: - 0 - Thinner - Thicker - Coef: - - The dimensions of a pixel neighborhood to consider. - - Window size: - Default value is 0.34. - The minimum possible gray level that can be made white. - Upper Bound: - Lower bound: - The maximum possible gray level that can be made black. - Coeff: - Default value is 0.3. - Picture Shape - Sensitivity (%): - Higher search sensivity - Output Resolution (DPI) - Splitting - Split output - B&&W foreground - Save the original background of the foreground layer. - Original background - Color foreground - Despeckling - - No despeckling - - - - - Cautious despeckling - - - - - Normal despeckling - - - - - Aggressive despeckling - - - - Dewarping - Post deskew - Depth perception - Black and White - Color / Grayscale - Mixed - Background - White - Otsu - Sauvola - Wolf - - Off - Free - Rectangular - - - Custom - Marginal - Default - Source - - - - Error - Error loading the profile. - The name conflicts with a default profile name. Please enter a different name. - Error saving the profile. - Error deleting the profile. + + Fill offcut + + + + Fill margins + + + + Despeckle + + DeskewApplyDialog - Apply to - This page only (already applied) - All pages - This page and the following ones - This page and the following every other page All odd or even pages, depending on the current page being odd or even. - Every other page All odd or even pages, depending on the current page being odd or even. - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. - Every other selected page - The current page will be included. @@ -789,27 +604,22 @@ DeskewOptionsWidget - Form - Deskew - Auto - Manual - Apply To ... @@ -817,7 +627,6 @@ DragHandler - Unrestricted dragging is possible by holding down the Shift key. @@ -825,7 +634,6 @@ ErrorWidget - Form @@ -833,68 +641,54 @@ FixDpiDialog - Fix DPI - Tab 1 - Tab 2 - DPI - Custom - x - Apply - Need Fixing - - All Pages - DPI is too large and most likely wrong. - DPI is too small. Even if it's correct, you are not going to get acceptable results with it. - DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. - %1 (page %2) @@ -902,7 +696,6 @@ ImageViewBase - Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. @@ -910,17 +703,14 @@ InteractiveXSpline - Click to create a new control point. - This point can be dragged. Hold Ctrl or Shift to drag along axes. - Drag this point or delete it by pressing Del or D. @@ -928,13 +718,11 @@ LoadFileTask - The following file could not be loaded: %1 - The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. @@ -942,17 +730,14 @@ LoadFilesStatusDialog - Some files failed to load - Loaded successfully: %1 - Failed to load: %1 @@ -960,419 +745,254 @@ MainWindow - MainWindow - Tools - Units - File - Help - Thumbnails - Keep current page in view. - Follow page - - Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. - - - - Filters - Debug Mode - - Save Project - - Ctrl+S - - - - Save Project As ... - - Next Page - - PgDown - - - - - Previous Page - - PgUp - - - - New Project ... - - Ctrl+N - - - - Open Project ... - - Ctrl+O - - - - - Q - - - - - W - - - - Close Project - - Ctrl+W - - - - Quit - - Ctrl+Q - - - - Settings ... - First Page - - Home - - - - Last Page - - End - - - - About - Fix DPI ... - Relinking ... - - - - - - Switch filter to orientation - - O - - - - Switch filter to split pages - - E - - - - Switch filter to deskew - - N - - - - Switch filter to select content - - V - - - - Switch filter to margins - - K - - - - Switch filter to output - - P - - - - Pixels - Millimetres - Inches - Centimetres - Default parameters ... - Stop batch processing - Save the project? - - Save - - - - - Discard - - - - - Cancel - - - - Insert before ... - Insert after ... - Remove from project ... - Insert here ... - - Scan Tailor Projects - Open Project - - - - Error - Unable to open the project file. - The project file is broken. - version - Output is not yet possible, as the final size of pages is not yet known. To determine it, run batch processing at "Select Content" or "Margins". - Unnamed - %2 - ScanTailor Advanced [%1bit] - - Error saving the project file! - Files to insert - Images not in project (%1) - Skip failed files - Remove + + Use Home, End, PgUp/Q, PgDown/W to navigate between pages or Shift+PgUp/Q and Shift+PgDown/W to navigate between selected ones. Alt+Wheel - scale thumbnails. + + + + Previous Selected Page + + + + Next Selected Page + + NewOpenProjectPanel - Form - New Project ... - Open Project ... - Recent Projects @@ -1380,59 +1000,48 @@ To determine it, run batch processing at "Select Content" or "Mar OrientationApplyDialog - Fix Orientation - Apply to - This page only (already applied) - All pages - This page and the following ones - This page and the following every other page All odd or even pages, depending on the current page being odd or even. - Every other page All odd or even pages, depending on the current page being odd or even. - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. - Every other selected page - The current page will be included. @@ -1440,28 +1049,22 @@ To determine it, run batch processing at "Select Content" or "Mar OrientationOptionsWidget - Form - Rotate - - ... - Reset - Apply to ... @@ -1469,22 +1072,18 @@ To determine it, run batch processing at "Select Content" or "Mar OtsuBinarizationOptionsWidget - Form - 0 - Thinner - Thicker @@ -1492,108 +1091,87 @@ To determine it, run batch processing at "Select Content" or "Mar OutOfMemoryDialog - Out of memory - Out of Memory Situation in Scan Tailor - Possible reasons - Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? - Sometimes your source images may have wrong DPI embedded into them. Scan Tailor tries to detect those, but it's not always easy to tell. You may need to check "Fix DPI even if they look normal" when creating a project and look into "All pages" tab in the "Fix DPI" dialog, which is also accessible from the Tools menu. - Is your output DPI set too high? Usually you don't need it higher than 600. - What can help - Fix your DPIs. Learn how to <a href="http://vimeo.com/12524529">estimate unknown DPIs</a>. Feel free to change the URL to a localized video / text tutorial. - If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. - When working with grayscale images, make sure they are really grayscale. If they are actually color images that just happen to look grayscale, convert them to grayscale using some kind of batch image converter. This will both save memory and increase performance. - As a last resort, you can save some memory by making sure thumbnails are pre-created rather than created on demand. This can be done by slowly scrolling the thumbnail list all the way from top to bottom before starting any real work. - What won't help - Surprisingly, upgrading your RAM won't help here. The lack of RAM is compensated by the swap mechanism, which makes things slow, but keeps programs running. An out of memory situation means we ran out of memory address space, which has nothing to do with the amount of RAM you have. The only way to increase the memory address space is to go 64-bit hardware, 64-bit operating system and 64-bit Scan Tailor. - Save Project - Save Project As ... - Don't Save - Project Saved Successfully - Please note that while Scan Tailor tries to catch out-of-memory situations and give you the opportunity to save your project, it's not always possible. This time it succeeded, but the next time it might just crash. - Scan Tailor Projects - Error - Error saving the project file! @@ -1601,37 +1179,30 @@ To determine it, run batch processing at "Select Content" or "Mar OutputApplyColorsDialog - Apply Mode - Apply to - This page only (already applied) - All pages - This page and the following ones - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. @@ -1639,72 +1210,58 @@ To determine it, run batch processing at "Select Content" or "Mar OutputChangeDewarpingDialog - Apply Dewarping Mode - Mode - Off - Auto (experimental) - Marginal (experimental) - Manual - Options - Post deskew - Apply to - This page only - All pages - This page and the following ones - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. @@ -1712,42 +1269,34 @@ To determine it, run batch processing at "Select Content" or "Mar OutputChangeDpiDialog - Apply Output Resolution - DPI - Apply to - This page only - All pages - This page and the following ones - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. @@ -1755,323 +1304,255 @@ To determine it, run batch processing at "Select Content" or "Mar OutputOptionsWidget - Form - Output Resolution (DPI) - 0 - - Change ... - Mode - Options - - Cut margins - - - - Normalize illumination before binarization. - Equalize illumination (B&&W) - Normalize illumination in color mode / in picture zones in mixed mode. - Equalize illumination (Color) - Savitzky-Golay smoothing - Morphological smoothing - Filling - Color: - Threshold - Method: - Color operations - Split the image into color segments and colorize b&w mask. - Color segmentation - R - Red component adjustment. A negative value means the segmenter will be more sensitive to red and vice versa for a positive one. - G - Green component adjustment. A negative value means the segmenter will be more sensitive to green and vice versa for a positive one. - B - Blue component adjustment. A negative value means the segmenter will be more sensitive to blue and vice versa for a positive one. - Reduce noise: - Reduce the number of colors of the output image by grouping similar colors. - Posterize - Level: - Lower value means lower count of colors in the output image, values between 2 and 6 inclusive guarantee an indexed image. - Normalize - Make dark and light gray gradients black and white respectively. - Force b&&w - Picture Shape - Sensitivity (%): - Higher search sensitivity - - - - Apply To ... - Splitting - Split output - B&&W foreground - Save the original background of the foreground layer. - Original background - Color foreground - Despeckling - - No despeckling + Depth perception - - Cautious despeckling + Dewarping - - - - ... + Fill offcut - - Normal despeckling + Fill margins - - Aggressive despeckling + Despeckle - - Depth perception + Processing - - Dewarping + This option should be enabled when the page has dark content on light background and disabled if vice versa in order to correct processing algorithms. + + + + Black on white mode PageLayoutApplyDialog - Apply to - This page only (already applied) - All pages - This page and the following ones - This page and the following every other page All odd or even pages, depending on the current page being odd or even. - Every other page All odd or even pages, depending on the current page being odd or even. - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. - Every other selected page - The current page will be included. @@ -2079,184 +1560,157 @@ To determine it, run batch processing at "Select Content" or "Mar PageLayoutOptionsWidget - Form - Margins - Top - - - - - - - - - - - ... - Bottom - Left - Right - - Apply To ... - Alignment - Auto auto - Manual manual - Original original - Auto Margins - Auto aligning - Enable horizontal - Enable vertical - Match size with other pages + + Guides Help + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> to create/remove guides from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Right-click</span><span style=" font-size:7pt;"> on a guide to delete that guide from the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> called.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift+LMB</span><span style=" font-size:7pt;"> - drag the guide under the cursor.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Shift/Ctrl+LMB</span><span style=" font-size:7pt;"> on the content rectangle - drag the page content. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to restrict moving along the horizontal axis only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for the vertical one. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> for usual dragging.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣ Double-click</span><span style=" font-size:7pt;"> on content - automatically attach that content to the nearest guide. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift</span><span style=" font-size:7pt;"> pressed to select vertical guides only or </span><span style=" font-size:7pt; font-weight:600;">Ctrl</span><span style=" font-size:7pt;"> for horizontal ones. Hold </span><span style=" font-size:7pt; font-weight:600;">Shift+Ctrl</span><span style=" font-size:7pt;"> to attach that to both the nearest vertical and horizontal guides.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:7pt; font-weight:600;">‣</span><span style=" font-size:7pt;"> Use the </span><span style=" font-size:7pt; font-weight:600;">context menu</span><span style=" font-size:7pt;"> to enable/disable showing the hard margins rectangle.</span></p></body></html> + + PageSplitModeDialog - Split Pages - Mode - Auto - Manual - Options - Apply cut - Apply to - This page only - All pages - This page and the following ones - This page and the following every other page - Every other page - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. - Every other selected page - The current page will be included. @@ -2264,37 +1718,30 @@ To determine it, run batch processing at "Select Content" or "Mar PageSplitOptionsWidget - Form - Page Layout - ? - Change ... - Split Line - Auto - Manual @@ -2302,22 +1749,18 @@ To determine it, run batch processing at "Select Content" or "Mar PictureZonePropDialog - Zone Properties - Subtract from all layers - Add to auto layer - Subtract from auto layer @@ -2325,41 +1768,30 @@ To determine it, run batch processing at "Select Content" or "Mar ProjectFilesDialog - Project Files - - Input Directory - - Browse - - Output Directory - Files Not In Project - - Select All - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> @@ -2367,12 +1799,10 @@ p, li { white-space: pre-wrap; } - >> - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> @@ -2380,72 +1810,54 @@ p, li { white-space: pre-wrap; } - << - Files In Project - Right to left layout (for Hebrew and Arabic) - Fix DPIs, even if they look OK - - - - - - Error - No files in project! - Input directory is not set or doesn't exist. - Input and output directories can't be the same. - Create Directory? - Output directory doesn't exist. Create it? - Unable to create output directory. - Output directory is not set or doesn't exist. - Some of the files failed to load. Either we don't support their format, or they are broken. You should remove them from the project. @@ -2455,18 +1867,14 @@ You should remove them from the project. ProjectOpeningContext - - Error - The project file is not compatible with the current application version. - Unable to interpret the project file. @@ -2474,22 +1882,18 @@ You should remove them from the project. QObject - px - mm - cm - in @@ -2497,32 +1901,26 @@ You should remove them from the project. RelinkingDialog - Relinking - Undo - ... - Substitution File for %1 - Substitution Directory for %1 - This change would merge several files into one. @@ -2530,17 +1928,14 @@ You should remove them from the project. RemovePagesDialog - Remove Pages - Remove %1 page(s) from project? - Corresponding output files will be deleted, while input files will remain. @@ -2548,27 +1943,22 @@ You should remove them from the project. SauvolaBinarizationOptionsWidget - Form - Coef: - The dimensions of a pixel neighborhood to consider. - Window size: - Default value is 0.34. @@ -2576,74 +1966,60 @@ You should remove them from the project. SelectContentApplyDialog - Select Content - Options - Apply content box - Apply page box - Apply to - This page only (already applied) - All pages - This page and the following ones - This page and the following every other page All odd or even pages, depending on the current page being odd or even. - Every other page All odd or even pages, depending on the current page being odd or even. - Selected pages - Use Ctrl+Click / Shift+Click to select multiple pages. - Every other selected page - The current page will be included. @@ -2651,65 +2027,50 @@ You should remove them from the project. SelectContentOptionsWidget - Form - Page Box - Options - Shift with corners while they are in black. - Fine Tune Page Corners - - Disable - - Auto - Content Box - - Manual - Width - Height - Apply to ... @@ -2717,163 +2078,173 @@ You should remove them from the project. SettingsDialog - Settings - - General - Accelerate user interface with OpenGL - Device: %1 - Auto-save the existing project - Processing - Deviation - Highlight the thumbnails of pages with high deviation - Params - Deksew: - Select content: - - - Deviation multiplier: a higher value means lower sensivity. - Margins: - - - The minimum deviation to be highlighted. - Color Scheme: - Language: - Saving - B&W Compression: - Color Compression: - Your hardware / driver don't provide the necessary features - Dark - Light - - None - - LZW - - Deflate - CCITT G4 - JPEG - Information - ScanTailor need to be restarted to apply the color scheme changes. + + User Interface + + + + Thumbnails + + + + Quality: + + + + The pixel size of the thumbnail image. The default value is 200. + + + + Size: + + + + The thumbnail size in the view. The default value is 250. + + + + White on black detection + + + + Auto detect pages with light content on dark background. The corrections to all the auto algorithms are made for such pages. + + + + Auto detect light content on dark background + + + + Whether to use auto detection at the output stage. The wrong result can be changed manually in the output filter options. + + + + Use auto detection at the output stage + + + + Native + + StageListView - Launch batch processing @@ -2881,62 +2252,50 @@ You should remove them from the project. StatusBarPanel - Form - Mouse position. - Mouse position relative to page. - Physical size. - Physical size of image. - Page number. - Position of the selected page in current order. - Page information. - Page name and type. - p. %1 / %2 - [L] - [R] @@ -2944,18 +2303,14 @@ You should remove them from the project. SystemLoadWidget - Form - System load - - ... @@ -2963,7 +2318,6 @@ You should remove them from the project. ThumbnailSequence - %1 (page %2) @@ -2971,47 +2325,38 @@ You should remove them from the project. WolfBinarizationOptionsWidget - Form - The dimensions of a pixel neighborhood to consider. - Window size: - The minimum possible gray level that can be made white. - Upper Bound: - Lower bound: - The maximum possible gray level that can be made black. - Coeff: - Default value is 0.3. @@ -3019,12 +2364,10 @@ You should remove them from the project. ZoneContextMenuInteraction - Delete - Properties @@ -3032,45 +2375,37 @@ You should remove them from the project. ZoneCreationInteraction - Click to finish this rectangular zone. ESC to cancel. - Click to finish this zone. ESC to cancel. - Connect first and last points to finish this zone. ESC to cancel. - - Hold Ctrl to create a rectangular zone, Alt+LMB to switch to lasso mode. ESC to cancel. + Hold Ctrl to create a rectangular zone or Shift+Alt+LMB to use lasso mode. ESC to cancel. ZoneDefaultInteraction - Drag the vertex. Hold Ctrl to make the vertex angle right. - Click to create a new vertex here. - Right click to edit zone properties. Hold Shift to drag the zone or Shift+Ctrl to copy. Press Del to delete this zone. - Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone. @@ -3078,7 +2413,6 @@ You should remove them from the project. ZoneDragInteraction - Release left mouse button to finish dragging. @@ -3086,12 +2420,10 @@ You should remove them from the project. ZoneVertexDragInteraction - Merge these two vertices. - Move the vertex to one of its neighbors to merge them. @@ -3099,17 +2431,14 @@ You should remove them from the project. deskew::Filter - Natural order - Order by decreasing deviation - Deskew @@ -3117,12 +2446,10 @@ You should remove them from the project. deskew::ImageView - Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. - Drag this handle to rotate the image. @@ -3130,7 +2457,6 @@ You should remove them from the project. deskew::OptionsWidget - Apply Deskew @@ -3138,7 +2464,6 @@ You should remove them from the project. fix_orientation::Filter - Fix Orientation @@ -3146,29 +2471,22 @@ You should remove them from the project. output::ChangeDpiDialog - Custom - - - Error - DPI is not set. - DPI is too low! - DPI is too high! @@ -3176,7 +2494,6 @@ You should remove them from the project. output::FillZoneEditor - Pick color @@ -3184,109 +2501,100 @@ You should remove them from the project. output::Filter - Output + + Natural order + + + + Order by completeness + + output::OptionsWidget - Black and White - Color / Grayscale - Mixed - Otsu - Sauvola - Wolf - Background - White - Free - Rectangular - Apply Splitting Settings - Apply Despeckling Level - Apply Depth Perception - - Off - Auto - Manual - Marginal - deskew disabled + + Apply Processing Settings + + output::TabbedImageView - Use Ctrl+1..5 to switch the tabs. @@ -3294,37 +2602,30 @@ You should remove them from the project. output::Task::UiUpdater - Picture zones are only available in Mixed mode. - Despeckling can't be done in Color / Grayscale mode. - Output - Picture Zones - Fill Zones - Dewarping - Despeckling @@ -3332,27 +2633,22 @@ You should remove them from the project. page_layout::Filter - Natural order - Order by increasing width - Order by increasing height - Order by decreasing deviation - Margins @@ -3360,20 +2656,49 @@ You should remove them from the project. page_layout::ImageView - Resize margins by dragging any of the solid lines. + + Hold left mouse button to drag the page content. + + + + Release left mouse button to finish dragging. + + + + Add a horizontal guide + + + + Add a vertical guide + + + + Remove all the guides + + + + Remove this guide + + + + Show hard margins rectangle + + + + Drag the guide. + + page_layout::OptionsWidget - Apply Margins - Apply Alignment @@ -3381,17 +2706,14 @@ You should remove them from the project. page_split::Filter - Natural order - Order by split type - Split Pages @@ -3399,7 +2721,6 @@ You should remove them from the project. page_split::ImageView - Drag the line or the handles. @@ -3407,15 +2728,10 @@ You should remove them from the project. page_split::OptionsWidget - - - Set manually - - Auto detected @@ -3423,7 +2739,6 @@ You should remove them from the project. page_split::UnremoveButton - Restore removed page. @@ -3431,27 +2746,22 @@ You should remove them from the project. select_content::Filter - Natural order - Order by increasing width - Order by increasing height - Order by decreasing deviation - Select Content @@ -3459,43 +2769,34 @@ You should remove them from the project. select_content::ImageView - Use the context menu to enable / disable the content box. Hold Shift to drag a box. Use double-click on content to automatically adjust the content area. - Drag lines or corners to resize the content box. - Drag lines or corners to resize the page box. - Hold left mouse button to drag the content box. - - Release left mouse button to finish dragging. - Hold left mouse button to drag the page box. - Create Content Box - Remove Content Box diff --git a/ui/AboutDialog.ui b/ui/AboutDialog.ui index 95b8b382f..01aab7927 100644 --- a/ui/AboutDialog.ui +++ b/ui/AboutDialog.ui @@ -122,7 +122,7 @@ font-weight:bold; font-weight: bold; - Lead Developer + Lead Developer of original version @@ -133,6 +133,23 @@ font-weight:bold; + + + + font-weight: bold; + + + Lead Developer of Advanced version + + + + + + + 4lex4 + + + @@ -200,13 +217,6 @@ font-weight:bold; - - - - 4lex4 - ver. Advanced - - - diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index bf3af775a..e1f6e9685 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1 +1 @@ -SOURCE_GROUP("UI Files" FILES ${ui_files}) +source_group("UI Files" FILES ${ui_files}) diff --git a/ui/DefaultParamsDialog.ui b/ui/DefaultParamsDialog.ui index 500a0316b..64adfc4ff 100644 --- a/ui/DefaultParamsDialog.ui +++ b/ui/DefaultParamsDialog.ui @@ -202,6 +202,9 @@ Rotate + + Qt::AlignCenter + @@ -441,6 +444,9 @@ Page Layout + + Qt::AlignCenter + @@ -678,6 +684,9 @@ Deskew + + Qt::AlignCenter + @@ -834,6 +843,9 @@ Page Box + + Qt::AlignCenter + @@ -1010,6 +1022,9 @@ Content Box + + Qt::AlignCenter + @@ -1099,8 +1114,8 @@ 0 0 - 637 - 442 + 410 + 319 @@ -1124,6 +1139,9 @@ Margins + + Qt::AlignCenter + @@ -1388,6 +1406,9 @@ Alignment + + Qt::AlignCenter + @@ -1868,8 +1889,8 @@ 0 0 - 646 - 477 + 620 + 496 @@ -1900,6 +1921,9 @@ Options + + Qt::AlignCenter + 0 @@ -1944,9 +1968,16 @@ 5 - + - Cut margins + Fill offcut + + + + + + + Fill margins @@ -2008,7 +2039,7 @@ Filling - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter false @@ -2065,6 +2096,9 @@ Color operations + + Qt::AlignCenter + 0 @@ -2577,7 +2611,7 @@ Threshold - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter false @@ -2667,11 +2701,6 @@ - - - 8 - - QFrame::NoFrame @@ -3045,6 +3074,9 @@ Picture Shape + + Qt::AlignCenter + @@ -3420,139 +3452,94 @@ false + + 6 + + + 15 + + + 9 + + + 15 + - + - 6 + 4 + + + 0 + + + 0 + + + 0 - - - Qt::Horizontal - - - - 1 - 1 - - - - - - - - No despeckling - - - - - - - 32 - 32 - - - - true - - - true - - - true - - - - - - - Cautious despeckling - - - ... - - - - :/icons/despeckle-cautious.png.png:/icons/despeckle-cautious.png.png - - - - 32 - 32 - - - - true - - - true - - - - - - - Normal despeckling - - - ... - - - - :/icons/despeckle-normal.png.png:/icons/despeckle-normal.png.png - - - - 32 - 32 - - - - true - - - true - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Despeckle + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - Aggressive despeckling + + + 10 - - ... - - - - :/icons/despeckle-aggressive.png.png:/icons/despeckle-aggressive.png.png + + 30 - - - 32 - 32 - + + 2 - - true + + 5 - - true + + 10 - - - - Qt::Horizontal - - - 1 - 1 - - - + @@ -3656,6 +3643,12 @@ false + + 15 + + + 15 + @@ -3678,6 +3671,19 @@ + + + + Qt::Vertical + + + + 20 + 1 + + + + diff --git a/ui/MainWindow.ui b/ui/MainWindow.ui index 3fb696461..0c4147e7f 100644 --- a/ui/MainWindow.ui +++ b/ui/MainWindow.ui @@ -174,7 +174,7 @@ - Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. + Use Home, End, PgUp/Q, PgDown/W to navigate between pages or Shift+PgUp/Q and Shift+PgDown/W to navigate between selected ones. Alt+Wheel - scale thumbnails. Qt::AlignHCenter|Qt::AlignTop @@ -203,7 +203,7 @@ 274 - 134 + 204 @@ -220,7 +220,7 @@ - + 0 0 @@ -276,11 +276,49 @@ - + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 256 + 185 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + - filterOptions - filterList @@ -293,26 +331,32 @@ Debug Mode + + + Save Project - Ctrl+S + Ctrl+S Save Project As ... + + + Next Page - PgDown + PgDown false @@ -323,7 +367,7 @@ Previous Page - PgUp + PgUp false @@ -334,7 +378,7 @@ New Project ... - Ctrl+N + Ctrl+N @@ -342,7 +386,7 @@ Open Project ... - Ctrl+O + Ctrl+O @@ -350,7 +394,7 @@ Previous Page - Q + Q false @@ -361,7 +405,7 @@ Next Page - W + W false @@ -372,7 +416,7 @@ Close Project - Ctrl+W + Ctrl+W @@ -380,20 +424,23 @@ Quit - Ctrl+Q + Ctrl+Q Settings ... + + + First Page - Home + Home @@ -401,30 +448,39 @@ Last Page - End + End About + + + Fix DPI ... + + + Relinking ... + + + Switch filter to orientation - O + O @@ -435,7 +491,7 @@ Switch filter to split pages - E + E @@ -446,7 +502,7 @@ Switch filter to deskew - N + N @@ -457,7 +513,7 @@ Switch filter to select content - V + V @@ -468,7 +524,7 @@ Switch filter to margins - K + K @@ -479,7 +535,7 @@ Switch filter to output - P + P @@ -489,6 +545,12 @@ Pixels + + + + + false + @@ -500,6 +562,12 @@ Millimetres + + + + + false + @@ -508,6 +576,12 @@ Inches + + + + + false + @@ -516,11 +590,76 @@ Centimetres + + + + + false + Default parameters ... + + + + + + + Previous Selected Page + + + Previous Selected Page + + + Shift+Q + + + false + + + + + Next Selected Page + + + Next Selected Page + + + Shift+W + + + false + + + + + Next Selected Page + + + Next Selected Page + + + Shift+PgDown + + + false + + + + + Previous Selected Page + + + Previous Selected Page + + + Shift+PgUp + + + false + diff --git a/ui/SettingsDialog.ui b/ui/SettingsDialog.ui index 15307abd1..607c887f1 100644 --- a/ui/SettingsDialog.ui +++ b/ui/SettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 337 - 333 + 394 @@ -30,7 +30,7 @@ - General + User Interface @@ -114,6 +114,98 @@ openglDeviceLabel + + + + Thumbnails + + + + + + + + Quality: + + + + + + + The pixel size of the thumbnail image. The default value is 200. + + + 100 + + + 1000 + + + 25 + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 18 + 1 + + + + + + + + + + Size: + + + + + + + The thumbnail size in the view. The default value is 250. + + + 100 + + + 1000 + + + 25 + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + @@ -180,6 +272,35 @@ Processing + + + + White on black detection + + + + + + Auto detect pages with light content on dark background. The corrections to all the auto algorithms are made for such pages. + + + Auto detect light content on dark background + + + + + + + Whether to use auto detection at the output stage. The wrong result can be changed manually in the output filter options. + + + Use auto detection at the output stage + + + + + + diff --git a/version.h b/version.h index e3243c85a..25b169820 100644 --- a/version.h +++ b/version.h @@ -19,8 +19,8 @@ #ifndef SCANTAILOR_VERSION_H_ #define SCANTAILOR_VERSION_H_ -#define VERSION "1.0.14" +#define VERSION "1.0.15" #define VERSION_QUAD "" // Must be "x.x.x.x" or an empty string. -#define PROJECT_VERSION 2 +#define PROJECT_VERSION 3 #endif diff --git a/zones/BasicSplineVisualizer.cpp b/zones/BasicSplineVisualizer.cpp index 65cd927a6..470714f24 100644 --- a/zones/BasicSplineVisualizer.cpp +++ b/zones/BasicSplineVisualizer.cpp @@ -17,41 +17,38 @@ */ #include "BasicSplineVisualizer.h" -#include "EditableZoneSet.h" #include +#include "EditableZoneSet.h" BasicSplineVisualizer::BasicSplineVisualizer() - : m_solidColor(0xcc1420), - m_highlightBrightColor(0xfffe00), - m_highlightDarkColor(0xffa90e), - m_pen(m_solidColor) { - m_pen.setCosmetic(true); - m_pen.setWidthF(1.5); + : m_solidColor(0xcc1420), m_highlightBrightColor(0xfffe00), m_highlightDarkColor(0xffa90e), m_pen(m_solidColor) { + m_pen.setCosmetic(true); + m_pen.setWidthF(1.5); } void BasicSplineVisualizer::drawSplines(QPainter& painter, const QTransform& to_screen, const EditableZoneSet& zones) { - for (const EditableZoneSet::Zone& zone : zones) { - drawSpline(painter, to_screen, zone.spline()); - } + for (const EditableZoneSet::Zone& zone : zones) { + drawSpline(painter, to_screen, zone.spline()); + } } void BasicSplineVisualizer::drawSpline(QPainter& painter, const QTransform& to_screen, const EditableSpline::Ptr& spline) { - prepareForSpline(painter, spline); - painter.drawPolygon(to_screen.map(spline->toPolygon()), Qt::WindingFill); + prepareForSpline(painter, spline); + painter.drawPolygon(to_screen.map(spline->toPolygon()), Qt::WindingFill); } void BasicSplineVisualizer::drawVertex(QPainter& painter, const QPointF& pt, const QColor& color) { - painter.setPen(Qt::NoPen); - painter.setBrush(color); + painter.setPen(Qt::NoPen); + painter.setBrush(color); - QRectF rect(0, 0, 4, 4); - rect.moveCenter(pt); - painter.drawEllipse(rect); + QRectF rect(0, 0, 4, 4); + rect.moveCenter(pt); + painter.drawEllipse(rect); } void BasicSplineVisualizer::prepareForSpline(QPainter& painter, const EditableSpline::Ptr&) { - painter.setPen(m_pen); - painter.setBrush(Qt::NoBrush); + painter.setPen(m_pen); + painter.setBrush(Qt::NoBrush); } diff --git a/zones/BasicSplineVisualizer.h b/zones/BasicSplineVisualizer.h index 1d255b033..8fa1e3cc3 100644 --- a/zones/BasicSplineVisualizer.h +++ b/zones/BasicSplineVisualizer.h @@ -19,43 +19,37 @@ #ifndef BASIC_SPLINE_VISUALIZER_H_ #define BASIC_SPLINE_VISUALIZER_H_ -#include "EditableSpline.h" -#include #include +#include +#include "EditableSpline.h" class EditableZoneSet; class QPainter; class QTransform; class BasicSplineVisualizer { -public: - BasicSplineVisualizer(); + public: + BasicSplineVisualizer(); - QRgb solidColor() const { - return m_solidColor; - } + QRgb solidColor() const { return m_solidColor; } - QRgb highlightBrightColor() const { - return m_highlightBrightColor; - } + QRgb highlightBrightColor() const { return m_highlightBrightColor; } - QRgb highlightDarkColor() const { - return m_highlightDarkColor; - } + QRgb highlightDarkColor() const { return m_highlightDarkColor; } - void drawVertex(QPainter& painter, const QPointF& pt, const QColor& color); + void drawVertex(QPainter& painter, const QPointF& pt, const QColor& color); - void drawSplines(QPainter& painter, const QTransform& to_screen, const EditableZoneSet& zones); + void drawSplines(QPainter& painter, const QTransform& to_screen, const EditableZoneSet& zones); - virtual void drawSpline(QPainter& painter, const QTransform& to_screen, const EditableSpline::Ptr& spline); + virtual void drawSpline(QPainter& painter, const QTransform& to_screen, const EditableSpline::Ptr& spline); - virtual void prepareForSpline(QPainter& painter, const EditableSpline::Ptr& spline); + virtual void prepareForSpline(QPainter& painter, const EditableSpline::Ptr& spline); -protected: - QRgb m_solidColor; - QRgb m_highlightBrightColor; - QRgb m_highlightDarkColor; - QPen m_pen; + protected: + QRgb m_solidColor; + QRgb m_highlightBrightColor; + QRgb m_highlightDarkColor; + QPen m_pen; }; diff --git a/zones/CMakeLists.txt b/zones/CMakeLists.txt index bf78cb9ee..017d5fe23 100644 --- a/zones/CMakeLists.txt +++ b/zones/CMakeLists.txt @@ -1,29 +1,29 @@ -PROJECT(zones) +project(zones) -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_BINARY_DIR}") -SET( - sources - SplineVertex.cpp SplineVertex.h - SplineSegment.cpp SplineSegment.h - EditableSpline.cpp EditableSpline.h - SerializableSpline.cpp SerializableSpline.h - Zone.cpp Zone.h - ZoneSet.cpp ZoneSet.h - EditableZoneSet.cpp EditableZoneSet.h - BasicSplineVisualizer.cpp BasicSplineVisualizer.h - ZoneInteractionContext.cpp ZoneInteractionContext.h - ZoneDefaultInteraction.cpp ZoneDefaultInteraction.h - ZoneCreationInteraction.cpp ZoneCreationInteraction.h - ZoneVertexDragInteraction.cpp ZoneVertexDragInteraction.h - ZoneContextMenuInteraction.cpp ZoneContextMenuInteraction.h - ZoneContextMenuItem.h - ZoneDragInteraction.cpp ZoneDragInteraction.h) +set( + sources + SplineVertex.cpp SplineVertex.h + SplineSegment.cpp SplineSegment.h + EditableSpline.cpp EditableSpline.h + SerializableSpline.cpp SerializableSpline.h + Zone.cpp Zone.h + ZoneSet.cpp ZoneSet.h + EditableZoneSet.cpp EditableZoneSet.h + BasicSplineVisualizer.cpp BasicSplineVisualizer.h + ZoneInteractionContext.cpp ZoneInteractionContext.h + ZoneDefaultInteraction.cpp ZoneDefaultInteraction.h + ZoneCreationInteraction.cpp ZoneCreationInteraction.h + ZoneVertexDragInteraction.cpp ZoneVertexDragInteraction.h + ZoneContextMenuInteraction.cpp ZoneContextMenuInteraction.h + ZoneContextMenuItem.h + ZoneDragInteraction.cpp ZoneDragInteraction.h) -SOURCE_GROUP(Sources FILES ${sources}) +source_group(Sources FILES ${sources}) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -ADD_LIBRARY(zones STATIC ${sources}) +add_library(zones STATIC ${sources}) -TRANSLATION_SOURCES(scantailor ${sources}) \ No newline at end of file +translation_sources(scantailor ${sources}) \ No newline at end of file diff --git a/zones/EditableSpline.cpp b/zones/EditableSpline.cpp index 9e5ff123c..0b9c3c930 100644 --- a/zones/EditableSpline.cpp +++ b/zones/EditableSpline.cpp @@ -22,60 +22,60 @@ EditableSpline::EditableSpline() = default; EditableSpline::EditableSpline(const SerializableSpline& spline) { - for (const QPointF& pt : spline.toPolygon()) { - appendVertex(pt); - } + for (const QPointF& pt : spline.toPolygon()) { + appendVertex(pt); + } - SplineVertex::Ptr last_vertex(lastVertex()); - if (last_vertex && (firstVertex()->point() == last_vertex->point())) { - last_vertex->remove(); - } + SplineVertex::Ptr last_vertex(lastVertex()); + if (last_vertex && (firstVertex()->point() == last_vertex->point())) { + last_vertex->remove(); + } - setBridged(true); + setBridged(true); } void EditableSpline::appendVertex(const QPointF& pt) { - m_sentinel.insertBefore(pt); + m_sentinel.insertBefore(pt); } bool EditableSpline::hasAtLeastSegments(int num) const { - for (SegmentIterator it((EditableSpline&) *this); num > 0 && it.hasNext(); it.next()) { - --num; - } + for (SegmentIterator it((EditableSpline&) *this); num > 0 && it.hasNext(); it.next()) { + --num; + } - return num == 0; + return num == 0; } QPolygonF EditableSpline::toPolygon() const { - QPolygonF poly; + QPolygonF poly; - SplineVertex::Ptr vertex(firstVertex()); - for (; vertex; vertex = vertex->next(SplineVertex::NO_LOOP)) { - poly.push_back(vertex->point()); - } + SplineVertex::Ptr vertex(firstVertex()); + for (; vertex; vertex = vertex->next(SplineVertex::NO_LOOP)) { + poly.push_back(vertex->point()); + } - vertex = lastVertex()->next(SplineVertex::LOOP_IF_BRIDGED); - if (vertex) { - poly.push_back(vertex->point()); - } + vertex = lastVertex()->next(SplineVertex::LOOP_IF_BRIDGED); + if (vertex) { + poly.push_back(vertex->point()); + } - return poly; + return poly; } /*======================== Spline::SegmentIterator =======================*/ bool EditableSpline::SegmentIterator::hasNext() const { - return m_ptrNextVertex && m_ptrNextVertex->next(SplineVertex::LOOP_IF_BRIDGED); + return m_nextVertex && m_nextVertex->next(SplineVertex::LOOP_IF_BRIDGED); } SplineSegment EditableSpline::SegmentIterator::next() { - assert(hasNext()); - - SplineVertex::Ptr origin(m_ptrNextVertex); - m_ptrNextVertex = m_ptrNextVertex->next(SplineVertex::NO_LOOP); - if (!m_ptrNextVertex) { - return SplineSegment(origin, origin->next(SplineVertex::LOOP_IF_BRIDGED)); - } else { - return SplineSegment(origin, m_ptrNextVertex); - } + assert(hasNext()); + + SplineVertex::Ptr origin(m_nextVertex); + m_nextVertex = m_nextVertex->next(SplineVertex::NO_LOOP); + if (!m_nextVertex) { + return SplineSegment(origin, origin->next(SplineVertex::LOOP_IF_BRIDGED)); + } else { + return SplineSegment(origin, m_nextVertex); + } } diff --git a/zones/EditableSpline.h b/zones/EditableSpline.h index 31edd01dd..560cc6d71 100644 --- a/zones/EditableSpline.h +++ b/zones/EditableSpline.h @@ -20,60 +20,51 @@ #ifndef EDITABLE_SPLINE_H_ #define EDITABLE_SPLINE_H_ -#include "ref_countable.h" -#include "intrusive_ptr.h" -#include "SplineVertex.h" -#include "SplineSegment.h" #include +#include "SplineSegment.h" +#include "SplineVertex.h" +#include "intrusive_ptr.h" +#include "ref_countable.h" class SerializableSpline; class EditableSpline : public ref_countable { -public: - typedef intrusive_ptr Ptr; + public: + typedef intrusive_ptr Ptr; - class SegmentIterator { - public: - explicit SegmentIterator(EditableSpline& spline) : m_ptrNextVertex(spline.firstVertex()) { - } + class SegmentIterator { + public: + explicit SegmentIterator(EditableSpline& spline) : m_nextVertex(spline.firstVertex()) {} - bool hasNext() const; + bool hasNext() const; - SplineSegment next(); + SplineSegment next(); - private: - SplineVertex::Ptr m_ptrNextVertex; - }; + private: + SplineVertex::Ptr m_nextVertex; + }; - EditableSpline(); + EditableSpline(); - EditableSpline(const SerializableSpline& spline); + EditableSpline(const SerializableSpline& spline); - void appendVertex(const QPointF& pt); + void appendVertex(const QPointF& pt); - SplineVertex::Ptr firstVertex() const { - return m_sentinel.firstVertex(); - } + SplineVertex::Ptr firstVertex() const { return m_sentinel.firstVertex(); } - SplineVertex::Ptr lastVertex() const { - return m_sentinel.lastVertex(); - } + SplineVertex::Ptr lastVertex() const { return m_sentinel.lastVertex(); } - bool hasAtLeastSegments(int num) const; + bool hasAtLeastSegments(int num) const; - bool bridged() const { - return m_sentinel.bridged(); - } + bool bridged() const { return m_sentinel.bridged(); } - void setBridged(bool bridged) { - m_sentinel.setBridged(true); - } + void setBridged(bool bridged) { m_sentinel.setBridged(true); } - QPolygonF toPolygon() const; + QPolygonF toPolygon() const; -private: - SentinelSplineVertex m_sentinel; + private: + SentinelSplineVertex m_sentinel; }; diff --git a/zones/EditableZoneSet.cpp b/zones/EditableZoneSet.cpp index e79552493..4136495ed 100644 --- a/zones/EditableZoneSet.cpp +++ b/zones/EditableZoneSet.cpp @@ -22,44 +22,44 @@ EditableZoneSet::EditableZoneSet() = default; void EditableZoneSet::setDefaultProperties(const PropertySet& props) { - m_defaultProps = props; + m_defaultProps = props; } void EditableZoneSet::addZone(const EditableSpline::Ptr& spline) { - auto new_props = make_intrusive(m_defaultProps); - m_splineMap.insert(Map::value_type(spline, new_props)); - m_splineList.push_back(spline); + auto new_props = make_intrusive(m_defaultProps); + m_splineMap.insert(Map::value_type(spline, new_props)); + m_splineList.push_back(spline); } void EditableZoneSet::addZone(const EditableSpline::Ptr& spline, const PropertySet& props) { - auto new_props = make_intrusive(props); - m_splineMap.insert(Map::value_type(spline, new_props)); - m_splineList.push_back(spline); + auto new_props = make_intrusive(props); + m_splineMap.insert(Map::value_type(spline, new_props)); + m_splineList.push_back(spline); } void EditableZoneSet::removeZone(const EditableSpline::Ptr& spline) { - m_splineMap.erase(spline); - m_splineList.remove(spline); + m_splineMap.erase(spline); + m_splineList.remove(spline); } void EditableZoneSet::commit() { - emit committed(); + emit committed(); } intrusive_ptr EditableZoneSet::propertiesFor(const EditableSpline::Ptr& spline) { - auto it(m_splineMap.find(spline)); - if (it != m_splineMap.end()) { - return it->second; - } else { - return nullptr; - } + auto it(m_splineMap.find(spline)); + if (it != m_splineMap.end()) { + return it->second; + } else { + return nullptr; + } } intrusive_ptr EditableZoneSet::propertiesFor(const EditableSpline::Ptr& spline) const { - auto it(m_splineMap.find(spline)); - if (it != m_splineMap.end()) { - return it->second; - } else { - return nullptr; - } + auto it(m_splineMap.find(spline)); + if (it != m_splineMap.end()) { + return it->second; + } else { + return nullptr; + } } diff --git a/zones/EditableZoneSet.h b/zones/EditableZoneSet.h index 1aeb9c100..d81e23189 100644 --- a/zones/EditableZoneSet.h +++ b/zones/EditableZoneSet.h @@ -20,128 +20,110 @@ #ifndef EDITABLE_ZONE_SET_H_ #define EDITABLE_ZONE_SET_H_ -#include "EditableSpline.h" -#include "PropertySet.h" -#include "intrusive_ptr.h" #include -#include #include #include +#include #include +#include "EditableSpline.h" +#include "PropertySet.h" +#include "intrusive_ptr.h" class EditableZoneSet : public QObject { - Q_OBJECT -private: - typedef std::unordered_map, EditableSpline::Ptr::hash> Map; + Q_OBJECT + private: + typedef std::unordered_map, EditableSpline::Ptr::hash> Map; -public: - class const_iterator; + public: + class const_iterator; - class Zone { - friend class EditableZoneSet::const_iterator; + class Zone { + friend class EditableZoneSet::const_iterator; - public: - Zone() = default; + public: + Zone() = default; - const EditableSpline::Ptr& spline() const { - return m_iter->first; - } + const EditableSpline::Ptr& spline() const { return m_iter->first; } - const intrusive_ptr& properties() const { - return m_iter->second; - } + const intrusive_ptr& properties() const { return m_iter->second; } - private: - explicit Zone(Map::const_iterator it) : m_iter(it) { - } + private: + explicit Zone(Map::const_iterator it) : m_iter(it) {} - Map::const_iterator m_iter; - }; + Map::const_iterator m_iter; + }; - class const_iterator - : public boost::iterator_facade { - friend class EditableZoneSet; + class const_iterator : public boost::iterator_facade { + friend class EditableZoneSet; - friend class boost::iterator_core_access; + friend class boost::iterator_core_access; - public: - const_iterator() : m_zone() { - } + public: + const_iterator() : m_zone() {} - void increment() { - ++m_iter; - m_zone.m_iter = m_ptrSplineMap->find(*m_iter); - } + void increment() { + ++m_iter; + m_zone.m_iter = m_splineMap->find(*m_iter); + } - void decrement() { - --m_iter; - m_zone.m_iter = m_ptrSplineMap->find(*m_iter); - } + void decrement() { + --m_iter; + m_zone.m_iter = m_splineMap->find(*m_iter); + } - bool equal(const const_iterator& other) const { - return m_iter == other.m_iter; - } + bool equal(const const_iterator& other) const { return m_iter == other.m_iter; } - const Zone& dereference() const { - return m_zone; - } + const Zone& dereference() const { return m_zone; } - private: - explicit const_iterator(std::list::const_iterator it, const Map* splineMap) - : m_iter(it), m_ptrSplineMap(splineMap), m_zone(splineMap->find(*it)) { - } + private: + explicit const_iterator(std::list::const_iterator it, const Map* splineMap) + : m_iter(it), m_splineMap(splineMap), m_zone(splineMap->find(*it)) {} - Zone m_zone; - std::list::const_iterator m_iter; - const Map* m_ptrSplineMap{}; - }; + Zone m_zone; + std::list::const_iterator m_iter; + const Map* m_splineMap{}; + }; - typedef const_iterator iterator; + typedef const_iterator iterator; - EditableZoneSet(); + EditableZoneSet(); - const_iterator begin() const { - return iterator(m_splineList.begin(), const_cast(&m_splineMap)); - } + const_iterator begin() const { return iterator(m_splineList.begin(), const_cast(&m_splineMap)); } - const_iterator end() const { - return iterator(m_splineList.end(), const_cast(&m_splineMap)); - } + const_iterator end() const { return iterator(m_splineList.end(), const_cast(&m_splineMap)); } - const PropertySet& defaultProperties() const { - return m_defaultProps; - } + const PropertySet& defaultProperties() const { return m_defaultProps; } - void setDefaultProperties(const PropertySet& props); + void setDefaultProperties(const PropertySet& props); - void addZone(const EditableSpline::Ptr& spline); + void addZone(const EditableSpline::Ptr& spline); - void addZone(const EditableSpline::Ptr& spline, const PropertySet& props); + void addZone(const EditableSpline::Ptr& spline, const PropertySet& props); - void removeZone(const EditableSpline::Ptr& spline); + void removeZone(const EditableSpline::Ptr& spline); - void commit(); + void commit(); - intrusive_ptr propertiesFor(const EditableSpline::Ptr& spline); + intrusive_ptr propertiesFor(const EditableSpline::Ptr& spline); - intrusive_ptr propertiesFor(const EditableSpline::Ptr& spline) const; + intrusive_ptr propertiesFor(const EditableSpline::Ptr& spline) const; -signals: + signals: - void committed(); + void committed(); -private: - Map m_splineMap; - std::list m_splineList; - PropertySet m_defaultProps; + private: + Map m_splineMap; + std::list m_splineList; + PropertySet m_defaultProps; }; namespace boost { namespace foreach { // Make BOOST_FOREACH work with the above class (necessary for boost >= 1.46 with gcc >= 4.6) -template<> +template <> struct is_noncopyable : public boost::mpl::true_ {}; } // namespace foreach } // namespace boost diff --git a/zones/SerializableSpline.cpp b/zones/SerializableSpline.cpp index a505658f0..bf50581b9 100644 --- a/zones/SerializableSpline.cpp +++ b/zones/SerializableSpline.cpp @@ -17,69 +17,69 @@ */ #include "SerializableSpline.h" +#include +#include #include "EditableSpline.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" -#include -#include SerializableSpline::SerializableSpline(const EditableSpline& spline) { - SplineVertex::Ptr vertex(spline.firstVertex()); - for (; vertex; vertex = vertex->next(SplineVertex::NO_LOOP)) { - m_points.push_back(vertex->point()); - } + SplineVertex::Ptr vertex(spline.firstVertex()); + for (; vertex; vertex = vertex->next(SplineVertex::NO_LOOP)) { + m_points.push_back(vertex->point()); + } } SerializableSpline::SerializableSpline(const QDomElement& el) { - const QString point_str("point"); - - QDomNode node(el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != point_str) { - continue; - } - - m_points.push_back(XmlUnmarshaller::pointF(node.toElement())); + const QString point_str("point"); + + QDomNode node(el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; } + if (node.nodeName() != point_str) { + continue; + } + + m_points.push_back(XmlUnmarshaller::pointF(node.toElement())); + } } SerializableSpline::SerializableSpline(const QPolygonF& polygon) { - for (int i = polygon.size() - 1; i >= 0; i--) { - m_points.push_back(polygon[i]); - } + for (int i = polygon.size() - 1; i >= 0; i--) { + m_points.push_back(polygon[i]); + } } QDomElement SerializableSpline::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); + QDomElement el(doc.createElement(name)); - const QString point_str("point"); - XmlMarshaller marshaller(doc); - for (const QPointF& pt : m_points) { - el.appendChild(marshaller.pointF(pt, point_str)); - } + const QString point_str("point"); + XmlMarshaller marshaller(doc); + for (const QPointF& pt : m_points) { + el.appendChild(marshaller.pointF(pt, point_str)); + } - return el; + return el; } SerializableSpline SerializableSpline::transformed(const QTransform& xform) const { - SerializableSpline transformed(*this); + SerializableSpline transformed(*this); - for (QPointF& pt : transformed.m_points) { - pt = xform.map(pt); - } + for (QPointF& pt : transformed.m_points) { + pt = xform.map(pt); + } - return transformed; + return transformed; } SerializableSpline SerializableSpline::transformed(const boost::function& xform) const { - SerializableSpline transformed(*this); + SerializableSpline transformed(*this); - for (QPointF& pt : transformed.m_points) { - pt = xform(pt); - } + for (QPointF& pt : transformed.m_points) { + pt = xform(pt); + } - return transformed; + return transformed; } diff --git a/zones/SerializableSpline.h b/zones/SerializableSpline.h index 2958cecf2..9634fc580 100644 --- a/zones/SerializableSpline.h +++ b/zones/SerializableSpline.h @@ -19,9 +19,9 @@ #ifndef SERIALIZABLE_SPLINE_H_ #define SERIALIZABLE_SPLINE_H_ -#include #include #include +#include #include class EditableSpline; @@ -31,25 +31,23 @@ class QDomElement; class QString; class SerializableSpline { -public: - SerializableSpline(const EditableSpline& spline); + public: + SerializableSpline(const EditableSpline& spline); - explicit SerializableSpline(const QDomElement& el); + explicit SerializableSpline(const QDomElement& el); - explicit SerializableSpline(const QPolygonF& polygon); + explicit SerializableSpline(const QPolygonF& polygon); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - SerializableSpline transformed(const QTransform& xform) const; + SerializableSpline transformed(const QTransform& xform) const; - SerializableSpline transformed(const boost::function& xform) const; + SerializableSpline transformed(const boost::function& xform) const; - QPolygonF toPolygon() const { - return QPolygonF(m_points); - } + QPolygonF toPolygon() const { return QPolygonF(m_points); } -private: - QVector m_points; + private: + QVector m_points; }; diff --git a/zones/SplineSegment.cpp b/zones/SplineSegment.cpp index 6e403c198..55a520651 100644 --- a/zones/SplineSegment.cpp +++ b/zones/SplineSegment.cpp @@ -19,15 +19,14 @@ #include "SplineSegment.h" #include -SplineSegment::SplineSegment(const SplineVertex::Ptr& prev, const SplineVertex::Ptr& next) : prev(prev), next(next) { -} +SplineSegment::SplineSegment(const SplineVertex::Ptr& prev, const SplineVertex::Ptr& next) : prev(prev), next(next) {} SplineVertex::Ptr SplineSegment::splitAt(const QPointF& pt) { - assert(isValid()); + assert(isValid()); - return prev->insertAfter(pt); + return prev->insertAfter(pt); } bool SplineSegment::isValid() const { - return prev && next && prev->next(SplineVertex::LOOP_IF_BRIDGED) == next; + return prev && next && prev->next(SplineVertex::LOOP_IF_BRIDGED) == next; } diff --git a/zones/SplineSegment.h b/zones/SplineSegment.h index 11a776293..2cd527c6a 100644 --- a/zones/SplineSegment.h +++ b/zones/SplineSegment.h @@ -19,30 +19,26 @@ #ifndef SPLINE_SEGMENT_H_ #define SPLINE_SEGMENT_H_ -#include "SplineVertex.h" -#include #include +#include +#include "SplineVertex.h" class SplineSegment { -public: - SplineVertex::Ptr prev; - SplineVertex::Ptr next; + public: + SplineVertex::Ptr prev; + SplineVertex::Ptr next; - SplineSegment() = default; + SplineSegment() = default; - SplineSegment(const SplineVertex::Ptr& prev, const SplineVertex::Ptr& next); + SplineSegment(const SplineVertex::Ptr& prev, const SplineVertex::Ptr& next); - SplineVertex::Ptr splitAt(const QPointF& pt); + SplineVertex::Ptr splitAt(const QPointF& pt); - bool isValid() const; + bool isValid() const; - bool operator==(const SplineSegment& other) const { - return prev == other.prev && next == other.next; - } + bool operator==(const SplineSegment& other) const { return prev == other.prev && next == other.next; } - QLineF toLine() const { - return QLineF(prev->point(), next->point()); - } + QLineF toLine() const { return QLineF(prev->point(), next->point()); } }; diff --git a/zones/SplineVertex.cpp b/zones/SplineVertex.cpp index 5059d1fb8..27537302a 100644 --- a/zones/SplineVertex.cpp +++ b/zones/SplineVertex.cpp @@ -22,148 +22,145 @@ /*============================= SplineVertex ============================*/ -SplineVertex::SplineVertex(SplineVertex* prev, SplineVertex* next) : m_pPrev(prev), m_ptrNext(next) { -} +SplineVertex::SplineVertex(SplineVertex* prev, SplineVertex* next) : m_prev(prev), m_next(next) {} void SplineVertex::remove() { - // Be very careful here - don't let this object - // be destroyed before we've finished working with it. + // Be very careful here - don't let this object + // be destroyed before we've finished working with it. - m_pPrev->m_ptrNext.swap(m_ptrNext); - assert(m_ptrNext.get() == this); + m_prev->m_next.swap(m_next); + assert(m_next.get() == this); - m_pPrev->m_ptrNext->m_pPrev = m_pPrev; - m_pPrev = nullptr; + m_prev->m_next->m_prev = m_prev; + m_prev = nullptr; - // This may or may not destroy this object, - // depending on if there are other references to it. - m_ptrNext.reset(); + // This may or may not destroy this object, + // depending on if there are other references to it. + m_next.reset(); } bool SplineVertex::hasAtLeastSiblings(const int num) { - int todo = num; - for (SplineVertex::Ptr node(this); (node = node->next(LOOP)).get() != this;) { - if (--todo == 0) { - return true; - } + int todo = num; + for (SplineVertex::Ptr node(this); (node = node->next(LOOP)).get() != this;) { + if (--todo == 0) { + return true; } + } - return false; + return false; } SplineVertex::Ptr SplineVertex::prev(const Loop loop) { - return m_pPrev->thisOrPrevReal(loop); + return m_prev->thisOrPrevReal(loop); } SplineVertex::Ptr SplineVertex::next(const Loop loop) { - return m_ptrNext->thisOrNextReal(loop); + return m_next->thisOrNextReal(loop); } SplineVertex::Ptr SplineVertex::insertBefore(const QPointF& pt) { - auto new_vertex = make_intrusive(pt, m_pPrev, this); - m_pPrev->m_ptrNext = new_vertex; - m_pPrev = new_vertex.get(); + auto new_vertex = make_intrusive(pt, m_prev, this); + m_prev->m_next = new_vertex; + m_prev = new_vertex.get(); - return new_vertex; + return new_vertex; } SplineVertex::Ptr SplineVertex::insertAfter(const QPointF& pt) { - auto new_vertex = make_intrusive(pt, this, m_ptrNext.get()); - m_ptrNext->m_pPrev = new_vertex.get(); - m_ptrNext = new_vertex; + auto new_vertex = make_intrusive(pt, this, m_next.get()); + m_next->m_prev = new_vertex.get(); + m_next = new_vertex; - return new_vertex; + return new_vertex; } /*========================= SentinelSplineVertex =======================*/ -SentinelSplineVertex::SentinelSplineVertex() : SplineVertex(this, this), m_bridged(false) { -} +SentinelSplineVertex::SentinelSplineVertex() : SplineVertex(this, this), m_bridged(false) {} SentinelSplineVertex::~SentinelSplineVertex() { - // Just releasing m_ptrNext is not enough, because in case some external - // object holds a reference to a vertix of this spline, that vertex will - // still (possibly indirectly) reference us through a chain of m_ptrNext - // smart pointers. Therefore, we explicitly unlink each node. - while (m_ptrNext.get() != this) { - m_ptrNext->remove(); - } + // Just releasing m_next is not enough, because in case some external + // object holds a reference to a vertix of this spline, that vertex will + // still (possibly indirectly) reference us through a chain of m_next + // smart pointers. Therefore, we explicitly unlink each node. + while (m_next.get() != this) { + m_next->remove(); + } } SplineVertex::Ptr SentinelSplineVertex::thisOrPrevReal(const Loop loop) { - if ((loop == LOOP) || ((loop == LOOP_IF_BRIDGED) && m_bridged)) { - return SplineVertex::Ptr(m_pPrev); - } else { - return nullptr; - } + if ((loop == LOOP) || ((loop == LOOP_IF_BRIDGED) && m_bridged)) { + return SplineVertex::Ptr(m_prev); + } else { + return nullptr; + } } SplineVertex::Ptr SentinelSplineVertex::thisOrNextReal(const Loop loop) { - if ((loop == LOOP) || ((loop == LOOP_IF_BRIDGED) && m_bridged)) { - return m_ptrNext; - } else { - return nullptr; - } + if ((loop == LOOP) || ((loop == LOOP_IF_BRIDGED) && m_bridged)) { + return m_next; + } else { + return nullptr; + } } const QPointF SentinelSplineVertex::point() const { - assert(!"Illegal call to SentinelSplineVertex::point()"); + assert(!"Illegal call to SentinelSplineVertex::point()"); - return QPointF(); + return QPointF(); } void SentinelSplineVertex::setPoint(const QPointF& pt) { - assert(!"Illegal call to SentinelSplineVertex::setPoint()"); + assert(!"Illegal call to SentinelSplineVertex::setPoint()"); } void SentinelSplineVertex::remove() { - assert(!"Illegal call to SentinelSplineVertex::remove()"); + assert(!"Illegal call to SentinelSplineVertex::remove()"); } SplineVertex::Ptr SentinelSplineVertex::firstVertex() const { - if (m_ptrNext.get() == this) { - return nullptr; - } else { - return m_ptrNext; - } + if (m_next.get() == this) { + return nullptr; + } else { + return m_next; + } } SplineVertex::Ptr SentinelSplineVertex::lastVertex() const { - if (m_pPrev == this) { - return nullptr; - } else { - return SplineVertex::Ptr(m_pPrev); - } + if (m_prev == this) { + return nullptr; + } else { + return SplineVertex::Ptr(m_prev); + } } /*============================== RealSplineVertex ============================*/ RealSplineVertex::RealSplineVertex(const QPointF& pt, SplineVertex* prev, SplineVertex* next) - : SplineVertex(prev, next), m_point(pt), m_refCounter(0) { -} + : SplineVertex(prev, next), m_point(pt), m_counter(0) {} void RealSplineVertex::ref() const { - ++m_refCounter; + ++m_counter; } void RealSplineVertex::unref() const { - if (--m_refCounter == 0) { - delete this; - } + if (--m_counter == 0) { + delete this; + } } SplineVertex::Ptr RealSplineVertex::thisOrPrevReal(Loop) { - return SplineVertex::Ptr(this); + return SplineVertex::Ptr(this); } SplineVertex::Ptr RealSplineVertex::thisOrNextReal(Loop loop) { - return SplineVertex::Ptr(this); + return SplineVertex::Ptr(this); } const QPointF RealSplineVertex::point() const { - return m_point; + return m_point; } void RealSplineVertex::setPoint(const QPointF& pt) { - m_point = pt; + m_point = pt; } diff --git a/zones/SplineVertex.h b/zones/SplineVertex.h index 5f2a62c76..e788a7102 100644 --- a/zones/SplineVertex.h +++ b/zones/SplineVertex.h @@ -19,135 +19,129 @@ #ifndef SPLINE_VERTEX_H_ #define SPLINE_VERTEX_H_ -#include "intrusive_ptr.h" -#include "NonCopyable.h" #include +#include "NonCopyable.h" +#include "intrusive_ptr.h" class SplineVertex { -public: - enum Loop { LOOP, NO_LOOP, LOOP_IF_BRIDGED }; + public: + enum Loop { LOOP, NO_LOOP, LOOP_IF_BRIDGED }; - typedef intrusive_ptr Ptr; + typedef intrusive_ptr Ptr; - SplineVertex(SplineVertex* prev, SplineVertex* next); + SplineVertex(SplineVertex* prev, SplineVertex* next); - virtual ~SplineVertex() = default; + virtual ~SplineVertex() = default; - /** - * We don't want reference counting for sentinel vertices, - * but we can't make ref() and unref() abstract here, because - * in case of sentinel vertices these function may actually - * be called from this class constructor. - */ - virtual void ref() const { - } + /** + * We don't want reference counting for sentinel vertices, + * but we can't make ref() and unref() abstract here, because + * in case of sentinel vertices these function may actually + * be called from this class constructor. + */ + virtual void ref() const {} - /** - * \see ref() - */ - virtual void unref() const { - } + /** + * \see ref() + */ + virtual void unref() const {} - /** - * \return Smart pointer to this vertex, unless it's a sentiel vertex, - * in which case the previous non-sentinel vertex is returned. - * If there are no non-sentinel vertices, a null smart pointer - * is returned. - */ - virtual SplineVertex::Ptr thisOrPrevReal(Loop loop) = 0; + /** + * \return Smart pointer to this vertex, unless it's a sentiel vertex, + * in which case the previous non-sentinel vertex is returned. + * If there are no non-sentinel vertices, a null smart pointer + * is returned. + */ + virtual SplineVertex::Ptr thisOrPrevReal(Loop loop) = 0; - /** - * \return Smart pointer to this vertex, unless it's a sentiel vertex, - * in which case the next non-sentinel vertex is returned. - * If there are no non-sentinel vertices, a null smart pointer - * is returned. - */ - virtual SplineVertex::Ptr thisOrNextReal(Loop loop) = 0; + /** + * \return Smart pointer to this vertex, unless it's a sentiel vertex, + * in which case the next non-sentinel vertex is returned. + * If there are no non-sentinel vertices, a null smart pointer + * is returned. + */ + virtual SplineVertex::Ptr thisOrNextReal(Loop loop) = 0; - virtual const QPointF point() const = 0; + virtual const QPointF point() const = 0; - virtual void setPoint(const QPointF& pt) = 0; + virtual void setPoint(const QPointF& pt) = 0; - virtual void remove(); + virtual void remove(); - bool hasAtLeastSiblings(int num); + bool hasAtLeastSiblings(int num); - SplineVertex::Ptr prev(Loop loop); + SplineVertex::Ptr prev(Loop loop); - SplineVertex::Ptr next(Loop loop); + SplineVertex::Ptr next(Loop loop); - SplineVertex::Ptr insertBefore(const QPointF& pt); + SplineVertex::Ptr insertBefore(const QPointF& pt); - SplineVertex::Ptr insertAfter(const QPointF& pt); + SplineVertex::Ptr insertAfter(const QPointF& pt); -protected: - /** - * The reason m_pPrev is an ordinary pointer rather than a smart pointer - * is that we don't want pairs of vertices holding smart pointers to each - * other. Note that we don't have a loop of smart pointers, because - * sentinel vertices aren't reference counted. - */ - SplineVertex* m_pPrev; - SplineVertex::Ptr m_ptrNext; + protected: + /** + * The reason m_prev is an ordinary pointer rather than a smart pointer + * is that we don't want pairs of vertices holding smart pointers to each + * other. Note that we don't have a loop of smart pointers, because + * sentinel vertices aren't reference counted. + */ + SplineVertex* m_prev; + SplineVertex::Ptr m_next; }; class SentinelSplineVertex : public SplineVertex { - DECLARE_NON_COPYABLE(SentinelSplineVertex) + DECLARE_NON_COPYABLE(SentinelSplineVertex) -public: - SentinelSplineVertex(); + public: + SentinelSplineVertex(); - ~SentinelSplineVertex() override; + ~SentinelSplineVertex() override; - SplineVertex::Ptr thisOrPrevReal(Loop loop) override; + SplineVertex::Ptr thisOrPrevReal(Loop loop) override; - SplineVertex::Ptr thisOrNextReal(Loop loop) override; + SplineVertex::Ptr thisOrNextReal(Loop loop) override; - const QPointF point() const override; + const QPointF point() const override; - void setPoint(const QPointF& pt) override; + void setPoint(const QPointF& pt) override; - void remove() override; + void remove() override; - SplineVertex::Ptr firstVertex() const; + SplineVertex::Ptr firstVertex() const; - SplineVertex::Ptr lastVertex() const; + SplineVertex::Ptr lastVertex() const; - bool bridged() const { - return m_bridged; - } + bool bridged() const { return m_bridged; } - void setBridged(bool bridged) { - m_bridged = bridged; - } + void setBridged(bool bridged) { m_bridged = bridged; } -private: - bool m_bridged; + private: + bool m_bridged; }; class RealSplineVertex : public SplineVertex { - DECLARE_NON_COPYABLE(RealSplineVertex) + DECLARE_NON_COPYABLE(RealSplineVertex) -public: - RealSplineVertex(const QPointF& pt, SplineVertex* prev, SplineVertex* next); + public: + RealSplineVertex(const QPointF& pt, SplineVertex* prev, SplineVertex* next); - void ref() const override; + void ref() const override; - void unref() const override; + void unref() const override; - SplineVertex::Ptr thisOrPrevReal(Loop loop) override; + SplineVertex::Ptr thisOrPrevReal(Loop loop) override; - SplineVertex::Ptr thisOrNextReal(Loop loop) override; + SplineVertex::Ptr thisOrNextReal(Loop loop) override; - const QPointF point() const override; + const QPointF point() const override; - void setPoint(const QPointF& pt) override; + void setPoint(const QPointF& pt) override; -private: - QPointF m_point; - mutable int m_refCounter; + private: + QPointF m_point; + mutable int m_counter; }; diff --git a/zones/Zone.cpp b/zones/Zone.cpp index df7a4bbc0..35be722fb 100644 --- a/zones/Zone.cpp +++ b/zones/Zone.cpp @@ -19,42 +19,40 @@ #include "Zone.h" #include -Zone::Zone(const SerializableSpline& spline, const PropertySet& props) : m_spline(spline), m_props(props) { -} +Zone::Zone(const SerializableSpline& spline, const PropertySet& props) : m_spline(spline), m_props(props) {} Zone::Zone(const QDomElement& el, const PropertyFactory& prop_factory) - : m_spline(el.namedItem("spline").toElement()), m_props(el.namedItem("properties").toElement(), prop_factory) { -} + : m_spline(el.namedItem("spline").toElement()), m_props(el.namedItem("properties").toElement(), prop_factory) {} Zone::Zone(const QPolygonF& polygon) : m_spline(polygon) { - m_props.locateOrCreate()->setLayer(output::PictureLayerProperty::PAINTER2); + m_props.locateOrCreate()->setLayer(output::PictureLayerProperty::PAINTER2); - m_props.locateOrCreate()->setZoneCategory( - output::ZoneCategoryProperty::RECTANGULAR_OUTLINE); + m_props.locateOrCreate()->setZoneCategory( + output::ZoneCategoryProperty::RECTANGULAR_OUTLINE); } QDomElement Zone::toXml(QDomDocument& doc, const QString& name) const { - QDomElement el(doc.createElement(name)); - el.appendChild(m_spline.toXml(doc, "spline")); - el.appendChild(m_props.toXml(doc, "properties")); + QDomElement el(doc.createElement(name)); + el.appendChild(m_spline.toXml(doc, "spline")); + el.appendChild(m_props.toXml(doc, "properties")); - return el; + return el; } bool Zone::isValid() const { - const QPolygonF& shape = m_spline.toPolygon(); - - switch (shape.size()) { - case 0: - case 1: - case 2: - return false; - case 3: - if (shape.front() == shape.back()) { - return false; - } - // fall through - default: - return true; - } + const QPolygonF& shape = m_spline.toPolygon(); + + switch (shape.size()) { + case 0: + case 1: + case 2: + return false; + case 3: + if (shape.front() == shape.back()) { + return false; + } + // fall through + default: + return true; + } } diff --git a/zones/Zone.h b/zones/Zone.h index 88ccd2574..0e7dda963 100644 --- a/zones/Zone.h +++ b/zones/Zone.h @@ -19,11 +19,11 @@ #ifndef ZONE_H_ #define ZONE_H_ -#include "SerializableSpline.h" -#include "intrusive_ptr.h" #include "PropertySet.h" +#include "SerializableSpline.h" #include "filters/output/PictureLayerProperty.h" #include "filters/output/ZoneCategoryProperty.h" +#include "intrusive_ptr.h" class PropertyFactory; class QDomDocument; @@ -31,33 +31,27 @@ class QDomElement; class QString; class Zone { - // Member-wise copying is OK, but that will produce a partly shallow copy. -public: - explicit Zone(const SerializableSpline& spline, const PropertySet& props = PropertySet()); + // Member-wise copying is OK, but that will produce a partly shallow copy. + public: + explicit Zone(const SerializableSpline& spline, const PropertySet& props = PropertySet()); - Zone(const QDomElement& el, const PropertyFactory& prop_factory); + Zone(const QDomElement& el, const PropertyFactory& prop_factory); - explicit Zone(const QPolygonF& polygon); + explicit Zone(const QPolygonF& polygon); - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - const SerializableSpline& spline() const { - return m_spline; - } + const SerializableSpline& spline() const { return m_spline; } - PropertySet& properties() { - return m_props; - } + PropertySet& properties() { return m_props; } - const PropertySet& properties() const { - return m_props; - } + const PropertySet& properties() const { return m_props; } - bool isValid() const; + bool isValid() const; -private: - SerializableSpline m_spline; - PropertySet m_props; + private: + SerializableSpline m_spline; + PropertySet m_props; }; diff --git a/zones/ZoneContextMenuInteraction.cpp b/zones/ZoneContextMenuInteraction.cpp index 745f576d5..0cfbb286f 100644 --- a/zones/ZoneContextMenuInteraction.cpp +++ b/zones/ZoneContextMenuInteraction.cpp @@ -17,246 +17,243 @@ */ #include "ZoneContextMenuInteraction.h" -#include "ZoneInteractionContext.h" -#include "ImageViewBase.h" -#include "QtSignalForwarder.h" +#include #include +#include #include #include -#include -#include #include +#include "ImageViewBase.h" +#include "QtSignalForwarder.h" +#include "ZoneInteractionContext.h" class ZoneContextMenuInteraction::OrderByArea { -public: - bool operator()(const EditableZoneSet::Zone& lhs, const EditableZoneSet::Zone& rhs) const { - const QRectF lhs_bbox(lhs.spline()->toPolygon().boundingRect()); - const QRectF rhs_bbox(rhs.spline()->toPolygon().boundingRect()); - const qreal lhs_area = lhs_bbox.width() * lhs_bbox.height(); - const qreal rhs_area = rhs_bbox.width() * rhs_bbox.height(); - - return lhs_area < rhs_area; - } + public: + bool operator()(const EditableZoneSet::Zone& lhs, const EditableZoneSet::Zone& rhs) const { + const QRectF lhs_bbox(lhs.spline()->toPolygon().boundingRect()); + const QRectF rhs_bbox(rhs.spline()->toPolygon().boundingRect()); + const qreal lhs_area = lhs_bbox.width() * lhs_bbox.height(); + const qreal rhs_area = rhs_bbox.width() * rhs_bbox.height(); + + return lhs_area < rhs_area; + } }; ZoneContextMenuInteraction* ZoneContextMenuInteraction::create(ZoneInteractionContext& context, InteractionState& interaction) { - return create(context, interaction, boost::bind(&ZoneContextMenuInteraction::defaultMenuCustomizer, _1, _2)); + return create(context, interaction, boost::bind(&ZoneContextMenuInteraction::defaultMenuCustomizer, _1, _2)); } ZoneContextMenuInteraction* ZoneContextMenuInteraction::create(ZoneInteractionContext& context, InteractionState& interaction, const MenuCustomizer& menu_customizer) { - std::vector selectable_zones(zonesUnderMouse(context)); + std::vector selectable_zones(zonesUnderMouse(context)); - if (selectable_zones.empty()) { - return nullptr; - } else { - return new ZoneContextMenuInteraction(context, interaction, menu_customizer, selectable_zones); - } + if (selectable_zones.empty()) { + return nullptr; + } else { + return new ZoneContextMenuInteraction(context, interaction, menu_customizer, selectable_zones); + } } std::vector ZoneContextMenuInteraction::zonesUnderMouse( - ZoneInteractionContext& context) { - const QTransform from_screen(context.imageView().widgetToImage()); - const QPointF image_mouse_pos( - from_screen.map(context.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5))); - - // Find zones containing the mouse position. - std::vector selectable_zones; - for (const EditableZoneSet::Zone& zone : context.zones()) { - QPainterPath path; - path.setFillRule(Qt::WindingFill); - path.addPolygon(zone.spline()->toPolygon()); - if (path.contains(image_mouse_pos)) { - selectable_zones.emplace_back(zone); - } + ZoneInteractionContext& context) { + const QTransform from_screen(context.imageView().widgetToImage()); + const QPointF image_mouse_pos(from_screen.map(context.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5))); + + // Find zones containing the mouse position. + std::vector selectable_zones; + for (const EditableZoneSet::Zone& zone : context.zones()) { + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addPolygon(zone.spline()->toPolygon()); + if (path.contains(image_mouse_pos)) { + selectable_zones.emplace_back(zone); } + } - return selectable_zones; + return selectable_zones; } ZoneContextMenuInteraction::ZoneContextMenuInteraction(ZoneInteractionContext& context, InteractionState& interaction, const MenuCustomizer& menu_customizer, std::vector& selectable_zones) - : m_rContext(context), - m_ptrMenu(new QMenu(&context.imageView())), - m_highlightedZoneIdx(-1), - m_menuItemTriggered(false) { + : m_context(context), + m_menu(new QMenu(&context.imageView())), + m_highlightedZoneIdx(-1), + m_menuItemTriggered(false) { #ifdef Q_OS_MAC - m_extraDelaysDone = 0; + m_extraDelaysDone = 0; #endif - m_selectableZones.swap(selectable_zones); - std::sort(m_selectableZones.begin(), m_selectableZones.end(), OrderByArea()); - - interaction.capture(m_interaction); + m_selectableZones.swap(selectable_zones); + std::sort(m_selectableZones.begin(), m_selectableZones.end(), OrderByArea()); - int h = 20; - const int h_step = 65; - const int s = 255 * 64 / 100; - const int v = 255 * 96 / 100; - const int alpha = 150; - QColor color; + interaction.capture(m_interaction); - auto* hover_map = new QSignalMapper(this); - connect(hover_map, SIGNAL(mapped(int)), SLOT(highlightItem(int))); + int h = 20; + const int h_step = 65; + const int s = 255 * 64 / 100; + const int v = 255 * 96 / 100; + const int alpha = 150; + QColor color; - QPixmap pixmap; + auto* hover_map = new QSignalMapper(this); + connect(hover_map, SIGNAL(mapped(int)), SLOT(highlightItem(int))); - auto it(m_selectableZones.begin()); - const auto end(m_selectableZones.end()); - for (int i = 0; it != end; ++it, ++i, h = (h + h_step) % 360) { - color.setHsv(h, s, v, alpha); - it->color = color.toRgb(); + QPixmap pixmap; - if (m_selectableZones.size() > 1) { - pixmap = QPixmap(16, 16); - color.setAlpha(255); - pixmap.fill(color); - } + auto it(m_selectableZones.begin()); + const auto end(m_selectableZones.end()); + for (int i = 0; it != end; ++it, ++i, h = (h + h_step) % 360) { + color.setHsv(h, s, v, alpha); + it->color = color.toRgb(); - const StandardMenuItems std_items(propertiesMenuItemFor(*it), deleteMenuItemFor(*it)); + if (m_selectableZones.size() > 1) { + pixmap = QPixmap(16, 16); + color.setAlpha(255); + pixmap.fill(color); + } - for (const ZoneContextMenuItem& item : menu_customizer(*it, std_items)) { - QAction* action = m_ptrMenu->addAction(pixmap, item.label()); - new QtSignalForwarder(action, SIGNAL(triggered()), - boost::bind(&ZoneContextMenuInteraction::menuItemTriggered, this, - boost::ref(interaction), item.callback())); + const StandardMenuItems std_items(propertiesMenuItemFor(*it), deleteMenuItemFor(*it)); - hover_map->setMapping(action, i); - connect(action, SIGNAL(hovered()), hover_map, SLOT(map())); - } + for (const ZoneContextMenuItem& item : menu_customizer(*it, std_items)) { + QAction* action = m_menu->addAction(pixmap, item.label()); + new QtSignalForwarder( + action, SIGNAL(triggered()), + boost::bind(&ZoneContextMenuInteraction::menuItemTriggered, this, boost::ref(interaction), item.callback())); - m_ptrMenu->addSeparator(); + hover_map->setMapping(action, i); + connect(action, SIGNAL(hovered()), hover_map, SLOT(map())); } - // The queued connection is used to ensure it gets called *after* - // QAction::triggered(). - connect(m_ptrMenu.get(), SIGNAL(aboutToHide()), SLOT(menuAboutToHide()), Qt::QueuedConnection); - highlightItem(0); - m_ptrMenu->popup(QCursor::pos()); + m_menu->addSeparator(); + } + // The queued connection is used to ensure it gets called *after* + // QAction::triggered(). + connect(m_menu.get(), SIGNAL(aboutToHide()), SLOT(menuAboutToHide()), Qt::QueuedConnection); + + highlightItem(0); + m_menu->popup(QCursor::pos()); } ZoneContextMenuInteraction::~ZoneContextMenuInteraction() = default; void ZoneContextMenuInteraction::onPaint(QPainter& painter, const InteractionState&) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHint(QPainter::Antialiasing); - - if (m_highlightedZoneIdx >= 0) { - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const Zone& zone = m_selectableZones[m_highlightedZoneIdx]; - m_visualizer.drawSpline(painter, to_screen, zone.spline()); - } + painter.setWorldMatrixEnabled(false); + painter.setRenderHint(QPainter::Antialiasing); + + if (m_highlightedZoneIdx >= 0) { + const QTransform to_screen(m_context.imageView().imageToWidget()); + const Zone& zone = m_selectableZones[m_highlightedZoneIdx]; + m_visualizer.drawSpline(painter, to_screen, zone.spline()); + } } void ZoneContextMenuInteraction::menuAboutToHide() { - if (m_menuItemTriggered) { - return; - } + if (m_menuItemTriggered) { + return; + } #ifdef Q_OS_MAC - // On OSX, QAction::triggered() is emitted significantly (like 150ms) - // later than QMenu::aboutToHide(). This makes it generally not possible - // to tell whether the menu was just dismissed or a menu item was clicked. - // The only way to tell is to check back later, which we do here. - if (m_extraDelaysDone++ < 1) { - QTimer::singleShot(200, this, SLOT(menuAboutToHide())); - - return; - } + // On OSX, QAction::triggered() is emitted significantly (like 150ms) + // later than QMenu::aboutToHide(). This makes it generally not possible + // to tell whether the menu was just dismissed or a menu item was clicked. + // The only way to tell is to check back later, which we do here. + if (m_extraDelaysDone++ < 1) { + QTimer::singleShot(200, this, SLOT(menuAboutToHide())); + + return; + } #endif - InteractionHandler* next_handler = m_rContext.createDefaultInteraction(); - if (next_handler) { - makePeerPreceeder(*next_handler); - } + InteractionHandler* next_handler = m_context.createDefaultInteraction(); + if (next_handler) { + makePeerPreceeder(*next_handler); + } - unlink(); - m_rContext.imageView().update(); - deleteLater(); + unlink(); + m_context.imageView().update(); + deleteLater(); } void ZoneContextMenuInteraction::menuItemTriggered(InteractionState& interaction, const ZoneContextMenuItem::Callback& callback) { - m_menuItemTriggered = true; - m_visualizer.switchToStrokeMode(); + m_menuItemTriggered = true; + m_visualizer.switchToStrokeMode(); - InteractionHandler* next_handler = callback(interaction); - if (next_handler) { - makePeerPreceeder(*next_handler); - } + InteractionHandler* next_handler = callback(interaction); + if (next_handler) { + makePeerPreceeder(*next_handler); + } - unlink(); - m_rContext.imageView().update(); - deleteLater(); + unlink(); + m_context.imageView().update(); + deleteLater(); } InteractionHandler* ZoneContextMenuInteraction::propertiesRequest(const EditableZoneSet::Zone& zone) { - m_rContext.showPropertiesCommand(zone); + m_context.showPropertiesCommand(zone); - return m_rContext.createDefaultInteraction(); + return m_context.createDefaultInteraction(); } InteractionHandler* ZoneContextMenuInteraction::deleteRequest(const EditableZoneSet::Zone& zone) { - m_rContext.zones().removeZone(zone.spline()); - m_rContext.zones().commit(); + m_context.zones().removeZone(zone.spline()); + m_context.zones().commit(); - return m_rContext.createDefaultInteraction(); + return m_context.createDefaultInteraction(); } ZoneContextMenuItem ZoneContextMenuInteraction::deleteMenuItemFor(const EditableZoneSet::Zone& zone) { - return ZoneContextMenuItem(tr("Delete"), boost::bind(&ZoneContextMenuInteraction::deleteRequest, this, zone)); + return ZoneContextMenuItem(tr("Delete"), boost::bind(&ZoneContextMenuInteraction::deleteRequest, this, zone)); } ZoneContextMenuItem ZoneContextMenuInteraction::propertiesMenuItemFor(const EditableZoneSet::Zone& zone) { - return ZoneContextMenuItem(tr("Properties"), - boost::bind(&ZoneContextMenuInteraction::propertiesRequest, this, zone)); + return ZoneContextMenuItem(tr("Properties"), boost::bind(&ZoneContextMenuInteraction::propertiesRequest, this, zone)); } void ZoneContextMenuInteraction::highlightItem(const int zone_idx) { - if (m_selectableZones.size() > 1) { - m_visualizer.switchToFillMode(m_selectableZones[zone_idx].color); - } else { - m_visualizer.switchToStrokeMode(); - } - m_highlightedZoneIdx = zone_idx; - m_rContext.imageView().update(); + if (m_selectableZones.size() > 1) { + m_visualizer.switchToFillMode(m_selectableZones[zone_idx].color); + } else { + m_visualizer.switchToStrokeMode(); + } + m_highlightedZoneIdx = zone_idx; + m_context.imageView().update(); } std::vector ZoneContextMenuInteraction::defaultMenuCustomizer(const EditableZoneSet::Zone& zone, const StandardMenuItems& std_items) { - std::vector items; - items.reserve(2); - items.push_back(std_items.propertiesItem); - items.push_back(std_items.deleteItem); + std::vector items; + items.reserve(2); + items.push_back(std_items.propertiesItem); + items.push_back(std_items.deleteItem); - return items; + return items; } /*========================== StandardMenuItem =========================*/ ZoneContextMenuInteraction::StandardMenuItems::StandardMenuItems(const ZoneContextMenuItem& properties_item, const ZoneContextMenuItem& delete_item) - : propertiesItem(properties_item), deleteItem(delete_item) { -} + : propertiesItem(properties_item), deleteItem(delete_item) {} /*============================= Visualizer ============================*/ void ZoneContextMenuInteraction::Visualizer::switchToFillMode(const QColor& color) { - m_color = color; + m_color = color; } void ZoneContextMenuInteraction::Visualizer::switchToStrokeMode() { - m_color = QColor(); + m_color = QColor(); } void ZoneContextMenuInteraction::Visualizer::prepareForSpline(QPainter& painter, const EditableSpline::Ptr& spline) { - BasicSplineVisualizer::prepareForSpline(painter, spline); - if (m_color.isValid()) { - painter.setBrush(m_color); - } + BasicSplineVisualizer::prepareForSpline(painter, spline); + if (m_color.isValid()) { + painter.setBrush(m_color); + } } diff --git a/zones/ZoneContextMenuInteraction.h b/zones/ZoneContextMenuInteraction.h index 074536da5..3950a85fd 100644 --- a/zones/ZoneContextMenuInteraction.h +++ b/zones/ZoneContextMenuInteraction.h @@ -19,121 +19,118 @@ #ifndef ZONE_CONTEXT_MENU_INTERACTION_H_ #define ZONE_CONTEXT_MENU_INTERACTION_H_ -#include "ZoneContextMenuItem.h" -#include "InteractionHandler.h" -#include "InteractionState.h" -#include "EditableSpline.h" -#include "EditableZoneSet.h" -#include "PropertySet.h" -#include "intrusive_ptr.h" -#include "BasicSplineVisualizer.h" -#include #include +#include #include #include #include #include #include +#include "BasicSplineVisualizer.h" +#include "EditableSpline.h" +#include "EditableZoneSet.h" +#include "InteractionHandler.h" +#include "InteractionState.h" +#include "PropertySet.h" +#include "ZoneContextMenuItem.h" +#include "intrusive_ptr.h" class ZoneInteractionContext; class QPainter; class QMenu; class ZoneContextMenuInteraction : public QObject, public InteractionHandler { - Q_OBJECT -public: - struct StandardMenuItems { - ZoneContextMenuItem propertiesItem; - ZoneContextMenuItem deleteItem; + Q_OBJECT + public: + struct StandardMenuItems { + ZoneContextMenuItem propertiesItem; + ZoneContextMenuItem deleteItem; - StandardMenuItems(const ZoneContextMenuItem& properties_item, const ZoneContextMenuItem& delete_item); - }; + StandardMenuItems(const ZoneContextMenuItem& properties_item, const ZoneContextMenuItem& delete_item); + }; - typedef boost::function(const EditableZoneSet::Zone&, const StandardMenuItems&)> - MenuCustomizer; + typedef boost::function(const EditableZoneSet::Zone&, const StandardMenuItems&)> + MenuCustomizer; - /** - * \note This factory method will return null if there are no zones - * under the mouse pointer. - */ - static ZoneContextMenuInteraction* create(ZoneInteractionContext& context, InteractionState& interaction); + /** + * \note This factory method will return null if there are no zones + * under the mouse pointer. + */ + static ZoneContextMenuInteraction* create(ZoneInteractionContext& context, InteractionState& interaction); - /** - * Same as above, plus a menu customization callback. - */ - static ZoneContextMenuInteraction* create(ZoneInteractionContext& context, - InteractionState& interaction, - const MenuCustomizer& menu_customizer); + /** + * Same as above, plus a menu customization callback. + */ + static ZoneContextMenuInteraction* create(ZoneInteractionContext& context, + InteractionState& interaction, + const MenuCustomizer& menu_customizer); - ~ZoneContextMenuInteraction() override; + ~ZoneContextMenuInteraction() override; -protected: - class Zone : public EditableZoneSet::Zone { - public: - QColor color; + protected: + class Zone : public EditableZoneSet::Zone { + public: + QColor color; - explicit Zone(const EditableZoneSet::Zone& zone) : EditableZoneSet::Zone(zone) { - } - }; + explicit Zone(const EditableZoneSet::Zone& zone) : EditableZoneSet::Zone(zone) {} + }; - static std::vector zonesUnderMouse(ZoneInteractionContext& context); + static std::vector zonesUnderMouse(ZoneInteractionContext& context); - ZoneContextMenuInteraction(ZoneInteractionContext& context, - InteractionState& interaction, - const MenuCustomizer& menu_customizer, - std::vector& selectable_zones); + ZoneContextMenuInteraction(ZoneInteractionContext& context, + InteractionState& interaction, + const MenuCustomizer& menu_customizer, + std::vector& selectable_zones); - ZoneInteractionContext& context() { - return m_rContext; - } + ZoneInteractionContext& context() { return m_context; } -private slots: + private slots: - void menuAboutToHide(); + void menuAboutToHide(); - void highlightItem(int zone_idx); + void highlightItem(int zone_idx); -private: - class OrderByArea; + private: + class OrderByArea; - class Visualizer : public BasicSplineVisualizer { - public: - void switchToFillMode(const QColor& color); + class Visualizer : public BasicSplineVisualizer { + public: + void switchToFillMode(const QColor& color); - void switchToStrokeMode(); + void switchToStrokeMode(); - void prepareForSpline(QPainter& painter, const EditableSpline::Ptr& spline) override; + void prepareForSpline(QPainter& painter, const EditableSpline::Ptr& spline) override; - private: - QColor m_color; - }; + private: + QColor m_color; + }; - static std::vector defaultMenuCustomizer(const EditableZoneSet::Zone& zone, - const StandardMenuItems& std_items); + static std::vector defaultMenuCustomizer(const EditableZoneSet::Zone& zone, + const StandardMenuItems& std_items); - void onPaint(QPainter& painter, const InteractionState& interaction) override; + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void menuItemTriggered(InteractionState& interaction, const ZoneContextMenuItem::Callback& callback); + void menuItemTriggered(InteractionState& interaction, const ZoneContextMenuItem::Callback& callback); - InteractionHandler* deleteRequest(const EditableZoneSet::Zone& zone); + InteractionHandler* deleteRequest(const EditableZoneSet::Zone& zone); - InteractionHandler* propertiesRequest(const EditableZoneSet::Zone& zone); + InteractionHandler* propertiesRequest(const EditableZoneSet::Zone& zone); - ZoneContextMenuItem propertiesMenuItemFor(const EditableZoneSet::Zone& zone); + ZoneContextMenuItem propertiesMenuItemFor(const EditableZoneSet::Zone& zone); - ZoneContextMenuItem deleteMenuItemFor(const EditableZoneSet::Zone& zone); + ZoneContextMenuItem deleteMenuItemFor(const EditableZoneSet::Zone& zone); - ZoneInteractionContext& m_rContext; - std::vector m_selectableZones; - InteractionState::Captor m_interaction; - Visualizer m_visualizer; - std::unique_ptr m_ptrMenu; - int m_highlightedZoneIdx; - bool m_menuItemTriggered; + ZoneInteractionContext& m_context; + std::vector m_selectableZones; + InteractionState::Captor m_interaction; + Visualizer m_visualizer; + std::unique_ptr m_menu; + int m_highlightedZoneIdx; + bool m_menuItemTriggered; #ifdef Q_OS_MAC - int m_extraDelaysDone; + int m_extraDelaysDone; #endif }; diff --git a/zones/ZoneContextMenuItem.h b/zones/ZoneContextMenuItem.h index e58bd377c..286092154 100644 --- a/zones/ZoneContextMenuItem.h +++ b/zones/ZoneContextMenuItem.h @@ -26,33 +26,28 @@ class InteractionState; class InteractionHandler; class ZoneContextMenuItem { -public: - /** - * A callback may either return the InteractionHandler to connect - * in place of ZoneContextMenuInteraction, or null, indicating not - * to connect anything when ZoneContextMenuInteraction is disconnected. - * The ownership of the returned InteractionHandler will be transferred - * to ZoneInteractionContext. It's still possible for the returned - * InteractionHandler to be a member of another object, but in this case - * you will need to make sure it's disconnected from ZoneInteractionContext - * before ZoneInteractionContext destroys. - */ - typedef boost::function Callback; - - ZoneContextMenuItem(const QString& label, const Callback& callback) : m_label(label), m_callback(callback) { - } - - const QString& label() const { - return m_label; - } - - const Callback& callback() const { - return m_callback; - } - -private: - QString m_label; - Callback m_callback; + public: + /** + * A callback may either return the InteractionHandler to connect + * in place of ZoneContextMenuInteraction, or null, indicating not + * to connect anything when ZoneContextMenuInteraction is disconnected. + * The ownership of the returned InteractionHandler will be transferred + * to ZoneInteractionContext. It's still possible for the returned + * InteractionHandler to be a member of another object, but in this case + * you will need to make sure it's disconnected from ZoneInteractionContext + * before ZoneInteractionContext destroys. + */ + typedef boost::function Callback; + + ZoneContextMenuItem(const QString& label, const Callback& callback) : m_label(label), m_callback(callback) {} + + const QString& label() const { return m_label; } + + const Callback& callback() const { return m_callback; } + + private: + QString m_label; + Callback m_callback; }; diff --git a/zones/ZoneCreationInteraction.cpp b/zones/ZoneCreationInteraction.cpp index 2350856f3..fa1242764 100644 --- a/zones/ZoneCreationInteraction.cpp +++ b/zones/ZoneCreationInteraction.cpp @@ -17,307 +17,313 @@ */ #include "ZoneCreationInteraction.h" -#include "ZoneInteractionContext.h" -#include "ImageViewBase.h" +#include #include #include -#include +#include #include +#include "ImageViewBase.h" +#include "ZoneInteractionContext.h" ZoneCreationInteraction::ZoneCreationInteraction(ZoneInteractionContext& context, InteractionState& interaction) - : m_rContext(context), - m_dragHandler(context.imageView(), boost::lambda::constant(true)), - m_dragWatcher(m_dragHandler), - m_zoomHandler(context.imageView(), boost::lambda::constant(true)), - m_ptrSpline(new EditableSpline) { - const QPointF screen_mouse_pos(m_rContext.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5)); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - m_nextVertexImagePos = from_screen.map(screen_mouse_pos); + : m_context(context), + m_dragHandler(context.imageView(), boost::bind(&ZoneCreationInteraction::isDragHandlerPermitted, this, _1)), + m_dragWatcher(m_dragHandler), + m_zoomHandler(context.imageView(), boost::lambda::constant(true)), + m_spline(new EditableSpline), + m_lassoModeModifiers(Qt::ShiftModifier | Qt::AltModifier) { + const QPointF screen_mouse_pos(m_context.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5)); + const QTransform from_screen(m_context.imageView().widgetToImage()); + m_nextVertexImagePos = from_screen.map(screen_mouse_pos); - m_nextVertexImagePos_mid1 = m_nextVertexImagePos; - m_nextVertexImagePos_mid2 = m_nextVertexImagePos; - m_rectangularZoneType = false; + m_nextVertexImagePos_mid1 = m_nextVertexImagePos; + m_nextVertexImagePos_mid2 = m_nextVertexImagePos; + m_rectangularZoneType = false; - m_lassoMode = false; + m_lassoMode = false; - makeLastFollower(m_dragHandler); - m_dragHandler.makeFirstFollower(m_dragWatcher); + makeLastFollower(m_dragHandler); + m_dragHandler.makeFirstFollower(m_dragWatcher); - makeLastFollower(m_zoomHandler); + makeLastFollower(m_zoomHandler); - interaction.capture(m_interaction); - m_ptrSpline->appendVertex(m_nextVertexImagePos); + interaction.capture(m_interaction); + m_spline->appendVertex(m_nextVertexImagePos); - updateStatusTip(); + updateStatusTip(); } void ZoneCreationInteraction::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHint(QPainter::Antialiasing); - - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - - m_visualizer.drawSplines(painter, to_screen, m_rContext.zones()); - - QPen solid_line_pen(m_visualizer.solidColor()); - solid_line_pen.setCosmetic(true); - solid_line_pen.setWidthF(1.5); - - QLinearGradient gradient; // From inactive to active point. - gradient.setColorAt(0.0, m_visualizer.solidColor()); - gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); - - QPen gradient_pen; - gradient_pen.setCosmetic(true); - gradient_pen.setWidthF(1.5); - - painter.setPen(solid_line_pen); - painter.setBrush(Qt::NoBrush); - - QColor start_color = m_visualizer.solidColor(); - QColor stop_color = m_visualizer.highlightDarkColor(); - QColor mid_color; - - int red = (start_color.red() + stop_color.red()) / 2; - int green = (start_color.green() + stop_color.green()) / 2; - int blue = (start_color.blue() + stop_color.blue()) / 2; - - mid_color.setRgb(red, green, blue); - - QLinearGradient gradient_mid1; - gradient_mid1.setColorAt(0.0, start_color); - gradient_mid1.setColorAt(1.0, mid_color); - - QLinearGradient gradient_mid2; - gradient_mid2.setColorAt(0.0, mid_color); - gradient_mid2.setColorAt(1.0, stop_color); - - if (m_rectangularZoneType) { - if (m_nextVertexImagePos != m_ptrSpline->firstVertex()->point()) { - m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos_mid1), - m_visualizer.highlightBrightColor()); - m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos_mid2), - m_visualizer.highlightBrightColor()); - - const QLineF line1_mid1( - to_screen.map(QLineF(m_ptrSpline->firstVertex()->point(), m_nextVertexImagePos_mid1))); - gradient_mid1.setStart(line1_mid1.p1()); - gradient_mid1.setFinalStop(line1_mid1.p2()); - gradient_pen.setBrush(gradient_mid1); - painter.setPen(gradient_pen); - painter.drawLine(line1_mid1); - - const QLineF line2_mid1(to_screen.map(QLineF(m_nextVertexImagePos_mid1, m_nextVertexImagePos))); - gradient_mid2.setStart(line2_mid1.p1()); - gradient_mid2.setFinalStop(line2_mid1.p2()); - gradient_pen.setBrush(gradient_mid2); - painter.setPen(gradient_pen); - painter.drawLine(line2_mid1); - - const QLineF line1_mid2( - to_screen.map(QLineF(m_ptrSpline->firstVertex()->point(), m_nextVertexImagePos_mid2))); - gradient_mid1.setStart(line1_mid2.p1()); - gradient_mid1.setFinalStop(line1_mid2.p2()); - gradient_pen.setBrush(gradient_mid1); - painter.setPen(gradient_pen); - painter.drawLine(line1_mid2); - - const QLineF line2_mid2(to_screen.map(QLineF(m_nextVertexImagePos_mid2, m_nextVertexImagePos))); - gradient_mid2.setStart(line2_mid2.p1()); - gradient_mid2.setFinalStop(line2_mid2.p2()); - gradient_pen.setBrush(gradient_mid2); - painter.setPen(gradient_pen); - painter.drawLine(line2_mid2); - } - } else { - for (EditableSpline::SegmentIterator it(*m_ptrSpline); it.hasNext();) { - const SplineSegment segment(it.next()); - const QLineF line(to_screen.map(segment.toLine())); - - if ((segment.prev == m_ptrSpline->firstVertex()) && (segment.prev->point() == m_nextVertexImagePos)) { - gradient.setStart(line.p2()); - gradient.setFinalStop(line.p1()); - gradient_pen.setBrush(gradient); - painter.setPen(gradient_pen); - painter.drawLine(line); - painter.setPen(solid_line_pen); - } else { - painter.drawLine(line); - } - } - - const QLineF line(to_screen.map(QLineF(m_ptrSpline->lastVertex()->point(), m_nextVertexImagePos))); - gradient.setStart(line.p1()); - gradient.setFinalStop(line.p2()); + painter.setWorldMatrixEnabled(false); + painter.setRenderHint(QPainter::Antialiasing); + + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QTransform from_screen(m_context.imageView().widgetToImage()); + + m_visualizer.drawSplines(painter, to_screen, m_context.zones()); + + QPen solid_line_pen(m_visualizer.solidColor()); + solid_line_pen.setCosmetic(true); + solid_line_pen.setWidthF(1.5); + + QLinearGradient gradient; // From inactive to active point. + gradient.setColorAt(0.0, m_visualizer.solidColor()); + gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); + + QPen gradient_pen; + gradient_pen.setCosmetic(true); + gradient_pen.setWidthF(1.5); + + painter.setPen(solid_line_pen); + painter.setBrush(Qt::NoBrush); + + QColor start_color = m_visualizer.solidColor(); + QColor stop_color = m_visualizer.highlightDarkColor(); + QColor mid_color; + + int red = (start_color.red() + stop_color.red()) / 2; + int green = (start_color.green() + stop_color.green()) / 2; + int blue = (start_color.blue() + stop_color.blue()) / 2; + + mid_color.setRgb(red, green, blue); + + QLinearGradient gradient_mid1; + gradient_mid1.setColorAt(0.0, start_color); + gradient_mid1.setColorAt(1.0, mid_color); + + QLinearGradient gradient_mid2; + gradient_mid2.setColorAt(0.0, mid_color); + gradient_mid2.setColorAt(1.0, stop_color); + + if (m_rectangularZoneType) { + if (m_nextVertexImagePos != m_spline->firstVertex()->point()) { + m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos_mid1), m_visualizer.highlightBrightColor()); + m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos_mid2), m_visualizer.highlightBrightColor()); + + const QLineF line1_mid1(to_screen.map(QLineF(m_spline->firstVertex()->point(), m_nextVertexImagePos_mid1))); + gradient_mid1.setStart(line1_mid1.p1()); + gradient_mid1.setFinalStop(line1_mid1.p2()); + gradient_pen.setBrush(gradient_mid1); + painter.setPen(gradient_pen); + painter.drawLine(line1_mid1); + + const QLineF line2_mid1(to_screen.map(QLineF(m_nextVertexImagePos_mid1, m_nextVertexImagePos))); + gradient_mid2.setStart(line2_mid1.p1()); + gradient_mid2.setFinalStop(line2_mid1.p2()); + gradient_pen.setBrush(gradient_mid2); + painter.setPen(gradient_pen); + painter.drawLine(line2_mid1); + + const QLineF line1_mid2(to_screen.map(QLineF(m_spline->firstVertex()->point(), m_nextVertexImagePos_mid2))); + gradient_mid1.setStart(line1_mid2.p1()); + gradient_mid1.setFinalStop(line1_mid2.p2()); + gradient_pen.setBrush(gradient_mid1); + painter.setPen(gradient_pen); + painter.drawLine(line1_mid2); + + const QLineF line2_mid2(to_screen.map(QLineF(m_nextVertexImagePos_mid2, m_nextVertexImagePos))); + gradient_mid2.setStart(line2_mid2.p1()); + gradient_mid2.setFinalStop(line2_mid2.p2()); + gradient_pen.setBrush(gradient_mid2); + painter.setPen(gradient_pen); + painter.drawLine(line2_mid2); + } + } else { + for (EditableSpline::SegmentIterator it(*m_spline); it.hasNext();) { + const SplineSegment segment(it.next()); + const QLineF line(to_screen.map(segment.toLine())); + + if ((segment.prev == m_spline->firstVertex()) && (segment.prev->point() == m_nextVertexImagePos)) { + gradient.setStart(line.p2()); + gradient.setFinalStop(line.p1()); gradient_pen.setBrush(gradient); painter.setPen(gradient_pen); painter.drawLine(line); + painter.setPen(solid_line_pen); + } else { + painter.drawLine(line); + } } - m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos), m_visualizer.highlightBrightColor()); + const QLineF line(to_screen.map(QLineF(m_spline->lastVertex()->point(), m_nextVertexImagePos))); + gradient.setStart(line.p1()); + gradient.setFinalStop(line.p2()); + gradient_pen.setBrush(gradient); + painter.setPen(gradient_pen); + painter.drawLine(line); + } + + m_visualizer.drawVertex(painter, to_screen.map(m_nextVertexImagePos), m_visualizer.highlightBrightColor()); } // ZoneCreationInteraction::onPaint void ZoneCreationInteraction::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - if (event->key() == Qt::Key_Escape) { - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - m_rContext.imageView().update(); - delete this; - event->accept(); - } + if (event->key() == Qt::Key_Escape) { + makePeerPreceeder(*m_context.createDefaultInteraction()); + m_context.imageView().update(); + delete this; + event->accept(); + } } void ZoneCreationInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() != Qt::LeftButton) { - return; - } + if (event->button() != Qt::LeftButton) { + return; + } - if (!m_rectangularZoneType && (event->modifiers() == Qt::AltModifier)) { - m_lassoMode = true; - } + if (!m_rectangularZoneType && (event->modifiers() == m_lassoModeModifiers)) { + m_lassoMode = true; + } - event->accept(); + event->accept(); } void ZoneCreationInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() != Qt::LeftButton) { - return; - } - if (m_dragWatcher.haveSignificantDrag() && !m_lassoMode) { - return; + if (event->button() != Qt::LeftButton) { + return; + } + if (m_dragWatcher.haveSignificantDrag() && !m_lassoMode) { + return; + } + m_lassoMode = false; + + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QTransform from_screen(m_context.imageView().widgetToImage()); + const QPointF screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); + const QPointF image_mouse_pos(from_screen.map(screen_mouse_pos)); + + if (m_rectangularZoneType) { + if (m_nextVertexImagePos != m_spline->firstVertex()->point()) { + QPointF first_point = m_spline->firstVertex()->point(); + + m_spline.reset(new EditableSpline); + + m_spline->appendVertex(first_point); + m_spline->appendVertex(m_nextVertexImagePos_mid1); + m_spline->appendVertex(m_nextVertexImagePos); + m_spline->appendVertex(m_nextVertexImagePos_mid2); + + m_spline->setBridged(true); + m_context.zones().addZone(m_spline); + m_context.zones().commit(); } - m_lassoMode = false; - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - const QPointF screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); - const QPointF image_mouse_pos(from_screen.map(screen_mouse_pos)); - - if (m_rectangularZoneType) { - if (m_nextVertexImagePos != m_ptrSpline->firstVertex()->point()) { - QPointF first_point = m_ptrSpline->firstVertex()->point(); - - m_ptrSpline.reset(new EditableSpline); - - m_ptrSpline->appendVertex(first_point); - m_ptrSpline->appendVertex(m_nextVertexImagePos_mid1); - m_ptrSpline->appendVertex(m_nextVertexImagePos); - m_ptrSpline->appendVertex(m_nextVertexImagePos_mid2); - - m_ptrSpline->setBridged(true); - m_rContext.zones().addZone(m_ptrSpline); - m_rContext.zones().commit(); - } - - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - m_rContext.imageView().update(); + makePeerPreceeder(*m_context.createDefaultInteraction()); + m_context.imageView().update(); + delete this; + } else { + if (m_spline->hasAtLeastSegments(2) && (m_nextVertexImagePos == m_spline->firstVertex()->point())) { + // Finishing the spline. Bridging the first and the last points + // will create another segment. + m_spline->setBridged(true); + m_context.zones().addZone(m_spline); + m_context.zones().commit(); + + makePeerPreceeder(*m_context.createDefaultInteraction()); + m_context.imageView().update(); + delete this; + } else if (m_nextVertexImagePos == m_spline->lastVertex()->point()) { + // Removing the last vertex. + m_spline->lastVertex()->remove(); + if (!m_spline->firstVertex()) { + // If it was the only vertex, cancelling spline creation. + makePeerPreceeder(*m_context.createDefaultInteraction()); + m_context.imageView().update(); delete this; + } } else { - if (m_ptrSpline->hasAtLeastSegments(2) && (m_nextVertexImagePos == m_ptrSpline->firstVertex()->point())) { - // Finishing the spline. Bridging the first and the last points - // will create another segment. - m_ptrSpline->setBridged(true); - m_rContext.zones().addZone(m_ptrSpline); - m_rContext.zones().commit(); - - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - m_rContext.imageView().update(); - delete this; - } else if (m_nextVertexImagePos == m_ptrSpline->lastVertex()->point()) { - // Removing the last vertex. - m_ptrSpline->lastVertex()->remove(); - if (!m_ptrSpline->firstVertex()) { - // If it was the only vertex, cancelling spline creation. - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - m_rContext.imageView().update(); - delete this; - } - } else { - // Adding a new vertex, provided we are not too close to the previous one. - const Proximity prox(screen_mouse_pos, m_ptrSpline->lastVertex()->point()); - if (prox > interaction.proximityThreshold()) { - m_ptrSpline->appendVertex(image_mouse_pos); - updateStatusTip(); - } - } + // Adding a new vertex, provided we are not too close to the previous one. + const Proximity prox(screen_mouse_pos, m_spline->lastVertex()->point()); + if (prox > interaction.proximityThreshold()) { + m_spline->appendVertex(image_mouse_pos); + updateStatusTip(); + } } + } - event->accept(); + event->accept(); } // ZoneCreationInteraction::onMouseReleaseEvent void ZoneCreationInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - const QPointF screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); + const QPointF screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QTransform from_screen(m_context.imageView().widgetToImage()); + + m_nextVertexImagePos = from_screen.map(screen_mouse_pos); + const QPointF first(to_screen.map(m_spline->firstVertex()->point())); + const QPointF last(to_screen.map(m_spline->lastVertex()->point())); + + if (!m_rectangularZoneType) { + if (event->modifiers() == Qt::ControlModifier) { + m_rectangularZoneType = true; + m_lassoMode = false; + updateStatusTip(); + } else if (!m_lassoMode && (event->modifiers() == m_lassoModeModifiers) && (event->buttons() & Qt::LeftButton)) { + m_lassoMode = true; + } + } + + if (Proximity(last, screen_mouse_pos) <= interaction.proximityThreshold()) { + m_nextVertexImagePos = m_spline->lastVertex()->point(); + } else if (m_spline->hasAtLeastSegments(2) || m_rectangularZoneType) { + if (Proximity(first, screen_mouse_pos) <= interaction.proximityThreshold()) { + m_nextVertexImagePos = m_spline->firstVertex()->point(); + updateStatusTip(); + } + } - m_nextVertexImagePos = from_screen.map(screen_mouse_pos); - const QPointF first(to_screen.map(m_ptrSpline->firstVertex()->point())); - const QPointF last(to_screen.map(m_ptrSpline->lastVertex()->point())); + if (m_rectangularZoneType && (m_nextVertexImagePos != m_spline->firstVertex()->point())) { + QPointF screen_mouse_pos_mid1; + screen_mouse_pos_mid1.setX(first.x()); + screen_mouse_pos_mid1.setY(screen_mouse_pos.y()); - if (!m_rectangularZoneType && (event->modifiers() == Qt::ControlModifier)) { - m_rectangularZoneType = true; - m_lassoMode = false; - updateStatusTip(); - } + QPointF screen_mouse_pos_mid2; + screen_mouse_pos_mid2.setX(screen_mouse_pos.x()); + screen_mouse_pos_mid2.setY(first.y()); - if (Proximity(last, screen_mouse_pos) <= interaction.proximityThreshold()) { - m_nextVertexImagePos = m_ptrSpline->lastVertex()->point(); - } else if (m_ptrSpline->hasAtLeastSegments(2) || m_rectangularZoneType) { - if (Proximity(first, screen_mouse_pos) <= interaction.proximityThreshold()) { - m_nextVertexImagePos = m_ptrSpline->firstVertex()->point(); - updateStatusTip(); - } - } + qreal dx = screen_mouse_pos.x() - first.x(); + qreal dy = screen_mouse_pos.y() - first.y(); - if (m_rectangularZoneType && (m_nextVertexImagePos != m_ptrSpline->firstVertex()->point())) { - QPointF screen_mouse_pos_mid1; - screen_mouse_pos_mid1.setX(first.x()); - screen_mouse_pos_mid1.setY(screen_mouse_pos.y()); - - QPointF screen_mouse_pos_mid2; - screen_mouse_pos_mid2.setX(screen_mouse_pos.x()); - screen_mouse_pos_mid2.setY(first.y()); - - qreal dx = screen_mouse_pos.x() - first.x(); - qreal dy = screen_mouse_pos.y() - first.y(); - - if (((dx > 0) && (dy > 0)) || ((dx < 0) && (dy < 0))) { - m_nextVertexImagePos_mid1 = from_screen.map(screen_mouse_pos_mid1); - m_nextVertexImagePos_mid2 = from_screen.map(screen_mouse_pos_mid2); - } else { - m_nextVertexImagePos_mid2 = from_screen.map(screen_mouse_pos_mid1); - m_nextVertexImagePos_mid1 = from_screen.map(screen_mouse_pos_mid2); - } + if (((dx > 0) && (dy > 0)) || ((dx < 0) && (dy < 0))) { + m_nextVertexImagePos_mid1 = from_screen.map(screen_mouse_pos_mid1); + m_nextVertexImagePos_mid2 = from_screen.map(screen_mouse_pos_mid2); + } else { + m_nextVertexImagePos_mid2 = from_screen.map(screen_mouse_pos_mid1); + m_nextVertexImagePos_mid1 = from_screen.map(screen_mouse_pos_mid2); } + } - if (m_lassoMode) { - Proximity min_distance = Proximity::fromDist(10); - if ((Proximity(last, screen_mouse_pos) > min_distance) && (Proximity(first, screen_mouse_pos) > min_distance)) { - m_ptrSpline->appendVertex(m_nextVertexImagePos); - } + if (m_lassoMode) { + Proximity min_distance = Proximity::fromDist(10); + if ((Proximity(last, screen_mouse_pos) > min_distance) && (Proximity(first, screen_mouse_pos) > min_distance)) { + m_spline->appendVertex(m_nextVertexImagePos); } + } - m_rContext.imageView().update(); + m_context.imageView().update(); } // ZoneCreationInteraction::onMouseMoveEvent void ZoneCreationInteraction::updateStatusTip() { - QString tip; + QString tip; - if (m_rectangularZoneType) { - if (m_nextVertexImagePos != m_ptrSpline->firstVertex()->point()) { - tip = tr("Click to finish this rectangular zone. ESC to cancel."); - } + if (m_rectangularZoneType) { + if (m_nextVertexImagePos != m_spline->firstVertex()->point()) { + tip = tr("Click to finish this rectangular zone. ESC to cancel."); + } + } else { + if (m_spline->hasAtLeastSegments(2)) { + if (m_nextVertexImagePos == m_spline->firstVertex()->point()) { + tip = tr("Click to finish this zone. ESC to cancel."); + } else { + tip = tr("Connect first and last points to finish this zone. ESC to cancel."); + } } else { - if (m_ptrSpline->hasAtLeastSegments(2)) { - if (m_nextVertexImagePos == m_ptrSpline->firstVertex()->point()) { - tip = tr("Click to finish this zone. ESC to cancel."); - } else { - tip = tr("Connect first and last points to finish this zone. ESC to cancel."); - } - } else { - tip = tr("Hold Ctrl to create a rectangular zone, Alt+LMB to switch to lasso mode. ESC to cancel."); - } + tip = tr("Hold Ctrl to create a rectangular zone or Shift+Alt+LMB to use lasso mode. ESC to cancel."); } + } + + m_interaction.setInteractionStatusTip(tip); +} - m_interaction.setInteractionStatusTip(tip); +bool ZoneCreationInteraction::isDragHandlerPermitted(const InteractionState& interaction) const { + return !m_lassoMode; } diff --git a/zones/ZoneCreationInteraction.h b/zones/ZoneCreationInteraction.h index 18e3e06a5..9f65cd0c6 100644 --- a/zones/ZoneCreationInteraction.h +++ b/zones/ZoneCreationInteraction.h @@ -19,73 +19,74 @@ #ifndef ZONE_CREATION_INTERACTION_H_ #define ZONE_CREATION_INTERACTION_H_ -#include "InteractionHandler.h" -#include "InteractionState.h" +#include +#include +#include +#include "BasicSplineVisualizer.h" #include "DragHandler.h" #include "DragWatcher.h" -#include "ZoomHandler.h" -#include "BasicSplineVisualizer.h" #include "EditableSpline.h" -#include -#include -#include +#include "InteractionHandler.h" +#include "InteractionState.h" +#include "ZoomHandler.h" class ZoneInteractionContext; class ZoneCreationInteraction : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ZoneCreationInteraction) -public: - ZoneCreationInteraction(ZoneInteractionContext& context, InteractionState& interaction); - -protected: - ZoneInteractionContext& context() { - return m_rContext; - } - - void onPaint(QPainter& painter, const InteractionState& interaction) override; - - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; - - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - -private: - void updateStatusTip(); - - ZoneInteractionContext& m_rContext; - - /** - * We have our own drag handler even though there is already a global one - * for the purpose of being able to monitor it with DragWatcher. Because - * we capture a state in the constructor, it's guaranteed the global - * drag handler will not be functioning until we release the state. - */ - DragHandler m_dragHandler; - - /** - * This must go after m_dragHandler, otherwise DragHandler's destructor - * will try to destroy this object. - */ - DragWatcher m_dragWatcher; - - /** - * Because we hold an interaction state from constructor to destructor, - * we have to have our own zoom handler with explicit interaction permission - * if we want zoom to work. - */ - ZoomHandler m_zoomHandler; - - BasicSplineVisualizer m_visualizer; - InteractionState::Captor m_interaction; - EditableSpline::Ptr m_ptrSpline; - QPointF m_nextVertexImagePos; - bool m_rectangularZoneType; - QPointF m_nextVertexImagePos_mid1; - QPointF m_nextVertexImagePos_mid2; - bool m_lassoMode; + Q_DECLARE_TR_FUNCTIONS(ZoneCreationInteraction) + public: + ZoneCreationInteraction(ZoneInteractionContext& context, InteractionState& interaction); + + protected: + ZoneInteractionContext& context() { return m_context; } + + void onPaint(QPainter& painter, const InteractionState& interaction) override; + + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + + private: + void updateStatusTip(); + + bool isDragHandlerPermitted(const InteractionState& interaction) const; + + ZoneInteractionContext& m_context; + + /** + * We have our own drag handler even though there is already a global one + * for the purpose of being able to monitor it with DragWatcher. Because + * we capture a state in the constructor, it's guaranteed the global + * drag handler will not be functioning until we release the state. + */ + DragHandler m_dragHandler; + + /** + * This must go after m_dragHandler, otherwise DragHandler's destructor + * will try to destroy this object. + */ + DragWatcher m_dragWatcher; + + /** + * Because we hold an interaction state from constructor to destructor, + * we have to have our own zoom handler with explicit interaction permission + * if we want zoom to work. + */ + ZoomHandler m_zoomHandler; + + BasicSplineVisualizer m_visualizer; + InteractionState::Captor m_interaction; + EditableSpline::Ptr m_spline; + QPointF m_nextVertexImagePos; + bool m_rectangularZoneType; + QPointF m_nextVertexImagePos_mid1; + QPointF m_nextVertexImagePos_mid2; + bool m_lassoMode; + Qt::KeyboardModifiers m_lassoModeModifiers; }; diff --git a/zones/ZoneDefaultInteraction.cpp b/zones/ZoneDefaultInteraction.cpp index 557067e1e..f1f3eb83d 100644 --- a/zones/ZoneDefaultInteraction.cpp +++ b/zones/ZoneDefaultInteraction.cpp @@ -17,273 +17,273 @@ */ #include "ZoneDefaultInteraction.h" -#include "ZoneInteractionContext.h" +#include +#include #include "ImageViewBase.h" #include "SerializableSpline.h" -#include -#include +#include "ZoneInteractionContext.h" ZoneDefaultInteraction::ZoneDefaultInteraction(ZoneInteractionContext& context) - : m_rContext(context), m_dragHandler(context.imageView()), m_dragWatcher(m_dragHandler) { - makeLastFollower(m_dragHandler); - m_dragHandler.makeFirstFollower(m_dragWatcher); - - m_vertexProximity.setProximityStatusTip(tr("Drag the vertex. Hold Ctrl to make the vertex angle right.")); - m_segmentProximity.setProximityStatusTip(tr("Click to create a new vertex here.")); - m_zoneAreaProximity.setProximityStatusTip(tr("Right click to edit zone properties. Hold Shift to drag the zone or " - "Shift+Ctrl to copy. Press Del to delete this zone.")); - m_zoneAreaDragProximity.setProximityStatusTip("Hold left mouse button to drag the zone."); - m_zoneAreaDragProximity.setProximityCursor(Qt::DragMoveCursor); - m_zoneAreaDragCopyProximity.setProximityStatusTip("Hold left mouse button to copy and drag the zone."); - m_zoneAreaDragCopyProximity.setProximityCursor(Qt::DragCopyCursor); - m_rContext.imageView().interactionState().setDefaultStatusTip( - tr("Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone.")); + : m_context(context), m_dragHandler(context.imageView()), m_dragWatcher(m_dragHandler) { + makeLastFollower(m_dragHandler); + m_dragHandler.makeFirstFollower(m_dragWatcher); + + m_vertexProximity.setProximityStatusTip(tr("Drag the vertex. Hold Ctrl to make the vertex angle right.")); + m_segmentProximity.setProximityStatusTip(tr("Click to create a new vertex here.")); + m_zoneAreaProximity.setProximityStatusTip( + tr("Right click to edit zone properties. Hold Shift to drag the zone or " + "Shift+Ctrl to copy. Press Del to delete this zone.")); + m_zoneAreaDragProximity.setProximityStatusTip("Hold left mouse button to drag the zone."); + m_zoneAreaDragProximity.setProximityCursor(Qt::DragMoveCursor); + m_zoneAreaDragCopyProximity.setProximityStatusTip("Hold left mouse button to copy and drag the zone."); + m_zoneAreaDragCopyProximity.setProximityCursor(Qt::DragCopyCursor); + m_context.imageView().interactionState().setDefaultStatusTip( + tr("Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone.")); } void ZoneDefaultInteraction::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHint(QPainter::Antialiasing); - - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - - for (const EditableZoneSet::Zone& zone : m_rContext.zones()) { - const EditableSpline::Ptr& spline = zone.spline(); - m_visualizer.prepareForSpline(painter, spline); - QPolygonF points; - - if (!interaction.captured() && interaction.proximityLeader(m_vertexProximity) - && (spline == m_ptrNearestVertexSpline)) { - SplineVertex::Ptr vertex(m_ptrNearestVertex->next(SplineVertex::LOOP)); - for (; vertex != m_ptrNearestVertex; vertex = vertex->next(SplineVertex::LOOP)) { - points.push_back(to_screen.map(vertex->point())); - } - painter.drawPolyline(points); - } else if (!interaction.captured() && interaction.proximityLeader(m_segmentProximity) - && (spline == m_ptrNearestSegmentSpline)) { - SplineVertex::Ptr vertex(m_nearestSegment.prev); - do { - vertex = vertex->next(SplineVertex::LOOP); - points.push_back(to_screen.map(vertex->point())); - } while (vertex != m_nearestSegment.prev); - painter.drawPolyline(points); - } else { - m_visualizer.drawSpline(painter, to_screen, spline); - } - } - - if (interaction.proximityLeader(m_vertexProximity)) { - // Draw the two adjacent edges in gradient red-to-orange. - QLinearGradient gradient; // From inactive to active point. - gradient.setColorAt(0.0, m_visualizer.solidColor()); - gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); - - QPen pen(painter.pen()); - - const QPointF prev(to_screen.map(m_ptrNearestVertex->prev(SplineVertex::LOOP)->point())); - const QPointF pt(to_screen.map(m_ptrNearestVertex->point())); - const QPointF next(to_screen.map(m_ptrNearestVertex->next(SplineVertex::LOOP)->point())); - - gradient.setStart(prev); - gradient.setFinalStop(pt); - pen.setBrush(gradient); - painter.setPen(pen); - painter.drawLine(prev, pt); - - gradient.setStart(next); - pen.setBrush(gradient); - painter.setPen(pen); - painter.drawLine(next, pt); - - // Visualize the highlighted vertex. - const QPointF screen_vertex(to_screen.map(m_ptrNearestVertex->point())); - m_visualizer.drawVertex(painter, screen_vertex, m_visualizer.highlightBrightColor()); - } else if (interaction.proximityLeader(m_segmentProximity)) { - const QLineF line(to_screen.map(m_nearestSegment.toLine())); - // Draw the highglighed edge in orange. - QPen pen(painter.pen()); - pen.setColor(m_visualizer.highlightDarkColor()); - painter.setPen(pen); - painter.drawLine(line); - - m_visualizer.drawVertex(painter, m_screenPointOnSegment, m_visualizer.highlightBrightColor()); - } else if (!interaction.captured()) { - m_visualizer.drawVertex(painter, m_screenMousePos, m_visualizer.solidColor()); + painter.setWorldMatrixEnabled(false); + painter.setRenderHint(QPainter::Antialiasing); + + const QTransform to_screen(m_context.imageView().imageToWidget()); + + for (const EditableZoneSet::Zone& zone : m_context.zones()) { + const EditableSpline::Ptr& spline = zone.spline(); + m_visualizer.prepareForSpline(painter, spline); + QPolygonF points; + + if (!interaction.captured() && interaction.proximityLeader(m_vertexProximity) + && (spline == m_nearestVertexSpline)) { + SplineVertex::Ptr vertex(m_nearestVertex->next(SplineVertex::LOOP)); + for (; vertex != m_nearestVertex; vertex = vertex->next(SplineVertex::LOOP)) { + points.push_back(to_screen.map(vertex->point())); + } + painter.drawPolyline(points); + } else if (!interaction.captured() && interaction.proximityLeader(m_segmentProximity) + && (spline == m_nearestSegmentSpline)) { + SplineVertex::Ptr vertex(m_nearestSegment.prev); + do { + vertex = vertex->next(SplineVertex::LOOP); + points.push_back(to_screen.map(vertex->point())); + } while (vertex != m_nearestSegment.prev); + painter.drawPolyline(points); + } else { + m_visualizer.drawSpline(painter, to_screen, spline); } + } + + if (interaction.proximityLeader(m_vertexProximity)) { + // Draw the two adjacent edges in gradient red-to-orange. + QLinearGradient gradient; // From inactive to active point. + gradient.setColorAt(0.0, m_visualizer.solidColor()); + gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); + + QPen pen(painter.pen()); + + const QPointF prev(to_screen.map(m_nearestVertex->prev(SplineVertex::LOOP)->point())); + const QPointF pt(to_screen.map(m_nearestVertex->point())); + const QPointF next(to_screen.map(m_nearestVertex->next(SplineVertex::LOOP)->point())); + + gradient.setStart(prev); + gradient.setFinalStop(pt); + pen.setBrush(gradient); + painter.setPen(pen); + painter.drawLine(prev, pt); + + gradient.setStart(next); + pen.setBrush(gradient); + painter.setPen(pen); + painter.drawLine(next, pt); + + // Visualize the highlighted vertex. + const QPointF screen_vertex(to_screen.map(m_nearestVertex->point())); + m_visualizer.drawVertex(painter, screen_vertex, m_visualizer.highlightBrightColor()); + } else if (interaction.proximityLeader(m_segmentProximity)) { + const QLineF line(to_screen.map(m_nearestSegment.toLine())); + // Draw the highglighed edge in orange. + QPen pen(painter.pen()); + pen.setColor(m_visualizer.highlightDarkColor()); + painter.setPen(pen); + painter.drawLine(line); + + m_visualizer.drawVertex(painter, m_screenPointOnSegment, m_visualizer.highlightBrightColor()); + } else if (!interaction.captured()) { + m_visualizer.drawVertex(painter, m_screenMousePos, m_visualizer.solidColor()); + } } // ZoneDefaultInteraction::onPaint void ZoneDefaultInteraction::onProximityUpdate(const QPointF& mouse_pos, InteractionState& interaction) { - m_screenMousePos = mouse_pos; - - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - const QPointF image_mouse_pos(from_screen.map(mouse_pos)); - - m_ptrNearestVertex.reset(); - m_ptrNearestVertexSpline.reset(); - m_nearestSegment = SplineSegment(); - m_ptrNearestSegmentSpline.reset(); - m_ptrUnderCursorSpline.reset(); - - Proximity best_vertex_proximity; - Proximity best_segment_proximity; - - bool has_zone_under_mouse = false; - - for (const EditableZoneSet::Zone& zone : m_rContext.zones()) { - const EditableSpline::Ptr& spline = zone.spline(); - - { - QPainterPath path; - path.setFillRule(Qt::WindingFill); - path.addPolygon(spline->toPolygon()); - if (path.contains(image_mouse_pos)) { - m_ptrUnderCursorSpline = spline; - - has_zone_under_mouse = true; - } - } - - // Process vertices. - for (SplineVertex::Ptr vert(spline->firstVertex()); vert; vert = vert->next(SplineVertex::NO_LOOP)) { - const Proximity proximity(mouse_pos, to_screen.map(vert->point())); - if (proximity < best_vertex_proximity) { - m_ptrNearestVertex = vert; - m_ptrNearestVertexSpline = spline; - best_vertex_proximity = proximity; - } - } - // Process segments. - for (EditableSpline::SegmentIterator it(*spline); it.hasNext();) { - const SplineSegment segment(it.next()); - const QLineF line(to_screen.map(segment.toLine())); - QPointF point_on_segment; - const Proximity proximity(Proximity::pointAndLineSegment(mouse_pos, line, &point_on_segment)); - if (proximity < best_segment_proximity) { - m_nearestSegment = segment; - m_ptrNearestSegmentSpline = spline; - best_segment_proximity = proximity; - m_screenPointOnSegment = point_on_segment; - } - } - } + m_screenMousePos = mouse_pos; + + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QTransform from_screen(m_context.imageView().widgetToImage()); + const QPointF image_mouse_pos(from_screen.map(mouse_pos)); + + m_nearestVertex.reset(); + m_nearestVertexSpline.reset(); + m_nearestSegment = SplineSegment(); + m_nearestSegmentSpline.reset(); + m_underCursorSpline.reset(); - interaction.updateProximity(m_vertexProximity, best_vertex_proximity, 2); - interaction.updateProximity(m_segmentProximity, best_segment_proximity, 1); - - if (has_zone_under_mouse) { - const Proximity zone_area_proximity(std::min(best_vertex_proximity, best_segment_proximity)); - interaction.updateProximity(m_zoneAreaProximity, zone_area_proximity, -1, zone_area_proximity); - if (m_activeKeyboardModifiers == Qt::ShiftModifier) { - interaction.updateProximity(m_zoneAreaDragProximity, Proximity::fromSqDist(0), 0); - } else if (m_activeKeyboardModifiers == (Qt::ShiftModifier | Qt::ControlModifier)) { - interaction.updateProximity(m_zoneAreaDragCopyProximity, Proximity::fromSqDist(0), 0); - } + Proximity best_vertex_proximity; + Proximity best_segment_proximity; + + bool has_zone_under_mouse = false; + + for (const EditableZoneSet::Zone& zone : m_context.zones()) { + const EditableSpline::Ptr& spline = zone.spline(); + + { + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addPolygon(spline->toPolygon()); + if (path.contains(image_mouse_pos)) { + m_underCursorSpline = spline; + + has_zone_under_mouse = true; + } } -} // ZoneDefaultInteraction::onProximityUpdate -void ZoneDefaultInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { - if (interaction.captured()) { - return; + // Process vertices. + for (SplineVertex::Ptr vert(spline->firstVertex()); vert; vert = vert->next(SplineVertex::NO_LOOP)) { + const Proximity proximity(mouse_pos, to_screen.map(vert->point())); + if (proximity < best_vertex_proximity) { + m_nearestVertex = vert; + m_nearestVertexSpline = spline; + best_vertex_proximity = proximity; + } } - if (event->button() != Qt::LeftButton) { - return; + // Process segments. + for (EditableSpline::SegmentIterator it(*spline); it.hasNext();) { + const SplineSegment segment(it.next()); + const QLineF line(to_screen.map(segment.toLine())); + QPointF point_on_segment; + const Proximity proximity(Proximity::pointAndLineSegment(mouse_pos, line, &point_on_segment)); + if (proximity < best_segment_proximity) { + m_nearestSegment = segment; + m_nearestSegmentSpline = spline; + best_segment_proximity = proximity; + m_screenPointOnSegment = point_on_segment; + } } - - if (interaction.proximityLeader(m_vertexProximity)) { - makePeerPreceeder( - *m_rContext.createVertexDragInteraction(interaction, m_ptrNearestVertexSpline, m_ptrNearestVertex)); - delete this; - event->accept(); - } else if (interaction.proximityLeader(m_segmentProximity)) { - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - SplineVertex::Ptr vertex(m_nearestSegment.splitAt(from_screen.map(m_screenPointOnSegment))); - makePeerPreceeder(*m_rContext.createVertexDragInteraction(interaction, m_ptrNearestSegmentSpline, vertex)); - delete this; - event->accept(); - } else if (interaction.proximityLeader(m_zoneAreaDragProximity)) { - makePeerPreceeder(*m_rContext.createZoneDragInteraction(interaction, m_ptrUnderCursorSpline)); - delete this; - event->accept(); - } else if (interaction.proximityLeader(m_zoneAreaDragCopyProximity)) { - auto new_spline = make_intrusive(SerializableSpline(*m_ptrUnderCursorSpline)); - m_rContext.zones().addZone(new_spline, *m_rContext.zones().propertiesFor(m_ptrUnderCursorSpline)); - makePeerPreceeder(*m_rContext.createZoneDragInteraction(interaction, new_spline)); - delete this; - event->accept(); + } + + interaction.updateProximity(m_vertexProximity, best_vertex_proximity, 2); + interaction.updateProximity(m_segmentProximity, best_segment_proximity, 1); + + if (has_zone_under_mouse) { + const Proximity zone_area_proximity(std::min(best_vertex_proximity, best_segment_proximity)); + interaction.updateProximity(m_zoneAreaProximity, zone_area_proximity, -1, zone_area_proximity); + if (m_activeKeyboardModifiers == Qt::ShiftModifier) { + interaction.updateProximity(m_zoneAreaDragProximity, Proximity::fromSqDist(0), 0); + } else if (m_activeKeyboardModifiers == (Qt::ShiftModifier | Qt::ControlModifier)) { + interaction.updateProximity(m_zoneAreaDragCopyProximity, Proximity::fromSqDist(0), 0); } + } +} // ZoneDefaultInteraction::onProximityUpdate + +void ZoneDefaultInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { + if (interaction.captured()) { + return; + } + if (event->button() != Qt::LeftButton) { + return; + } + + if (interaction.proximityLeader(m_vertexProximity)) { + makePeerPreceeder(*m_context.createVertexDragInteraction(interaction, m_nearestVertexSpline, m_nearestVertex)); + delete this; + event->accept(); + } else if (interaction.proximityLeader(m_segmentProximity)) { + const QTransform from_screen(m_context.imageView().widgetToImage()); + SplineVertex::Ptr vertex(m_nearestSegment.splitAt(from_screen.map(m_screenPointOnSegment))); + makePeerPreceeder(*m_context.createVertexDragInteraction(interaction, m_nearestSegmentSpline, vertex)); + delete this; + event->accept(); + } else if (interaction.proximityLeader(m_zoneAreaDragProximity)) { + makePeerPreceeder(*m_context.createZoneDragInteraction(interaction, m_underCursorSpline)); + delete this; + event->accept(); + } else if (interaction.proximityLeader(m_zoneAreaDragCopyProximity)) { + auto new_spline = make_intrusive(SerializableSpline(*m_underCursorSpline)); + m_context.zones().addZone(new_spline, *m_context.zones().propertiesFor(m_underCursorSpline)); + makePeerPreceeder(*m_context.createZoneDragInteraction(interaction, new_spline)); + delete this; + event->accept(); + } } void ZoneDefaultInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() != Qt::LeftButton) { - return; - } - - if (!interaction.captured()) { - return; - } - if (!m_dragHandler.isActive() || m_dragWatcher.haveSignificantDrag()) { - return; - } + if (event->button() != Qt::LeftButton) { + return; + } - if (m_activeKeyboardModifiers == (Qt::ControlModifier | Qt::AltModifier)) { - const QTransform from_screen(m_rContext.imageView().widgetToImage()); + if (!interaction.captured()) { + return; + } + if (!m_dragHandler.isActive() || m_dragWatcher.haveSignificantDrag()) { + return; + } - EditableZoneSet::const_iterator latest_zone = --m_rContext.zones().end(); - if (latest_zone != m_rContext.zones().end()) { - SerializableSpline serializable_spline(*(*latest_zone).spline()); + if (m_activeKeyboardModifiers == (Qt::ControlModifier | Qt::AltModifier)) { + const QTransform from_screen(m_context.imageView().widgetToImage()); - const QPointF old_center = serializable_spline.toPolygon().boundingRect().center(); - const QPointF new_center = from_screen.map(event->pos() + QPointF(0.5, 0.5)); - const QPointF shift = new_center - old_center; + EditableZoneSet::const_iterator latest_zone = --m_context.zones().end(); + if (latest_zone != m_context.zones().end()) { + SerializableSpline serializable_spline(*(*latest_zone).spline()); - serializable_spline = serializable_spline.transformed(QTransform().translate(shift.x(), shift.y())); + const QPointF old_center = serializable_spline.toPolygon().boundingRect().center(); + const QPointF new_center = from_screen.map(event->pos() + QPointF(0.5, 0.5)); + const QPointF shift = new_center - old_center; - auto new_spline = make_intrusive(serializable_spline); - m_rContext.zones().addZone(new_spline, *(*latest_zone).properties()); - m_rContext.zones().commit(); - } + serializable_spline = serializable_spline.transformed(QTransform().translate(shift.x(), shift.y())); - m_rContext.imageView().update(); - return; + auto new_spline = make_intrusive(serializable_spline); + m_context.zones().addZone(new_spline, *(*latest_zone).properties()); + m_context.zones().commit(); } - makePeerPreceeder(*m_rContext.createZoneCreationInteraction(interaction)); - delete this; - event->accept(); + m_context.imageView().update(); + return; + } + + makePeerPreceeder(*m_context.createZoneCreationInteraction(interaction)); + delete this; + event->accept(); } void ZoneDefaultInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - const QTransform to_screen(m_rContext.imageView().imageToWidget()); + const QTransform to_screen(m_context.imageView().imageToWidget()); - m_screenMousePos = to_screen.map(event->pos() + QPointF(0.5, 0.5)); - m_rContext.imageView().update(); + m_screenMousePos = to_screen.map(event->pos() + QPointF(0.5, 0.5)); + m_context.imageView().update(); } void ZoneDefaultInteraction::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) { - m_activeKeyboardModifiers = event->modifiers(); + m_activeKeyboardModifiers = event->modifiers(); } void ZoneDefaultInteraction::onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) { - m_activeKeyboardModifiers = event->modifiers(); + m_activeKeyboardModifiers = event->modifiers(); - if (event->key() == Qt::Key_Delete) { - if (m_ptrUnderCursorSpline != nullptr) { - m_rContext.zones().removeZone(m_ptrUnderCursorSpline); - m_rContext.zones().commit(); - } - - m_rContext.imageView().update(); + if (event->key() == Qt::Key_Delete) { + if (m_underCursorSpline != nullptr) { + m_context.zones().removeZone(m_underCursorSpline); + m_context.zones().commit(); } + + m_context.imageView().update(); + } } void ZoneDefaultInteraction::onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { - event->accept(); + event->accept(); - InteractionHandler* cm_interaction = m_rContext.createContextMenuInteraction(interaction); - if (!cm_interaction) { - return; - } + InteractionHandler* cm_interaction = m_context.createContextMenuInteraction(interaction); + if (!cm_interaction) { + return; + } - makePeerPreceeder(*cm_interaction); - delete this; + makePeerPreceeder(*cm_interaction); + delete this; } diff --git a/zones/ZoneDefaultInteraction.h b/zones/ZoneDefaultInteraction.h index f3d4909c5..b12f10cad 100644 --- a/zones/ZoneDefaultInteraction.h +++ b/zones/ZoneDefaultInteraction.h @@ -19,79 +19,77 @@ #ifndef ZONE_DEFAULT_INTERACTION_H_ #define ZONE_DEFAULT_INTERACTION_H_ -#include "InteractionHandler.h" -#include "InteractionState.h" +#include +#include +#include "BasicSplineVisualizer.h" #include "DragHandler.h" #include "DragWatcher.h" -#include "BasicSplineVisualizer.h" #include "EditableSpline.h" -#include "SplineVertex.h" +#include "InteractionHandler.h" +#include "InteractionState.h" #include "SplineSegment.h" -#include -#include +#include "SplineVertex.h" class ZoneInteractionContext; class ZoneDefaultInteraction : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ZoneDefaultInteraction) -public: - explicit ZoneDefaultInteraction(ZoneInteractionContext& context); + Q_DECLARE_TR_FUNCTIONS(ZoneDefaultInteraction) + public: + explicit ZoneDefaultInteraction(ZoneInteractionContext& context); -protected: - ZoneInteractionContext& context() { - return m_rContext; - } + protected: + ZoneInteractionContext& context() { return m_context; } - void onPaint(QPainter& painter, const InteractionState& interaction) override; + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onProximityUpdate(const QPointF& mouse_pos, InteractionState& interaction) override; + void onProximityUpdate(const QPointF& mouse_pos, InteractionState& interaction) override; - void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMousePressEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) override; - void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) override; + void onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) override; - void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) override; + void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) override; -private: - ZoneInteractionContext& m_rContext; - BasicSplineVisualizer m_visualizer; - InteractionState::Captor m_vertexProximity; - InteractionState::Captor m_segmentProximity; - InteractionState::Captor m_zoneAreaProximity; - InteractionState::Captor m_zoneAreaDragProximity; - InteractionState::Captor m_zoneAreaDragCopyProximity; - QPointF m_screenMousePos; - Qt::KeyboardModifiers m_activeKeyboardModifiers; + private: + ZoneInteractionContext& m_context; + BasicSplineVisualizer m_visualizer; + InteractionState::Captor m_vertexProximity; + InteractionState::Captor m_segmentProximity; + InteractionState::Captor m_zoneAreaProximity; + InteractionState::Captor m_zoneAreaDragProximity; + InteractionState::Captor m_zoneAreaDragCopyProximity; + QPointF m_screenMousePos; + Qt::KeyboardModifiers m_activeKeyboardModifiers; - /** - * We want our own drag handler, to be able to monitor it - * and decide if we should go into zone creation state - * after the left mouse button is released. - */ - DragHandler m_dragHandler; + /** + * We want our own drag handler, to be able to monitor it + * and decide if we should go into zone creation state + * after the left mouse button is released. + */ + DragHandler m_dragHandler; - /** - * Because we hold an interaction state from constructor to destructor, - * we have to have our own zoom handler with explicit interaction permission - * if we want zoom to work. - */ - DragWatcher m_dragWatcher; + /** + * Because we hold an interaction state from constructor to destructor, + * we have to have our own zoom handler with explicit interaction permission + * if we want zoom to work. + */ + DragWatcher m_dragWatcher; - // These are valid if m_vertexProximity is the proximity leader. - SplineVertex::Ptr m_ptrNearestVertex; - EditableSpline::Ptr m_ptrNearestVertexSpline; + // These are valid if m_vertexProximity is the proximity leader. + SplineVertex::Ptr m_nearestVertex; + EditableSpline::Ptr m_nearestVertexSpline; - // These are valid if m_segmentProximity is the proximity leader. - SplineSegment m_nearestSegment; - EditableSpline::Ptr m_ptrNearestSegmentSpline; - QPointF m_screenPointOnSegment; - EditableSpline::Ptr m_ptrUnderCursorSpline; + // These are valid if m_segmentProximity is the proximity leader. + SplineSegment m_nearestSegment; + EditableSpline::Ptr m_nearestSegmentSpline; + QPointF m_screenPointOnSegment; + EditableSpline::Ptr m_underCursorSpline; }; diff --git a/zones/ZoneDragInteraction.cpp b/zones/ZoneDragInteraction.cpp index 71b44d52b..55c3672a3 100644 --- a/zones/ZoneDragInteraction.cpp +++ b/zones/ZoneDragInteraction.cpp @@ -17,71 +17,71 @@ */ #include "ZoneDragInteraction.h" -#include "ZoneInteractionContext.h" -#include "ImageViewBase.h" #include #include +#include "ImageViewBase.h" +#include "ZoneInteractionContext.h" ZoneDragInteraction::ZoneDragInteraction(ZoneInteractionContext& context, InteractionState& interaction, const EditableSpline::Ptr& spline) - : m_rContext(context), m_ptrSpline(spline) { - m_initialMousePos = m_rContext.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5); + : m_context(context), m_spline(spline) { + m_initialMousePos = m_context.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5); - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - m_initialSplineFirstVertexPos = to_screen.map(spline->firstVertex()->point()); + const QTransform to_screen(m_context.imageView().imageToWidget()); + m_initialSplineFirstVertexPos = to_screen.map(spline->firstVertex()->point()); - m_interaction.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); - m_interaction.setInteractionCursor(Qt::DragMoveCursor); - interaction.capture(m_interaction); + m_interaction.setInteractionStatusTip(tr("Release left mouse button to finish dragging.")); + m_interaction.setInteractionCursor(Qt::DragMoveCursor); + interaction.capture(m_interaction); } void ZoneDragInteraction::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHint(QPainter::Antialiasing); - - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - - for (const EditableZoneSet::Zone& zone : m_rContext.zones()) { - const EditableSpline::Ptr& spline = zone.spline(); - - if (spline != m_ptrSpline) { - // Draw the whole spline in solid color. - m_visualizer.drawSpline(painter, to_screen, spline); - continue; - } - // Draw the solid part of the spline. - QPolygonF points; - for (SplineVertex::Ptr vertex(m_ptrSpline->firstVertex()); vertex != m_ptrSpline->firstVertex(); - vertex = vertex->next(SplineVertex::LOOP)) { - points.push_back(to_screen.map(vertex->point())); - } - - m_visualizer.prepareForSpline(painter, spline); - painter.drawPolyline(points); - - m_visualizer.drawSpline(painter, to_screen, spline); + painter.setWorldMatrixEnabled(false); + painter.setRenderHint(QPainter::Antialiasing); + + const QTransform to_screen(m_context.imageView().imageToWidget()); + + for (const EditableZoneSet::Zone& zone : m_context.zones()) { + const EditableSpline::Ptr& spline = zone.spline(); + + if (spline != m_spline) { + // Draw the whole spline in solid color. + m_visualizer.drawSpline(painter, to_screen, spline); + continue; + } + // Draw the solid part of the spline. + QPolygonF points; + for (SplineVertex::Ptr vertex(m_spline->firstVertex()); vertex != m_spline->firstVertex(); + vertex = vertex->next(SplineVertex::LOOP)) { + points.push_back(to_screen.map(vertex->point())); } + + m_visualizer.prepareForSpline(painter, spline); + painter.drawPolyline(points); + + m_visualizer.drawSpline(painter, to_screen, spline); + } } // ZoneDragInteraction::onPaint void ZoneDragInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() == Qt::LeftButton) { - m_rContext.zones().commit(); - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - delete this; - } + if (event->button() == Qt::LeftButton) { + m_context.zones().commit(); + makePeerPreceeder(*m_context.createDefaultInteraction()); + delete this; + } } void ZoneDragInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - const QPointF shift = (event->pos() + QPointF(0.5, 0.5)) - m_initialMousePos; - const QPointF splineShift = to_screen.map(m_ptrSpline->firstVertex()->point()) - m_initialSplineFirstVertexPos; + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QTransform from_screen(m_context.imageView().widgetToImage()); + const QPointF shift = (event->pos() + QPointF(0.5, 0.5)) - m_initialMousePos; + const QPointF splineShift = to_screen.map(m_spline->firstVertex()->point()) - m_initialSplineFirstVertexPos; - SplineVertex::Ptr vertex(m_ptrSpline->firstVertex()); - do { - vertex->setPoint(from_screen.map(to_screen.map(vertex->point()) + shift - splineShift)); - } while (vertex = vertex->next(SplineVertex::LOOP), vertex != m_ptrSpline->firstVertex()); + SplineVertex::Ptr vertex(m_spline->firstVertex()); + do { + vertex->setPoint(from_screen.map(to_screen.map(vertex->point()) + shift - splineShift)); + } while (vertex = vertex->next(SplineVertex::LOOP), vertex != m_spline->firstVertex()); - m_rContext.imageView().update(); + m_context.imageView().update(); } // ZoneDragInteraction::onMouseMoveEvent diff --git a/zones/ZoneDragInteraction.h b/zones/ZoneDragInteraction.h index 90fa6bc1e..632a2786b 100644 --- a/zones/ZoneDragInteraction.h +++ b/zones/ZoneDragInteraction.h @@ -2,35 +2,35 @@ #ifndef SCANTAILOR_ZONEDRAGINTERACTION_H #define SCANTAILOR_ZONEDRAGINTERACTION_H +#include +#include #include "BasicSplineVisualizer.h" #include "EditableSpline.h" #include "InteractionHandler.h" #include "InteractionState.h" -#include -#include class ZoneInteractionContext; class ZoneDragInteraction : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ZoneDragInteraction) -public: - ZoneDragInteraction(ZoneInteractionContext& context, - InteractionState& interaction, - const EditableSpline::Ptr& spline); + Q_DECLARE_TR_FUNCTIONS(ZoneDragInteraction) + public: + ZoneDragInteraction(ZoneInteractionContext& context, + InteractionState& interaction, + const EditableSpline::Ptr& spline); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; - ZoneInteractionContext& m_rContext; - EditableSpline::Ptr m_ptrSpline; - QPointF m_initialMousePos; - QPointF m_initialSplineFirstVertexPos; - InteractionState::Captor m_interaction; - BasicSplineVisualizer m_visualizer; + ZoneInteractionContext& m_context; + EditableSpline::Ptr m_spline; + QPointF m_initialMousePos; + QPointF m_initialSplineFirstVertexPos; + InteractionState::Captor m_interaction; + BasicSplineVisualizer m_visualizer; }; diff --git a/zones/ZoneInteractionContext.cpp b/zones/ZoneInteractionContext.cpp index 77b893780..f53b95c09 100644 --- a/zones/ZoneInteractionContext.cpp +++ b/zones/ZoneInteractionContext.cpp @@ -17,49 +17,46 @@ */ #include "ZoneInteractionContext.h" -#include "ZoneDefaultInteraction.h" -#include "ZoneCreationInteraction.h" -#include "ZoneVertexDragInteraction.h" +#include #include "ZoneContextMenuInteraction.h" +#include "ZoneCreationInteraction.h" +#include "ZoneDefaultInteraction.h" #include "ZoneDragInteraction.h" -#include +#include "ZoneVertexDragInteraction.h" ZoneInteractionContext::ZoneInteractionContext(ImageViewBase& image_view, EditableZoneSet& zones) - : m_rImageView(image_view), - m_rZones(zones), - m_defaultInteractionCreator(boost::bind(&ZoneInteractionContext::createStdDefaultInteraction, this)), - m_zoneCreationInteractionCreator( - boost::bind(&ZoneInteractionContext::createStdZoneCreationInteraction, this, _1)), - m_vertexDragInteractionCreator( - boost::bind(&ZoneInteractionContext::createStdVertexDragInteraction, this, _1, _2, _3)), - m_zoneDragInteractionCreator( - boost::bind(&ZoneInteractionContext::createStdZoneDragInteraction, this, _1, _2)), - m_contextMenuInteractionCreator( - boost::bind(&ZoneInteractionContext::createStdContextMenuInteraction, this, _1)), - m_showPropertiesCommand(&ZoneInteractionContext::showPropertiesStub) { -} + : m_imageView(image_view), + m_zones(zones), + m_defaultInteractionCreator(boost::bind(&ZoneInteractionContext::createStdDefaultInteraction, this)), + m_zoneCreationInteractionCreator( + boost::bind(&ZoneInteractionContext::createStdZoneCreationInteraction, this, _1)), + m_vertexDragInteractionCreator( + boost::bind(&ZoneInteractionContext::createStdVertexDragInteraction, this, _1, _2, _3)), + m_zoneDragInteractionCreator(boost::bind(&ZoneInteractionContext::createStdZoneDragInteraction, this, _1, _2)), + m_contextMenuInteractionCreator(boost::bind(&ZoneInteractionContext::createStdContextMenuInteraction, this, _1)), + m_showPropertiesCommand(&ZoneInteractionContext::showPropertiesStub) {} ZoneInteractionContext::~ZoneInteractionContext() = default; InteractionHandler* ZoneInteractionContext::createStdDefaultInteraction() { - return new ZoneDefaultInteraction(*this); + return new ZoneDefaultInteraction(*this); } InteractionHandler* ZoneInteractionContext::createStdZoneCreationInteraction(InteractionState& interaction) { - return new ZoneCreationInteraction(*this, interaction); + return new ZoneCreationInteraction(*this, interaction); } InteractionHandler* ZoneInteractionContext::createStdVertexDragInteraction(InteractionState& interaction, const EditableSpline::Ptr& spline, const SplineVertex::Ptr& vertex) { - return new ZoneVertexDragInteraction(*this, interaction, spline, vertex); + return new ZoneVertexDragInteraction(*this, interaction, spline, vertex); } InteractionHandler* ZoneInteractionContext::createStdZoneDragInteraction(InteractionState& interaction, const EditableSpline::Ptr& spline) { - return new ZoneDragInteraction(*this, interaction, spline); + return new ZoneDragInteraction(*this, interaction, spline); } InteractionHandler* ZoneInteractionContext::createStdContextMenuInteraction(InteractionState& interaction) { - return ZoneContextMenuInteraction::create(*this, interaction); + return ZoneContextMenuInteraction::create(*this, interaction); } diff --git a/zones/ZoneInteractionContext.h b/zones/ZoneInteractionContext.h index 3809dc3d6..0a1b967a4 100644 --- a/zones/ZoneInteractionContext.h +++ b/zones/ZoneInteractionContext.h @@ -19,10 +19,10 @@ #ifndef ZONE_INTERACTION_CONTEXT_H_ #define ZONE_INTERACTION_CONTEXT_H_ +#include #include "EditableSpline.h" -#include "SplineVertex.h" #include "EditableZoneSet.h" -#include +#include "SplineVertex.h" class InteractionHandler; class InteractionState; @@ -30,128 +30,115 @@ class ImageViewBase; class EditableZoneSet; class ZoneInteractionContext { -public: - typedef boost::function DefaultInteractionCreator; + public: + typedef boost::function DefaultInteractionCreator; + + typedef boost::function ZoneCreationInteractionCreator; + + typedef boost::function + VertexDragInteractionCreator; + + typedef boost::function + ZoneDragInteractionCreator; + + typedef boost::function ContextMenuInteractionCreator; + + typedef boost::function ShowPropertiesCommand; + + ZoneInteractionContext(ImageViewBase& image_view, EditableZoneSet& zones); + + virtual ~ZoneInteractionContext(); + + ImageViewBase& imageView() { return m_imageView; } + + EditableZoneSet& zones() { return m_zones; } + + virtual InteractionHandler* createDefaultInteraction() { return m_defaultInteractionCreator(); } + + void setDefaultInteractionCreator(const DefaultInteractionCreator& creator) { m_defaultInteractionCreator = creator; } + + virtual InteractionHandler* createZoneCreationInteraction(InteractionState& interaction) { + return m_zoneCreationInteractionCreator(interaction); + } + + void setZoneCreationInteractionCreator(const ZoneCreationInteractionCreator& creator) { + m_zoneCreationInteractionCreator = creator; + } + + virtual InteractionHandler* createVertexDragInteraction(InteractionState& interaction, + const EditableSpline::Ptr& spline, + const SplineVertex::Ptr& vertex) { + return m_vertexDragInteractionCreator(interaction, spline, vertex); + } + + void setVertexDragInteractionCreator(const VertexDragInteractionCreator& creator) { + m_vertexDragInteractionCreator = creator; + } + + virtual InteractionHandler* createZoneDragInteraction(InteractionState& interaction, + const EditableSpline::Ptr& spline) { + return m_zoneDragInteractionCreator(interaction, spline); + } + + void setZoneDragInteractionCreator(const ZoneDragInteractionCreator& creator) { + m_zoneDragInteractionCreator = creator; + } + + /** + * \note This function may refuse to create a context menu interaction by returning null. + */ + virtual InteractionHandler* createContextMenuInteraction(InteractionState& interaction) { + return m_contextMenuInteractionCreator(interaction); + } + + void setContextMenuInteractionCreator(const ContextMenuInteractionCreator& creator) { + m_contextMenuInteractionCreator = creator; + } + + virtual void showPropertiesCommand(const EditableZoneSet::Zone& zone) { m_showPropertiesCommand(zone); } + + void setShowPropertiesCommand(const ShowPropertiesCommand& command) { m_showPropertiesCommand = command; } + + private: + /** + * Creates an instance of ZoneDefaultInteraction. + */ + InteractionHandler* createStdDefaultInteraction(); + + /** + * Creates an instance of ZoneCreationInteraction. + */ + InteractionHandler* createStdZoneCreationInteraction(InteractionState& interaction); + + /** + * Creates an instance of ZoneVertexDragInteraction. + */ + InteractionHandler* createStdVertexDragInteraction(InteractionState& interaction, + const EditableSpline::Ptr& spline, + const SplineVertex::Ptr& vertex); + + /** + * Creates an instance of ZoneDragInteraction. + */ + InteractionHandler* createStdZoneDragInteraction(InteractionState& interaction, const EditableSpline::Ptr& spline); + + /** + * Creates an instance of ZoneContextMenuInteraction. May return null. + */ + InteractionHandler* createStdContextMenuInteraction(InteractionState& interaction); - typedef boost::function ZoneCreationInteractionCreator; - - typedef boost::function - VertexDragInteractionCreator; - - typedef boost::function - ZoneDragInteractionCreator; - - typedef boost::function ContextMenuInteractionCreator; + static void showPropertiesStub(const EditableZoneSet::Zone&) {} - typedef boost::function ShowPropertiesCommand; - - ZoneInteractionContext(ImageViewBase& image_view, EditableZoneSet& zones); - - virtual ~ZoneInteractionContext(); - - ImageViewBase& imageView() { - return m_rImageView; - } - - EditableZoneSet& zones() { - return m_rZones; - } - - virtual InteractionHandler* createDefaultInteraction() { - return m_defaultInteractionCreator(); - } - - void setDefaultInteractionCreator(const DefaultInteractionCreator& creator) { - m_defaultInteractionCreator = creator; - } - - virtual InteractionHandler* createZoneCreationInteraction(InteractionState& interaction) { - return m_zoneCreationInteractionCreator(interaction); - } - - void setZoneCreationInteractionCreator(const ZoneCreationInteractionCreator& creator) { - m_zoneCreationInteractionCreator = creator; - } - - virtual InteractionHandler* createVertexDragInteraction(InteractionState& interaction, - const EditableSpline::Ptr& spline, - const SplineVertex::Ptr& vertex) { - return m_vertexDragInteractionCreator(interaction, spline, vertex); - } - - void setVertexDragInteractionCreator(const VertexDragInteractionCreator& creator) { - m_vertexDragInteractionCreator = creator; - } - - virtual InteractionHandler* createZoneDragInteraction(InteractionState& interaction, - const EditableSpline::Ptr& spline) { - return m_zoneDragInteractionCreator(interaction, spline); - } - - void setZoneDragInteractionCreator(const ZoneDragInteractionCreator& creator) { - m_zoneDragInteractionCreator = creator; - } - - /** - * \note This function may refuse to create a context menu interaction by returning null. - */ - virtual InteractionHandler* createContextMenuInteraction(InteractionState& interaction) { - return m_contextMenuInteractionCreator(interaction); - } - - void setContextMenuInteractionCreator(const ContextMenuInteractionCreator& creator) { - m_contextMenuInteractionCreator = creator; - } - - virtual void showPropertiesCommand(const EditableZoneSet::Zone& zone) { - m_showPropertiesCommand(zone); - } - - void setShowPropertiesCommand(const ShowPropertiesCommand& command) { - m_showPropertiesCommand = command; - } - -private: - /** - * Creates an instance of ZoneDefaultInteraction. - */ - InteractionHandler* createStdDefaultInteraction(); - - /** - * Creates an instance of ZoneCreationInteraction. - */ - InteractionHandler* createStdZoneCreationInteraction(InteractionState& interaction); - - /** - * Creates an instance of ZoneVertexDragInteraction. - */ - InteractionHandler* createStdVertexDragInteraction(InteractionState& interaction, - const EditableSpline::Ptr& spline, - const SplineVertex::Ptr& vertex); - - /** - * Creates an instance of ZoneDragInteraction. - */ - InteractionHandler* createStdZoneDragInteraction(InteractionState& interaction, const EditableSpline::Ptr& spline); - - /** - * Creates an instance of ZoneContextMenuInteraction. May return null. - */ - InteractionHandler* createStdContextMenuInteraction(InteractionState& interaction); - - static void showPropertiesStub(const EditableZoneSet::Zone&) { - } - - ImageViewBase& m_rImageView; - EditableZoneSet& m_rZones; - DefaultInteractionCreator m_defaultInteractionCreator; - ZoneCreationInteractionCreator m_zoneCreationInteractionCreator; - VertexDragInteractionCreator m_vertexDragInteractionCreator; - ZoneDragInteractionCreator m_zoneDragInteractionCreator; - ContextMenuInteractionCreator m_contextMenuInteractionCreator; - ShowPropertiesCommand m_showPropertiesCommand; + ImageViewBase& m_imageView; + EditableZoneSet& m_zones; + DefaultInteractionCreator m_defaultInteractionCreator; + ZoneCreationInteractionCreator m_zoneCreationInteractionCreator; + VertexDragInteractionCreator m_vertexDragInteractionCreator; + ZoneDragInteractionCreator m_zoneDragInteractionCreator; + ContextMenuInteractionCreator m_contextMenuInteractionCreator; + ShowPropertiesCommand m_showPropertiesCommand; }; diff --git a/zones/ZoneSet.cpp b/zones/ZoneSet.cpp index cf037597a..a2069a300 100644 --- a/zones/ZoneSet.cpp +++ b/zones/ZoneSet.cpp @@ -20,31 +20,31 @@ #include ZoneSet::ZoneSet(const QDomElement& el, const PropertyFactory& prop_factory) { - const QString zone_str("zone"); - - QDomNode node(el.firstChild()); - for (; !node.isNull(); node = node.nextSibling()) { - if (!node.isElement()) { - continue; - } - if (node.nodeName() != zone_str) { - continue; - } - - const Zone zone(node.toElement(), prop_factory); - if (zone.isValid()) { - m_zones.push_back(zone); - } + const QString zone_str("zone"); + + QDomNode node(el.firstChild()); + for (; !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) { + continue; + } + if (node.nodeName() != zone_str) { + continue; } + + const Zone zone(node.toElement(), prop_factory); + if (zone.isValid()) { + m_zones.push_back(zone); + } + } } QDomElement ZoneSet::toXml(QDomDocument& doc, const QString& name) const { - const QString zone_str("zone"); + const QString zone_str("zone"); - QDomElement el(doc.createElement(name)); - for (const Zone& zone : m_zones) { - el.appendChild(zone.toXml(doc, zone_str)); - } + QDomElement el(doc.createElement(name)); + for (const Zone& zone : m_zones) { + el.appendChild(zone.toXml(doc, zone_str)); + } - return el; + return el; } diff --git a/zones/ZoneSet.h b/zones/ZoneSet.h index 283924c6b..dab710d7c 100644 --- a/zones/ZoneSet.h +++ b/zones/ZoneSet.h @@ -19,9 +19,9 @@ #ifndef ZONE_SET_H_ #define ZONE_SET_H_ -#include "Zone.h" #include #include +#include "Zone.h" class PropertyFactory; class QDomDocument; @@ -29,39 +29,29 @@ class QDomElement; class QString; class ZoneSet { -public: - typedef std::list::const_iterator const_iterator; + public: + typedef std::list::const_iterator const_iterator; - ZoneSet() = default; + ZoneSet() = default; - ZoneSet(const QDomElement& el, const PropertyFactory& prop_factory); + ZoneSet(const QDomElement& el, const PropertyFactory& prop_factory); - virtual ~ZoneSet() = default; + virtual ~ZoneSet() = default; - QDomElement toXml(QDomDocument& doc, const QString& name) const; + QDomElement toXml(QDomDocument& doc, const QString& name) const; - bool empty() const { - return m_zones.empty(); - } + bool empty() const { return m_zones.empty(); } - void add(const Zone& zone) { - m_zones.push_back(zone); - } + void add(const Zone& zone) { m_zones.push_back(zone); } - const_iterator erase(const_iterator position) { - return m_zones.erase(position); - } + const_iterator erase(const_iterator position) { return m_zones.erase(position); } - const_iterator begin() const { - return m_zones.begin(); - } + const_iterator begin() const { return m_zones.begin(); } - const_iterator end() const { - return m_zones.end(); - } + const_iterator end() const { return m_zones.end(); } -private: - std::list m_zones; + private: + std::list m_zones; }; diff --git a/zones/ZoneVertexDragInteraction.cpp b/zones/ZoneVertexDragInteraction.cpp index c68e8b14d..17a7f34cc 100644 --- a/zones/ZoneVertexDragInteraction.cpp +++ b/zones/ZoneVertexDragInteraction.cpp @@ -17,158 +17,157 @@ */ #include "ZoneVertexDragInteraction.h" -#include "ZoneInteractionContext.h" -#include "ImageViewBase.h" #include #include +#include "ImageViewBase.h" +#include "ZoneInteractionContext.h" ZoneVertexDragInteraction::ZoneVertexDragInteraction(ZoneInteractionContext& context, InteractionState& interaction, const EditableSpline::Ptr& spline, const SplineVertex::Ptr& vertex) - : m_rContext(context), m_ptrSpline(spline), m_ptrVertex(vertex) { - const QPointF screen_mouse_pos(m_rContext.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5)); - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - m_dragOffset = to_screen.map(vertex->point()) - screen_mouse_pos; + : m_context(context), m_spline(spline), m_vertex(vertex) { + const QPointF screen_mouse_pos(m_context.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5)); + const QTransform to_screen(m_context.imageView().imageToWidget()); + m_dragOffset = to_screen.map(vertex->point()) - screen_mouse_pos; - interaction.capture(m_interaction); - checkProximity(interaction); + interaction.capture(m_interaction); + checkProximity(interaction); } void ZoneVertexDragInteraction::onPaint(QPainter& painter, const InteractionState& interaction) { - painter.setWorldMatrixEnabled(false); - painter.setRenderHint(QPainter::Antialiasing); - - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - - for (const EditableZoneSet::Zone& zone : m_rContext.zones()) { - const EditableSpline::Ptr& spline = zone.spline(); - - if (spline != m_ptrSpline) { - // Draw the whole spline in solid color. - m_visualizer.drawSpline(painter, to_screen, spline); - continue; - } - // Draw the solid part of the spline. - QPolygonF points; - SplineVertex::Ptr vertex(m_ptrVertex->next(SplineVertex::LOOP)); - for (; vertex != m_ptrVertex; vertex = vertex->next(SplineVertex::LOOP)) { - points.push_back(to_screen.map(vertex->point())); - } - - m_visualizer.prepareForSpline(painter, spline); - painter.drawPolyline(points); + painter.setWorldMatrixEnabled(false); + painter.setRenderHint(QPainter::Antialiasing); + + const QTransform to_screen(m_context.imageView().imageToWidget()); + + for (const EditableZoneSet::Zone& zone : m_context.zones()) { + const EditableSpline::Ptr& spline = zone.spline(); + + if (spline != m_spline) { + // Draw the whole spline in solid color. + m_visualizer.drawSpline(painter, to_screen, spline); + continue; + } + // Draw the solid part of the spline. + QPolygonF points; + SplineVertex::Ptr vertex(m_vertex->next(SplineVertex::LOOP)); + for (; vertex != m_vertex; vertex = vertex->next(SplineVertex::LOOP)) { + points.push_back(to_screen.map(vertex->point())); } - QLinearGradient gradient; // From remote to selected point. - gradient.setColorAt(0.0, m_visualizer.solidColor()); - gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); + m_visualizer.prepareForSpline(painter, spline); + painter.drawPolyline(points); + } + + QLinearGradient gradient; // From remote to selected point. + gradient.setColorAt(0.0, m_visualizer.solidColor()); + gradient.setColorAt(1.0, m_visualizer.highlightDarkColor()); - QPen gradient_pen; - gradient_pen.setCosmetic(true); - gradient_pen.setWidthF(1.5); + QPen gradient_pen; + gradient_pen.setCosmetic(true); + gradient_pen.setWidthF(1.5); - painter.setBrush(Qt::NoBrush); + painter.setBrush(Qt::NoBrush); - const QPointF pt(to_screen.map(m_ptrVertex->point())); - const QPointF prev(to_screen.map(m_ptrVertex->prev(SplineVertex::LOOP)->point())); - const QPointF next(to_screen.map(m_ptrVertex->next(SplineVertex::LOOP)->point())); + const QPointF pt(to_screen.map(m_vertex->point())); + const QPointF prev(to_screen.map(m_vertex->prev(SplineVertex::LOOP)->point())); + const QPointF next(to_screen.map(m_vertex->next(SplineVertex::LOOP)->point())); - gradient.setStart(prev); - gradient.setFinalStop(pt); - gradient_pen.setBrush(gradient); - painter.setPen(gradient_pen); - painter.drawLine(prev, pt); + gradient.setStart(prev); + gradient.setFinalStop(pt); + gradient_pen.setBrush(gradient); + painter.setPen(gradient_pen); + painter.drawLine(prev, pt); - gradient.setStart(next); - gradient_pen.setBrush(gradient); - painter.setPen(gradient_pen); - painter.drawLine(next, pt); + gradient.setStart(next); + gradient_pen.setBrush(gradient); + painter.setPen(gradient_pen); + painter.drawLine(next, pt); - m_visualizer.drawVertex(painter, to_screen.map(m_ptrVertex->point()), m_visualizer.highlightBrightColor()); + m_visualizer.drawVertex(painter, to_screen.map(m_vertex->point()), m_visualizer.highlightBrightColor()); } // ZoneVertexDragInteraction::onPaint void ZoneVertexDragInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { - if (event->button() == Qt::LeftButton) { - if ((m_ptrVertex->point() == m_ptrVertex->next(SplineVertex::LOOP)->point()) - || (m_ptrVertex->point() == m_ptrVertex->prev(SplineVertex::LOOP)->point())) { - if (m_ptrVertex->hasAtLeastSiblings(3)) { - m_ptrVertex->remove(); - } - } - - m_rContext.zones().commit(); - makePeerPreceeder(*m_rContext.createDefaultInteraction()); - delete this; + if (event->button() == Qt::LeftButton) { + if ((m_vertex->point() == m_vertex->next(SplineVertex::LOOP)->point()) + || (m_vertex->point() == m_vertex->prev(SplineVertex::LOOP)->point())) { + if (m_vertex->hasAtLeastSiblings(3)) { + m_vertex->remove(); + } } + + m_context.zones().commit(); + makePeerPreceeder(*m_context.createDefaultInteraction()); + delete this; + } } void ZoneVertexDragInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { - const QTransform from_screen(m_rContext.imageView().widgetToImage()); - m_ptrVertex->setPoint(from_screen.map(event->pos() + QPointF(0.5, 0.5) + m_dragOffset)); - - checkProximity(interaction); - - const Qt::KeyboardModifiers mask = event->modifiers(); - if (mask == Qt::ControlModifier) { - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - - const QPointF current = to_screen.map(m_ptrVertex->point()); - QPointF prev = to_screen.map(m_ptrVertex->prev(SplineVertex::LOOP)->point()); - QPointF next = to_screen.map(m_ptrVertex->next(SplineVertex::LOOP)->point()); - - if (!((current == prev) && (current == next))) { - double prev_angle_cos = std::abs( - (prev.x() - current.x()) - / std::sqrt(std::pow((prev.y() - current.y()), 2) + std::pow((prev.x() - current.x()), 2))); - double next_angle_cos = std::abs( - (next.x() - current.x()) - / std::sqrt(std::pow((next.y() - current.y()), 2) + std::pow((next.x() - current.x()), 2))); - - - if ((prev_angle_cos < next_angle_cos) - || (std::isnan(prev_angle_cos) && (next_angle_cos > (1.0 / std::sqrt(2)))) - || (std::isnan(next_angle_cos) && (prev_angle_cos < (1.0 / std::sqrt(2))))) { - prev.setX(current.x()); - next.setY(current.y()); - } else { - next.setX(current.x()); - prev.setY(current.y()); - } - - m_ptrVertex->prev(SplineVertex::LOOP)->setPoint(from_screen.map(prev)); - m_ptrVertex->next(SplineVertex::LOOP)->setPoint(from_screen.map(next)); - } + const QTransform from_screen(m_context.imageView().widgetToImage()); + m_vertex->setPoint(from_screen.map(event->pos() + QPointF(0.5, 0.5) + m_dragOffset)); + + checkProximity(interaction); + + const Qt::KeyboardModifiers mask = event->modifiers(); + if (mask == Qt::ControlModifier) { + const QTransform to_screen(m_context.imageView().imageToWidget()); + + const QPointF current = to_screen.map(m_vertex->point()); + QPointF prev = to_screen.map(m_vertex->prev(SplineVertex::LOOP)->point()); + QPointF next = to_screen.map(m_vertex->next(SplineVertex::LOOP)->point()); + + if (!((current == prev) && (current == next))) { + double prev_angle_cos + = std::abs((prev.x() - current.x()) + / std::sqrt(std::pow((prev.y() - current.y()), 2) + std::pow((prev.x() - current.x()), 2))); + double next_angle_cos + = std::abs((next.x() - current.x()) + / std::sqrt(std::pow((next.y() - current.y()), 2) + std::pow((next.x() - current.x()), 2))); + + + if ((prev_angle_cos < next_angle_cos) || (std::isnan(prev_angle_cos) && (next_angle_cos > (1.0 / std::sqrt(2)))) + || (std::isnan(next_angle_cos) && (prev_angle_cos < (1.0 / std::sqrt(2))))) { + prev.setX(current.x()); + next.setY(current.y()); + } else { + next.setX(current.x()); + prev.setY(current.y()); + } + + m_vertex->prev(SplineVertex::LOOP)->setPoint(from_screen.map(prev)); + m_vertex->next(SplineVertex::LOOP)->setPoint(from_screen.map(next)); } + } - m_rContext.imageView().update(); + m_context.imageView().update(); } // ZoneVertexDragInteraction::onMouseMoveEvent void ZoneVertexDragInteraction::checkProximity(const InteractionState& interaction) { - bool can_merge = false; + bool can_merge = false; - if (m_ptrVertex->hasAtLeastSiblings(3)) { - const QTransform to_screen(m_rContext.imageView().imageToWidget()); - const QPointF origin(to_screen.map(m_ptrVertex->point())); + if (m_vertex->hasAtLeastSiblings(3)) { + const QTransform to_screen(m_context.imageView().imageToWidget()); + const QPointF origin(to_screen.map(m_vertex->point())); - const QPointF prev(m_ptrVertex->prev(SplineVertex::LOOP)->point()); - const Proximity prox_prev(origin, to_screen.map(prev)); + const QPointF prev(m_vertex->prev(SplineVertex::LOOP)->point()); + const Proximity prox_prev(origin, to_screen.map(prev)); - const QPointF next(m_ptrVertex->next(SplineVertex::LOOP)->point()); - const Proximity prox_next(origin, to_screen.map(next)); + const QPointF next(m_vertex->next(SplineVertex::LOOP)->point()); + const Proximity prox_next(origin, to_screen.map(next)); - if ((prox_prev <= interaction.proximityThreshold()) && (prox_prev < prox_next)) { - m_ptrVertex->setPoint(prev); - can_merge = true; - } else if (prox_next <= interaction.proximityThreshold()) { - m_ptrVertex->setPoint(next); - can_merge = true; - } + if ((prox_prev <= interaction.proximityThreshold()) && (prox_prev < prox_next)) { + m_vertex->setPoint(prev); + can_merge = true; + } else if (prox_next <= interaction.proximityThreshold()) { + m_vertex->setPoint(next); + can_merge = true; } + } - if (can_merge) { - m_interaction.setInteractionStatusTip(tr("Merge these two vertices.")); - } else { - m_interaction.setInteractionStatusTip(tr("Move the vertex to one of its neighbors to merge them.")); - } + if (can_merge) { + m_interaction.setInteractionStatusTip(tr("Merge these two vertices.")); + } else { + m_interaction.setInteractionStatusTip(tr("Move the vertex to one of its neighbors to merge them.")); + } } diff --git a/zones/ZoneVertexDragInteraction.h b/zones/ZoneVertexDragInteraction.h index 117f4aed6..dedb52deb 100644 --- a/zones/ZoneVertexDragInteraction.h +++ b/zones/ZoneVertexDragInteraction.h @@ -19,39 +19,39 @@ #ifndef ZONE_VERTEX_DRAG_INTERACTION_H_ #define ZONE_VERTEX_DRAG_INTERACTION_H_ +#include +#include #include "BasicSplineVisualizer.h" #include "EditableSpline.h" #include "InteractionHandler.h" #include "InteractionState.h" -#include -#include class ZoneInteractionContext; class ZoneVertexDragInteraction : public InteractionHandler { - Q_DECLARE_TR_FUNCTIONS(ZoneVertexDragInteraction) -public: - ZoneVertexDragInteraction(ZoneInteractionContext& context, - InteractionState& interaction, - const EditableSpline::Ptr& spline, - const SplineVertex::Ptr& vertex); + Q_DECLARE_TR_FUNCTIONS(ZoneVertexDragInteraction) + public: + ZoneVertexDragInteraction(ZoneInteractionContext& context, + InteractionState& interaction, + const EditableSpline::Ptr& spline, + const SplineVertex::Ptr& vertex); -protected: - void onPaint(QPainter& painter, const InteractionState& interaction) override; + protected: + void onPaint(QPainter& painter, const InteractionState& interaction) override; - void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) override; - void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; + void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) override; -private: - void checkProximity(const InteractionState& interaction); + private: + void checkProximity(const InteractionState& interaction); - ZoneInteractionContext& m_rContext; - EditableSpline::Ptr m_ptrSpline; - SplineVertex::Ptr m_ptrVertex; - InteractionState::Captor m_interaction; - BasicSplineVisualizer m_visualizer; - QPointF m_dragOffset; + ZoneInteractionContext& m_context; + EditableSpline::Ptr m_spline; + SplineVertex::Ptr m_vertex; + InteractionState::Captor m_interaction; + BasicSplineVisualizer m_visualizer; + QPointF m_dragOffset; };